From 2d11a5c55ceb410c5b3abd6e9b77543a19e6cca2 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Tue, 21 Apr 2020 15:54:33 -0500 Subject: [PATCH] Save custom filter criteria on rotate --- .../astrid/api/MultipleSelectCriterion.java | 27 +++ .../astrid/api/TextInputCriterion.java | 28 +++ .../astrid/core/CriterionInstance.java | 87 ++++++++ .../astrid/core/CustomFilterActivity.java | 185 ++++++++---------- .../astrid/core/CustomFilterAdapter.java | 1 - .../tasks/filters/FilterCriteriaProvider.java | 51 ++++- 6 files changed, 273 insertions(+), 106 deletions(-) create mode 100644 app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java diff --git a/app/src/main/java/com/todoroo/astrid/api/MultipleSelectCriterion.java b/app/src/main/java/com/todoroo/astrid/api/MultipleSelectCriterion.java index e24af5d3a..2cf4b07ef 100644 --- a/app/src/main/java/com/todoroo/astrid/api/MultipleSelectCriterion.java +++ b/app/src/main/java/com/todoroo/astrid/api/MultipleSelectCriterion.java @@ -9,6 +9,7 @@ package com.todoroo.astrid.api; import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; +import java.util.Arrays; import java.util.Map; /** @@ -84,4 +85,30 @@ public class MultipleSelectCriterion extends CustomFilterCriterion implements Pa dest.writeStringArray(entryValues); writeToParcel(dest); } + + @Override + public String toString() { + return "MultipleSelectCriterion{" + + "entryTitles=" + + Arrays.toString(entryTitles) + + ", entryValues=" + + Arrays.toString(entryValues) + + ", valuesForNewTasks=" + + valuesForNewTasks + + ", identifier='" + + identifier + + '\'' + + ", text='" + + text + + '\'' + + ", sql='" + + sql + + '\'' + + ", name='" + + name + + '\'' + + ", icon=" + + icon + + '}'; + } } diff --git a/app/src/main/java/com/todoroo/astrid/api/TextInputCriterion.java b/app/src/main/java/com/todoroo/astrid/api/TextInputCriterion.java index f8e7415c0..9469f5788 100644 --- a/app/src/main/java/com/todoroo/astrid/api/TextInputCriterion.java +++ b/app/src/main/java/com/todoroo/astrid/api/TextInputCriterion.java @@ -79,4 +79,32 @@ public class TextInputCriterion extends CustomFilterCriterion implements Parcela dest.writeString(hint); writeToParcel(dest); } + + @Override + public String toString() { + return "TextInputCriterion{" + + "hint='" + + hint + + '\'' + + ", prompt='" + + prompt + + '\'' + + ", valuesForNewTasks=" + + valuesForNewTasks + + ", identifier='" + + identifier + + '\'' + + ", text='" + + text + + '\'' + + ", sql='" + + sql + + '\'' + + ", name='" + + name + + '\'' + + ", icon=" + + icon + + '}'; + } } diff --git a/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java new file mode 100644 index 000000000..2d3568754 --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java @@ -0,0 +1,87 @@ +package com.todoroo.astrid.core; + +import com.todoroo.astrid.api.CustomFilterCriterion; +import com.todoroo.astrid.api.MultipleSelectCriterion; +import com.todoroo.astrid.api.TextInputCriterion; + +public class CriterionInstance { + + static final int TYPE_ADD = 0; + static final int TYPE_SUBTRACT = 1; + static final int TYPE_INTERSECT = 2; + static final int TYPE_UNIVERSE = 3; + + /** criteria for this instance */ + public CustomFilterCriterion criterion; + + /** which of the entries is selected (MultipleSelect) */ + int selectedIndex = -1; + + /** text of selection (TextInput) */ + String selectedText = null; + + /** type of join */ + public int type = TYPE_INTERSECT; + + public int end; + /** statistics for filter count */ + int start; + + int max; + + 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$ + } + + @Override + public String toString() { + return "CriterionInstance{" + + "criterion=" + + criterion + + ", selectedIndex=" + + selectedIndex + + ", selectedText='" + + selectedText + + '\'' + + ", type=" + + type + + ", end=" + + end + + ", start=" + + start + + ", max=" + + max + + '}'; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/core/CustomFilterActivity.java b/app/src/main/java/com/todoroo/astrid/core/CustomFilterActivity.java index 3c6abb0d2..5f5213b0b 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CustomFilterActivity.java +++ b/app/src/main/java/com/todoroo/astrid/core/CustomFilterActivity.java @@ -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 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 deserializeCriterion(@Nullable String criterion) { + if (Strings.isNullOrEmpty(criterion)) { + return Collections.emptyList(); } + List entries = new ArrayList<>(); + for (String row : criterion.split("\n")) { + CriterionInstance entry = new CriterionInstance(); + List 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 startingCriteria = new ArrayList<>(); - startingCriteria.add(getStartingUniverse()); - adapter = new CustomFilterAdapter(this, dialogBuilder, startingCriteria, locale); + List 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$ - } - } } diff --git a/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java b/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java index d4ae607bd..777c8c1e2 100644 --- a/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java +++ b/app/src/main/java/com/todoroo/astrid/core/CustomFilterAdapter.java @@ -20,7 +20,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import com.todoroo.astrid.api.MultipleSelectCriterion; import com.todoroo.astrid.api.TextInputCriterion; -import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance; import java.util.List; import org.tasks.R; import org.tasks.dialogs.AlertDialogBuilder; diff --git a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java index 68dcf3298..a93674767 100644 --- a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java +++ b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java @@ -14,6 +14,7 @@ import com.todoroo.astrid.api.CustomFilterCriterion; import com.todoroo.astrid.api.MultipleSelectCriterion; import com.todoroo.astrid.api.PermaSql; import com.todoroo.astrid.api.TextInputCriterion; +import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance; import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task.Priority; @@ -35,13 +36,14 @@ import org.tasks.injection.ForApplication; public class FilterCriteriaProvider { - private static final String IDENTIFIER_TITLE = "title"; // $NON-NLS-1$ - private static final String IDENTIFIER_IMPORTANCE = "importance"; // $NON-NLS-1$ - private static final String IDENTIFIER_DUEDATE = "dueDate"; // $NON-NLS-1$ - private static final String IDENTIFIER_GTASKS = "gtaskslist"; // $NON-NLS-1$ - private static final String IDENTIFIER_CALDAV = "caldavlist"; // $NON-NLS-1$ - private static final String IDENTIFIER_TAG_IS = "tag_is"; // $NON-NLS-1$ - private static final String IDENTIFIER_TAG_CONTAINS = "tag_contains"; // $NON-NLS-1$ + private static final String IDENTIFIER_UNIVERSE = "active"; + private static final String IDENTIFIER_TITLE = "title"; + private static final String IDENTIFIER_IMPORTANCE = "importance"; + private static final String IDENTIFIER_DUEDATE = "dueDate"; + private static final String IDENTIFIER_GTASKS = "gtaskslist"; + private static final String IDENTIFIER_CALDAV = "caldavlist"; + private static final String IDENTIFIER_TAG_IS = "tag_is"; + private static final String IDENTIFIER_TAG_CONTAINS = "tag_contains"; private final Context context; private final TagDataDao tagDataDao; @@ -63,6 +65,41 @@ public class FilterCriteriaProvider { this.caldavDao = caldavDao; } + public CustomFilterCriterion getFilterCriteria(String identifier) { + switch (identifier) { + case IDENTIFIER_UNIVERSE: + return getStartingUniverse(); + case IDENTIFIER_TITLE: + return getTaskTitleContainsFilter(); + case IDENTIFIER_IMPORTANCE: + return getPriorityFilter(); + case IDENTIFIER_DUEDATE: + return getDueDateFilter(); + case IDENTIFIER_GTASKS: + return getGtasksFilterCriteria(); + case IDENTIFIER_CALDAV: + return getCaldavFilterCriteria(); + case IDENTIFIER_TAG_IS: + return getTagFilter(); + case IDENTIFIER_TAG_CONTAINS: + return getTagNameContainsFilter(); + default: + throw new RuntimeException("Unknown identifier: " + identifier); + } + } + + public CustomFilterCriterion getStartingUniverse() { + return new MultipleSelectCriterion( + IDENTIFIER_UNIVERSE, + context.getString(R.string.CFA_universe_all), + null, + null, + null, + null, + null, + null); + } + public List getAll() { List result = newArrayList();