|
|
|
@ -7,7 +7,9 @@
|
|
|
|
|
package com.todoroo.astrid.core;
|
|
|
|
|
|
|
|
|
|
import static android.text.TextUtils.isEmpty;
|
|
|
|
|
import static com.google.common.collect.Lists.transform;
|
|
|
|
|
import static com.todoroo.andlib.utility.AndroidUtilities.mapToSerializedString;
|
|
|
|
|
import static java.util.Arrays.asList;
|
|
|
|
|
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
@ -19,8 +21,13 @@ import android.view.MenuItem;
|
|
|
|
|
import android.view.View;
|
|
|
|
|
import android.view.inputmethod.InputMethodManager;
|
|
|
|
|
import android.widget.ListView;
|
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
|
import androidx.appcompat.widget.Toolbar;
|
|
|
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
|
import com.google.common.base.Joiner;
|
|
|
|
|
import com.google.common.base.Splitter;
|
|
|
|
|
import com.google.common.base.Strings;
|
|
|
|
|
import com.todoroo.andlib.data.Property.CountProperty;
|
|
|
|
|
import com.todoroo.andlib.sql.Query;
|
|
|
|
|
import com.todoroo.andlib.sql.UnaryCriterion;
|
|
|
|
@ -36,6 +43,7 @@ import com.todoroo.astrid.dao.Database;
|
|
|
|
|
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
|
|
|
|
|
import com.todoroo.astrid.data.Task;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
@ -49,6 +57,7 @@ import org.tasks.filters.FilterCriteriaProvider;
|
|
|
|
|
import org.tasks.injection.ActivityComponent;
|
|
|
|
|
import org.tasks.injection.ThemedInjectingAppCompatActivity;
|
|
|
|
|
import org.tasks.locale.Locale;
|
|
|
|
|
import timber.log.Timber;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Activity that allows users to build custom filters
|
|
|
|
@ -60,7 +69,7 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
|
|
|
|
|
static final int MENU_GROUP_CONTEXT_TYPE = 1;
|
|
|
|
|
static final int MENU_GROUP_CONTEXT_DELETE = 2;
|
|
|
|
|
private static final String IDENTIFIER_UNIVERSE = "active"; // $NON-NLS-1$
|
|
|
|
|
private static final String EXTRA_CRITERIA = "extra_criteria";
|
|
|
|
|
private static final int MENU_GROUP_FILTER = 0;
|
|
|
|
|
|
|
|
|
|
@Inject Database database;
|
|
|
|
@ -75,26 +84,59 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
private CustomFilterAdapter adapter;
|
|
|
|
|
|
|
|
|
|
private static String serializeFilters(CustomFilterAdapter adapter) {
|
|
|
|
|
StringBuilder values = new StringBuilder();
|
|
|
|
|
List<String> rows = new ArrayList<>();
|
|
|
|
|
for (int i = 0; i < adapter.getCount(); i++) {
|
|
|
|
|
CriterionInstance item = adapter.getItem(i);
|
|
|
|
|
|
|
|
|
|
// criterion|entry|text|type|sql
|
|
|
|
|
values
|
|
|
|
|
.append(escape(item.criterion.identifier))
|
|
|
|
|
.append(AndroidUtilities.SERIALIZATION_SEPARATOR);
|
|
|
|
|
values
|
|
|
|
|
.append(escape(item.getValueFromCriterion()))
|
|
|
|
|
.append(AndroidUtilities.SERIALIZATION_SEPARATOR);
|
|
|
|
|
values.append(escape(item.criterion.text)).append(AndroidUtilities.SERIALIZATION_SEPARATOR);
|
|
|
|
|
values.append(item.type).append(AndroidUtilities.SERIALIZATION_SEPARATOR);
|
|
|
|
|
if (item.criterion.sql != null) {
|
|
|
|
|
values.append(item.criterion.sql);
|
|
|
|
|
}
|
|
|
|
|
values.append('\n');
|
|
|
|
|
String row =
|
|
|
|
|
Joiner.on(AndroidUtilities.SERIALIZATION_SEPARATOR)
|
|
|
|
|
.join(
|
|
|
|
|
asList(
|
|
|
|
|
escape(item.criterion.identifier),
|
|
|
|
|
escape(item.getValueFromCriterion()),
|
|
|
|
|
escape(item.criterion.text),
|
|
|
|
|
item.type,
|
|
|
|
|
item.criterion.sql == null ? "" : item.criterion.sql));
|
|
|
|
|
Timber.d("%s -> %s", item, row);
|
|
|
|
|
rows.add(row);
|
|
|
|
|
}
|
|
|
|
|
return Joiner.on("\n").join(rows);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<CriterionInstance> deserializeCriterion(@Nullable String criterion) {
|
|
|
|
|
if (Strings.isNullOrEmpty(criterion)) {
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
List<CriterionInstance> entries = new ArrayList<>();
|
|
|
|
|
for (String row : criterion.split("\n")) {
|
|
|
|
|
CriterionInstance entry = new CriterionInstance();
|
|
|
|
|
List<String> split =
|
|
|
|
|
transform(
|
|
|
|
|
Splitter.on(AndroidUtilities.SERIALIZATION_SEPARATOR).splitToList(row),
|
|
|
|
|
CustomFilterActivity::unescape);
|
|
|
|
|
if (split.size() != 4 && split.size() != 5) {
|
|
|
|
|
Timber.e("invalid row: %s", row);
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return values.toString();
|
|
|
|
|
entry.criterion = filterCriteriaProvider.getFilterCriteria(split.get(0));
|
|
|
|
|
String value = split.get(1);
|
|
|
|
|
if (entry.criterion instanceof TextInputCriterion) {
|
|
|
|
|
entry.selectedText = value;
|
|
|
|
|
} else if (entry.criterion instanceof MultipleSelectCriterion) {
|
|
|
|
|
MultipleSelectCriterion multipleSelectCriterion = (MultipleSelectCriterion) entry.criterion;
|
|
|
|
|
if (multipleSelectCriterion.entryValues != null) {
|
|
|
|
|
entry.selectedIndex = asList(multipleSelectCriterion.entryValues).indexOf(value);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Timber.d("Ignored value %s for %s", value, entry.criterion);
|
|
|
|
|
}
|
|
|
|
|
entry.type = Integer.parseInt(split.get(3));
|
|
|
|
|
entry.criterion.sql = split.get(4);
|
|
|
|
|
Timber.d("%s -> %s", row, entry);
|
|
|
|
|
entries.add(entry);
|
|
|
|
|
}
|
|
|
|
|
return entries;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String escape(String item) {
|
|
|
|
@ -105,6 +147,21 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static String unescape(String item) {
|
|
|
|
|
if (Strings.isNullOrEmpty(item)) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
return item.replace(
|
|
|
|
|
AndroidUtilities.SEPARATOR_ESCAPE, AndroidUtilities.SERIALIZATION_SEPARATOR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
|
|
|
|
super.onSaveInstanceState(outState);
|
|
|
|
|
|
|
|
|
|
outState.putString(EXTRA_CRITERIA, serializeFilters(adapter));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
@ -121,9 +178,19 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
themeColor.apply(toolbar);
|
|
|
|
|
listView = findViewById(android.R.id.list);
|
|
|
|
|
|
|
|
|
|
List<CriterionInstance> startingCriteria = new ArrayList<>();
|
|
|
|
|
startingCriteria.add(getStartingUniverse());
|
|
|
|
|
adapter = new CustomFilterAdapter(this, dialogBuilder, startingCriteria, locale);
|
|
|
|
|
List<CriterionInstance> criteria =
|
|
|
|
|
new ArrayList<>(
|
|
|
|
|
deserializeCriterion(
|
|
|
|
|
savedInstanceState == null
|
|
|
|
|
? getIntent().getStringExtra(EXTRA_CRITERIA)
|
|
|
|
|
: savedInstanceState.getString(EXTRA_CRITERIA)));
|
|
|
|
|
if (criteria.isEmpty()) {
|
|
|
|
|
CriterionInstance instance = new CriterionInstance();
|
|
|
|
|
instance.criterion = filterCriteriaProvider.getStartingUniverse();
|
|
|
|
|
instance.type = CriterionInstance.TYPE_UNIVERSE;
|
|
|
|
|
criteria.add(instance);
|
|
|
|
|
}
|
|
|
|
|
adapter = new CustomFilterAdapter(this, dialogBuilder, criteria, locale);
|
|
|
|
|
listView.setAdapter(adapter);
|
|
|
|
|
updateList();
|
|
|
|
|
|
|
|
|
@ -135,22 +202,6 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
component.inject(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CriterionInstance getStartingUniverse() {
|
|
|
|
|
CriterionInstance instance = new CriterionInstance();
|
|
|
|
|
instance.criterion =
|
|
|
|
|
new MultipleSelectCriterion(
|
|
|
|
|
IDENTIFIER_UNIVERSE,
|
|
|
|
|
getString(R.string.CFA_universe_all),
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null);
|
|
|
|
|
instance.type = CriterionInstance.TYPE_UNIVERSE;
|
|
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void setUpListeners() {
|
|
|
|
|
findViewById(R.id.add).setOnClickListener(v -> listView.showContextMenu());
|
|
|
|
|
|
|
|
|
@ -270,11 +321,10 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
case CriterionInstance.TYPE_INTERSECT:
|
|
|
|
|
sql.append("AND ");
|
|
|
|
|
break;
|
|
|
|
|
case CriterionInstance.TYPE_UNIVERSE:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// special code for all tasks universe
|
|
|
|
|
if (instance.criterion.sql == null) {
|
|
|
|
|
if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) {
|
|
|
|
|
sql.append(TaskCriteria.activeAndVisible()).append(' ');
|
|
|
|
|
} else {
|
|
|
|
|
String subSql = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value));
|
|
|
|
@ -381,65 +431,4 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
|
|
|
|
|
storeObject.setId(filterDao.insertOrUpdate(storeObject));
|
|
|
|
|
return storeObject.getId() >= 0 ? storeObject : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class CriterionInstance {
|
|
|
|
|
|
|
|
|
|
public static final int TYPE_ADD = 0;
|
|
|
|
|
public static final int TYPE_SUBTRACT = 1;
|
|
|
|
|
public static final int TYPE_INTERSECT = 2;
|
|
|
|
|
public static final int TYPE_UNIVERSE = 3;
|
|
|
|
|
|
|
|
|
|
/** criteria for this instance */
|
|
|
|
|
public CustomFilterCriterion criterion;
|
|
|
|
|
|
|
|
|
|
/** which of the entries is selected (MultipleSelect) */
|
|
|
|
|
public int selectedIndex = -1;
|
|
|
|
|
|
|
|
|
|
/** text of selection (TextInput) */
|
|
|
|
|
public String selectedText = null;
|
|
|
|
|
|
|
|
|
|
/** type of join */
|
|
|
|
|
public int type = TYPE_INTERSECT;
|
|
|
|
|
|
|
|
|
|
public int end;
|
|
|
|
|
/** statistics for filter count */
|
|
|
|
|
int start;
|
|
|
|
|
|
|
|
|
|
int max;
|
|
|
|
|
|
|
|
|
|
public String getTitleFromCriterion() {
|
|
|
|
|
if (criterion instanceof MultipleSelectCriterion) {
|
|
|
|
|
if (selectedIndex >= 0
|
|
|
|
|
&& ((MultipleSelectCriterion) criterion).entryTitles != null
|
|
|
|
|
&& selectedIndex < ((MultipleSelectCriterion) criterion).entryTitles.length) {
|
|
|
|
|
String title = ((MultipleSelectCriterion) criterion).entryTitles[selectedIndex];
|
|
|
|
|
return criterion.text.replace("?", title);
|
|
|
|
|
}
|
|
|
|
|
return criterion.text;
|
|
|
|
|
} else if (criterion instanceof TextInputCriterion) {
|
|
|
|
|
if (selectedText == null) {
|
|
|
|
|
return criterion.text;
|
|
|
|
|
}
|
|
|
|
|
return criterion.text.replace("?", selectedText);
|
|
|
|
|
}
|
|
|
|
|
throw new UnsupportedOperationException("Unknown criterion type"); // $NON-NLS-1$
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String getValueFromCriterion() {
|
|
|
|
|
if (type == TYPE_UNIVERSE) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (criterion instanceof MultipleSelectCriterion) {
|
|
|
|
|
if (selectedIndex >= 0
|
|
|
|
|
&& ((MultipleSelectCriterion) criterion).entryValues != null
|
|
|
|
|
&& selectedIndex < ((MultipleSelectCriterion) criterion).entryValues.length) {
|
|
|
|
|
return ((MultipleSelectCriterion) criterion).entryValues[selectedIndex];
|
|
|
|
|
}
|
|
|
|
|
return criterion.text;
|
|
|
|
|
} else if (criterion instanceof TextInputCriterion) {
|
|
|
|
|
return selectedText;
|
|
|
|
|
}
|
|
|
|
|
throw new UnsupportedOperationException("Unknown criterion type"); // $NON-NLS-1$
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|