Save custom filter criteria on rotate

pull/996/head
Alex Baker 6 years ago
parent 347dadfcb0
commit 2d11a5c55c

@ -9,6 +9,7 @@ package com.todoroo.astrid.api;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
/** /**
@ -84,4 +85,30 @@ public class MultipleSelectCriterion extends CustomFilterCriterion implements Pa
dest.writeStringArray(entryValues); dest.writeStringArray(entryValues);
writeToParcel(dest); 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
+ '}';
}
} }

@ -79,4 +79,32 @@ public class TextInputCriterion extends CustomFilterCriterion implements Parcela
dest.writeString(hint); dest.writeString(hint);
writeToParcel(dest); writeToParcel(dest);
} }
@Override
public String toString() {
return "TextInputCriterion{"
+ "hint='"
+ hint
+ '\''
+ ", prompt='"
+ prompt
+ '\''
+ ", valuesForNewTasks="
+ valuesForNewTasks
+ ", identifier='"
+ identifier
+ '\''
+ ", text='"
+ text
+ '\''
+ ", sql='"
+ sql
+ '\''
+ ", name='"
+ name
+ '\''
+ ", icon="
+ icon
+ '}';
}
} }

@ -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
+ '}';
}
}

@ -7,7 +7,9 @@
package com.todoroo.astrid.core; package com.todoroo.astrid.core;
import static android.text.TextUtils.isEmpty; import static android.text.TextUtils.isEmpty;
import static com.google.common.collect.Lists.transform;
import static com.todoroo.andlib.utility.AndroidUtilities.mapToSerializedString; import static com.todoroo.andlib.utility.AndroidUtilities.mapToSerializedString;
import static java.util.Arrays.asList;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -19,8 +21,13 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.ListView; import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.content.ContextCompat; 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.data.Property.CountProperty;
import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.sql.UnaryCriterion; 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.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -49,6 +57,7 @@ import org.tasks.filters.FilterCriteriaProvider;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.ThemedInjectingAppCompatActivity; import org.tasks.injection.ThemedInjectingAppCompatActivity;
import org.tasks.locale.Locale; import org.tasks.locale.Locale;
import timber.log.Timber;
/** /**
* Activity that allows users to build custom filters * 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_TYPE = 1;
static final int MENU_GROUP_CONTEXT_DELETE = 2; 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; private static final int MENU_GROUP_FILTER = 0;
@Inject Database database; @Inject Database database;
@ -75,26 +84,59 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
private CustomFilterAdapter adapter; private CustomFilterAdapter adapter;
private static String serializeFilters(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++) { for (int i = 0; i < adapter.getCount(); i++) {
CriterionInstance item = adapter.getItem(i); CriterionInstance item = adapter.getItem(i);
// criterion|entry|text|type|sql // criterion|entry|text|type|sql
values String row =
.append(escape(item.criterion.identifier)) Joiner.on(AndroidUtilities.SERIALIZATION_SEPARATOR)
.append(AndroidUtilities.SERIALIZATION_SEPARATOR); .join(
values asList(
.append(escape(item.getValueFromCriterion())) escape(item.criterion.identifier),
.append(AndroidUtilities.SERIALIZATION_SEPARATOR); escape(item.getValueFromCriterion()),
values.append(escape(item.criterion.text)).append(AndroidUtilities.SERIALIZATION_SEPARATOR); escape(item.criterion.text),
values.append(item.type).append(AndroidUtilities.SERIALIZATION_SEPARATOR); item.type,
if (item.criterion.sql != null) { item.criterion.sql == null ? "" : item.criterion.sql));
values.append(item.criterion.sql); Timber.d("%s -> %s", item, row);
} rows.add(row);
values.append('\n'); }
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) { private static String escape(String item) {
@ -105,6 +147,21 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE); 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 @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -121,9 +178,19 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
themeColor.apply(toolbar); themeColor.apply(toolbar);
listView = findViewById(android.R.id.list); listView = findViewById(android.R.id.list);
List<CriterionInstance> startingCriteria = new ArrayList<>(); List<CriterionInstance> criteria =
startingCriteria.add(getStartingUniverse()); new ArrayList<>(
adapter = new CustomFilterAdapter(this, dialogBuilder, startingCriteria, locale); 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); listView.setAdapter(adapter);
updateList(); updateList();
@ -135,22 +202,6 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
component.inject(this); 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() { private void setUpListeners() {
findViewById(R.id.add).setOnClickListener(v -> listView.showContextMenu()); findViewById(R.id.add).setOnClickListener(v -> listView.showContextMenu());
@ -270,11 +321,10 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
case CriterionInstance.TYPE_INTERSECT: case CriterionInstance.TYPE_INTERSECT:
sql.append("AND "); sql.append("AND ");
break; break;
case CriterionInstance.TYPE_UNIVERSE:
} }
// special code for all tasks 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(' '); sql.append(TaskCriteria.activeAndVisible()).append(' ');
} else { } else {
String subSql = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value)); String subSql = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value));
@ -381,65 +431,4 @@ public class CustomFilterActivity extends ThemedInjectingAppCompatActivity
storeObject.setId(filterDao.insertOrUpdate(storeObject)); storeObject.setId(filterDao.insertOrUpdate(storeObject));
return storeObject.getId() >= 0 ? storeObject : null; 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$
}
}
} }

@ -20,7 +20,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.todoroo.astrid.api.MultipleSelectCriterion; import com.todoroo.astrid.api.MultipleSelectCriterion;
import com.todoroo.astrid.api.TextInputCriterion; import com.todoroo.astrid.api.TextInputCriterion;
import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance;
import java.util.List; import java.util.List;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.AlertDialogBuilder; import org.tasks.dialogs.AlertDialogBuilder;

@ -14,6 +14,7 @@ import com.todoroo.astrid.api.CustomFilterCriterion;
import com.todoroo.astrid.api.MultipleSelectCriterion; import com.todoroo.astrid.api.MultipleSelectCriterion;
import com.todoroo.astrid.api.PermaSql; import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.api.TextInputCriterion; import com.todoroo.astrid.api.TextInputCriterion;
import com.todoroo.astrid.core.CustomFilterActivity.CriterionInstance;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.Task.Priority; import com.todoroo.astrid.data.Task.Priority;
@ -35,13 +36,14 @@ import org.tasks.injection.ForApplication;
public class FilterCriteriaProvider { public class FilterCriteriaProvider {
private static final String IDENTIFIER_TITLE = "title"; // $NON-NLS-1$ private static final String IDENTIFIER_UNIVERSE = "active";
private static final String IDENTIFIER_IMPORTANCE = "importance"; // $NON-NLS-1$ private static final String IDENTIFIER_TITLE = "title";
private static final String IDENTIFIER_DUEDATE = "dueDate"; // $NON-NLS-1$ private static final String IDENTIFIER_IMPORTANCE = "importance";
private static final String IDENTIFIER_GTASKS = "gtaskslist"; // $NON-NLS-1$ private static final String IDENTIFIER_DUEDATE = "dueDate";
private static final String IDENTIFIER_CALDAV = "caldavlist"; // $NON-NLS-1$ private static final String IDENTIFIER_GTASKS = "gtaskslist";
private static final String IDENTIFIER_TAG_IS = "tag_is"; // $NON-NLS-1$ private static final String IDENTIFIER_CALDAV = "caldavlist";
private static final String IDENTIFIER_TAG_CONTAINS = "tag_contains"; // $NON-NLS-1$ private static final String IDENTIFIER_TAG_IS = "tag_is";
private static final String IDENTIFIER_TAG_CONTAINS = "tag_contains";
private final Context context; private final Context context;
private final TagDataDao tagDataDao; private final TagDataDao tagDataDao;
@ -63,6 +65,41 @@ public class FilterCriteriaProvider {
this.caldavDao = caldavDao; 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<CustomFilterCriterion> getAll() { public List<CustomFilterCriterion> getAll() {
List<CustomFilterCriterion> result = newArrayList(); List<CustomFilterCriterion> result = newArrayList();

Loading…
Cancel
Save