From 6fd1cb3e44733bda6fe4e586c164b4f55922307c Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Tue, 4 Aug 2020 15:08:53 -0500 Subject: [PATCH] Convert more custom filter code to Kotlin --- .../astrid/core/CriterionInstance.java | 201 ------------- .../todoroo/astrid/core/CriterionInstance.kt | 174 +++++++++++ .../activities/FilterSettingsActivity.kt | 63 ++-- .../java/org/tasks/data/CaldavDaoBlocking.kt | 11 - .../tasks/data/GoogleTaskListDaoBlocking.kt | 8 - .../java/org/tasks/data/TagDataDaoBlocking.kt | 11 - .../java/org/tasks/dialogs/NewFilterDialog.kt | 5 +- .../tasks/filters/FilterCriteriaProvider.java | 278 ------------------ .../tasks/filters/FilterCriteriaProvider.kt | 233 +++++++++++++++ 9 files changed, 447 insertions(+), 537 deletions(-) delete mode 100644 app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java create mode 100644 app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt delete mode 100644 app/src/main/java/org/tasks/data/CaldavDaoBlocking.kt delete mode 100644 app/src/main/java/org/tasks/data/TagDataDaoBlocking.kt delete mode 100644 app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java create mode 100644 app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt diff --git a/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java deleted file mode 100644 index 2aa8b5bb9..000000000 --- a/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.todoroo.astrid.core; - -import static com.google.common.collect.Lists.transform; -import static java.util.Arrays.asList; -import static org.tasks.Strings.isNullOrEmpty; - -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.todoroo.andlib.utility.AndroidUtilities; -import com.todoroo.astrid.api.CustomFilterCriterion; -import com.todoroo.astrid.api.MultipleSelectCriterion; -import com.todoroo.astrid.api.TextInputCriterion; -import com.todoroo.astrid.helper.UUIDHelper; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import org.tasks.filters.FilterCriteriaProvider; -import timber.log.Timber; - -public 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; - public CustomFilterCriterion criterion; - public int selectedIndex = -1; - public String selectedText = null; - public int type = TYPE_INTERSECT; - public int end; - public int start; - public int max; - private String id = UUIDHelper.newUUID(); - - public CriterionInstance() {} - - public CriterionInstance(CriterionInstance other) { - id = other.id; - criterion = other.criterion; - selectedIndex = other.selectedIndex; - selectedText = other.selectedText; - type = other.type; - end = other.end; - start = other.start; - max = other.max; - } - - public static List fromString( - FilterCriteriaProvider provider, String criterion) { - if (isNullOrEmpty(criterion)) { - return Collections.emptyList(); - } - List entries = new ArrayList<>(); - for (String row : criterion.split("\n")) { - List split = - transform( - Splitter.on(AndroidUtilities.SERIALIZATION_SEPARATOR).splitToList(row), - CriterionInstance::unescape); - if (split.size() != 4 && split.size() != 5) { - Timber.e("invalid row: %s", row); - return Collections.emptyList(); - } - - CriterionInstance entry = new CriterionInstance(); - entry.criterion = provider.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) { - if (item == null) { - return ""; // $NON-NLS-1$ - } - return item.replace( - AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE); - } - - private static String unescape(String item) { - if (isNullOrEmpty(item)) { - return ""; - } - return item.replace( - AndroidUtilities.SEPARATOR_ESCAPE, AndroidUtilities.SERIALIZATION_SEPARATOR); - } - - public String getId() { - return id; - } - - 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$ - } - - public 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 boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof CriterionInstance)) { - return false; - } - CriterionInstance that = (CriterionInstance) o; - return selectedIndex == that.selectedIndex - && type == that.type - && end == that.end - && start == that.start - && max == that.max - && Objects.equals(criterion, that.criterion) - && Objects.equals(selectedText, that.selectedText) - && Objects.equals(id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(criterion, selectedIndex, selectedText, type, end, start, max, id); - } - - @Override - public String toString() { - return "CriterionInstance{" - + "criterion=" - + criterion - + ", selectedIndex=" - + selectedIndex - + ", selectedText='" - + selectedText - + '\'' - + ", type=" - + type - + ", end=" - + end - + ", start=" - + start - + ", max=" - + max - + '}'; - } - - public static String serialize(List criterion) { - return Joiner.on("\n").join(transform(criterion, CriterionInstance::serialize)); - } - - private String serialize() { - // criterion|entry|text|type|sql - return Joiner.on(AndroidUtilities.SERIALIZATION_SEPARATOR) - .join( - asList( - escape(criterion.identifier), - escape(getValueFromCriterion()), - escape(criterion.text), - type, - criterion.sql == null ? "" : criterion.sql)); - } -} diff --git a/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt new file mode 100644 index 000000000..7c6c34b2e --- /dev/null +++ b/app/src/main/java/com/todoroo/astrid/core/CriterionInstance.kt @@ -0,0 +1,174 @@ +package com.todoroo.astrid.core + +import com.google.common.base.Joiner +import com.google.common.base.Splitter +import com.google.common.collect.Lists +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.astrid.api.CustomFilterCriterion +import com.todoroo.astrid.api.MultipleSelectCriterion +import com.todoroo.astrid.api.TextInputCriterion +import com.todoroo.astrid.helper.UUIDHelper +import org.tasks.Strings.isNullOrEmpty +import org.tasks.filters.FilterCriteriaProvider +import timber.log.Timber +import java.util.* + +class CriterionInstance { + lateinit var criterion: CustomFilterCriterion + var selectedIndex = -1 + var selectedText: String? = null + var type = TYPE_INTERSECT + var end = 0 + var start = 0 + var max = 0 + var id: String = UUIDHelper.newUUID() + private set + + constructor() + + constructor(other: CriterionInstance) { + id = other.id + criterion = other.criterion + selectedIndex = other.selectedIndex + selectedText = other.selectedText + type = other.type + end = other.end + start = other.start + max = other.max + } + + // $NON-NLS-1$ + val titleFromCriterion: String + get() { + if (criterion is MultipleSelectCriterion) { + if (selectedIndex >= 0 && (criterion as MultipleSelectCriterion).entryTitles != null && selectedIndex < (criterion as MultipleSelectCriterion).entryTitles.size) { + val title = (criterion as MultipleSelectCriterion).entryTitles[selectedIndex] + return criterion.text.replace("?", title) + } + return criterion.text + } else if (criterion is TextInputCriterion) { + return if (selectedText == null) { + criterion.text + } else criterion.text.replace("?", selectedText!!) + } + throw UnsupportedOperationException("Unknown criterion type") // $NON-NLS-1$ + } + + // $NON-NLS-1$ + val valueFromCriterion: String? + get() { + if (type == TYPE_UNIVERSE) { + return null + } + if (criterion is MultipleSelectCriterion) { + return if (selectedIndex >= 0 && (criterion as MultipleSelectCriterion).entryValues != null && selectedIndex < (criterion as MultipleSelectCriterion).entryValues.size) { + (criterion as MultipleSelectCriterion).entryValues[selectedIndex] + } else criterion.text + } else if (criterion is TextInputCriterion) { + return selectedText + } + throw UnsupportedOperationException("Unknown criterion type") // $NON-NLS-1$ + } + + private fun serialize(): String { + // criterion|entry|text|type|sql + return Joiner.on(AndroidUtilities.SERIALIZATION_SEPARATOR) + .join( + listOf( + escape(criterion.identifier), + escape(valueFromCriterion), + escape(criterion.text), + type, + if (criterion.sql == null) "" else criterion.sql)) + } + + override fun toString(): String { + return "CriterionInstance(criterion=$criterion, selectedIndex=$selectedIndex, selectedText=$selectedText, type=$type, end=$end, start=$start, max=$max, id='$id')" + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is CriterionInstance) return false + + if (criterion != other.criterion) return false + if (selectedIndex != other.selectedIndex) return false + if (selectedText != other.selectedText) return false + if (type != other.type) return false + if (end != other.end) return false + if (start != other.start) return false + if (max != other.max) return false + if (id != other.id) return false + + return true + } + + override fun hashCode(): Int { + var result = criterion.hashCode() + result = 31 * result + selectedIndex + result = 31 * result + (selectedText?.hashCode() ?: 0) + result = 31 * result + type + result = 31 * result + end + result = 31 * result + start + result = 31 * result + max + result = 31 * result + (id.hashCode() ?: 0) + return result + } + + companion object { + const val TYPE_ADD = 0 + const val TYPE_SUBTRACT = 1 + const val TYPE_INTERSECT = 2 + const val TYPE_UNIVERSE = 3 + + suspend fun fromString( + provider: FilterCriteriaProvider, criterion: String): List { + if (isNullOrEmpty(criterion)) { + return emptyList() + } + val entries: MutableList = ArrayList() + for (row in criterion.split("\n".toRegex()).toTypedArray()) { + val split = Lists.transform( + Splitter.on(AndroidUtilities.SERIALIZATION_SEPARATOR).splitToList(row)) { item: String? -> unescape(item) } + if (split.size != 4 && split.size != 5) { + Timber.e("invalid row: %s", row) + return emptyList() + } + val entry = CriterionInstance() + entry.criterion = provider.getFilterCriteria(split[0]) + val value = split[1] + if (entry.criterion is TextInputCriterion) { + entry.selectedText = value + } else if (entry.criterion is MultipleSelectCriterion) { + val multipleSelectCriterion = entry.criterion as MultipleSelectCriterion? + if (multipleSelectCriterion!!.entryValues != null) { + entry.selectedIndex = listOf(*multipleSelectCriterion.entryValues).indexOf(value) + } + } else { + Timber.d("Ignored value %s for %s", value, entry.criterion) + } + entry.type = split[3].toInt() + entry.criterion.sql = split[4] + Timber.d("%s -> %s", row, entry) + entries.add(entry) + } + return entries + } + + private fun escape(item: String?): String { + return item?.replace( + AndroidUtilities.SERIALIZATION_SEPARATOR, AndroidUtilities.SEPARATOR_ESCAPE) + ?: "" // $NON-NLS-1$ + } + + private fun unescape(item: String?): String { + return if (isNullOrEmpty(item)) { + "" + } else item!!.replace( + AndroidUtilities.SEPARATOR_ESCAPE, AndroidUtilities.SERIALIZATION_SEPARATOR) + } + + fun serialize(criterion: List?): String { + return Joiner.on("\n").join(Lists.transform(criterion!!) { obj: CriterionInstance? -> obj!!.serialize() }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt index 159dc5780..a27c4e091 100644 --- a/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt +++ b/app/src/main/java/org/tasks/activities/FilterSettingsActivity.kt @@ -10,6 +10,7 @@ import android.view.MenuItem import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.FrameLayout +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -33,6 +34,7 @@ import com.todoroo.astrid.core.CustomFilterItemTouchHelper import com.todoroo.astrid.dao.Database import com.todoroo.astrid.data.Task import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import org.tasks.R import org.tasks.Strings import org.tasks.data.Filter @@ -76,39 +78,44 @@ class FilterSettingsActivity : BaseListSettingsActivity() { name.setText(filter!!.listingTitle) } when { - savedInstanceState != null -> { - criteria = CriterionInstance.fromString( - filterCriteriaProvider, savedInstanceState.getString(EXTRA_CRITERIA)) + savedInstanceState != null -> lifecycleScope.launch { + setCriteria(CriterionInstance.fromString( + filterCriteriaProvider, savedInstanceState.getString(EXTRA_CRITERIA)!!)) } - filter != null -> { - criteria = CriterionInstance.fromString(filterCriteriaProvider, filter!!.criterion) + filter != null -> lifecycleScope.launch { + setCriteria(CriterionInstance.fromString( + filterCriteriaProvider, filter!!.criterion)) } - intent.hasExtra(EXTRA_CRITERIA) -> { + intent.hasExtra(EXTRA_CRITERIA) -> lifecycleScope.launch { name.setText(intent.getStringExtra(EXTRA_TITLE)) - criteria = CriterionInstance.fromString( - filterCriteriaProvider, intent.getStringExtra(EXTRA_CRITERIA)) + setCriteria(CriterionInstance.fromString( + filterCriteriaProvider, intent.getStringExtra(EXTRA_CRITERIA)!!)) } else -> { val instance = CriterionInstance() instance.criterion = filterCriteriaProvider.startingUniverse instance.type = CriterionInstance.TYPE_UNIVERSE - criteria = mutableListOf(instance) + setCriteria(mutableListOf(instance)) } } - adapter = CustomFilterAdapter(criteria, locale) { replaceId: String -> onClick(replaceId) } recyclerView.layoutManager = LinearLayoutManager(this) - recyclerView.adapter = adapter ItemTouchHelper( CustomFilterItemTouchHelper(this, this::onMove, this::onDelete, this::updateList)) .attachToRecyclerView(recyclerView) - fab.isExtended = isNew || adapter.itemCount <= 1 if (isNew) { toolbar.inflateMenu(R.menu.menu_help) } - updateList() updateTheme() } + private fun setCriteria(criteria: List) { + this.criteria = criteria.toMutableList() + adapter = CustomFilterAdapter(criteria, locale) { replaceId: String -> onClick(replaceId) } + recyclerView.adapter = adapter + fab.isExtended = isNew || adapter.itemCount <= 1 + updateList() + } + private fun onDelete(index: Int) { criteria.removeAt(index) updateList() @@ -161,19 +168,21 @@ class FilterSettingsActivity : BaseListSettingsActivity() { fun addCriteria() { AndroidUtilities.hideKeyboard(this) fab.shrink() - val all = filterCriteriaProvider.all - val names = all.map(CustomFilterCriterion::getName) - dialogBuilder.newDialog() - .setItems(names) { dialog: DialogInterface, which: Int -> - val instance = CriterionInstance() - instance.criterion = all[which] - showOptionsFor(instance, Runnable { - criteria.add(instance) - updateList() - }) - dialog.dismiss() - } - .show() + lifecycleScope.launch { + val all = filterCriteriaProvider.all() + val names = all.map(CustomFilterCriterion::getName) + dialogBuilder.newDialog() + .setItems(names) { dialog: DialogInterface, which: Int -> + val instance = CriterionInstance() + instance.criterion = all[which] + showOptionsFor(instance, Runnable { + criteria.add(instance) + updateList() + }) + dialog.dismiss() + } + .show() + } } /** Show options menu for the given criterioninstance */ @@ -317,7 +326,7 @@ class FilterSettingsActivity : BaseListSettingsActivity() { if (instance.type == CriterionInstance.TYPE_UNIVERSE || instance.criterion.sql == null) { sql.append(activeAndVisible()).append(' ') } else { - var subSql: String? = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value)) + var subSql: String? = instance.criterion.sql.replace("?", UnaryCriterion.sanitize(value!!)) subSql = PermaSql.replacePlaceholdersForQuery(subSql) sql.append(Task.ID).append(" IN (").append(subSql).append(") ") } diff --git a/app/src/main/java/org/tasks/data/CaldavDaoBlocking.kt b/app/src/main/java/org/tasks/data/CaldavDaoBlocking.kt deleted file mode 100644 index cf990bc8d..000000000 --- a/app/src/main/java/org/tasks/data/CaldavDaoBlocking.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.tasks.data - -import kotlinx.coroutines.runBlocking -import javax.inject.Inject - -@Deprecated("use coroutines") -class CaldavDaoBlocking @Inject constructor(private val dao: CaldavDao) { - fun getCalendars(): List = runBlocking { - dao.getCalendars() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/data/GoogleTaskListDaoBlocking.kt b/app/src/main/java/org/tasks/data/GoogleTaskListDaoBlocking.kt index 661640fee..c2fc826ce 100644 --- a/app/src/main/java/org/tasks/data/GoogleTaskListDaoBlocking.kt +++ b/app/src/main/java/org/tasks/data/GoogleTaskListDaoBlocking.kt @@ -5,10 +5,6 @@ import javax.inject.Inject @Deprecated("use coroutines") class GoogleTaskListDaoBlocking @Inject constructor(private val dao: GoogleTaskListDao) { - fun getAccounts(): List = runBlocking { - dao.getAccounts() - } - fun getById(id: Long): GoogleTaskList? = runBlocking { dao.getById(id) } @@ -25,10 +21,6 @@ class GoogleTaskListDaoBlocking @Inject constructor(private val dao: GoogleTaskL dao.findExistingList(remoteId) } - fun getAllLists(): List = runBlocking { - dao.getAllLists() - } - fun insertOrReplace(googleTaskList: GoogleTaskList): Long = runBlocking { dao.insertOrReplace(googleTaskList) } diff --git a/app/src/main/java/org/tasks/data/TagDataDaoBlocking.kt b/app/src/main/java/org/tasks/data/TagDataDaoBlocking.kt deleted file mode 100644 index 0ff353f3a..000000000 --- a/app/src/main/java/org/tasks/data/TagDataDaoBlocking.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.tasks.data - -import kotlinx.coroutines.runBlocking -import javax.inject.Inject - -@Deprecated("use coroutines") -class TagDataDaoBlocking @Inject constructor(private val dao: TagDataDao) { - fun tagDataOrderedByName(): List = runBlocking { - dao.tagDataOrderedByName() - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/dialogs/NewFilterDialog.kt b/app/src/main/java/org/tasks/dialogs/NewFilterDialog.kt index f46fe5876..69fe636dd 100644 --- a/app/src/main/java/org/tasks/dialogs/NewFilterDialog.kt +++ b/app/src/main/java/org/tasks/dialogs/NewFilterDialog.kt @@ -6,7 +6,10 @@ import android.os.Bundle import androidx.fragment.app.DialogFragment import com.todoroo.astrid.api.CustomFilterCriterion import com.todoroo.astrid.core.CriterionInstance -import com.todoroo.astrid.core.CriterionInstance.* +import com.todoroo.astrid.core.CriterionInstance.Companion.TYPE_INTERSECT +import com.todoroo.astrid.core.CriterionInstance.Companion.TYPE_SUBTRACT +import com.todoroo.astrid.core.CriterionInstance.Companion.TYPE_UNIVERSE +import com.todoroo.astrid.core.CriterionInstance.Companion.serialize import dagger.hilt.android.AndroidEntryPoint import org.tasks.R import org.tasks.activities.FilterSettingsActivity diff --git a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java deleted file mode 100644 index ab6d2f113..000000000 --- a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.java +++ /dev/null @@ -1,278 +0,0 @@ -package org.tasks.filters; - -import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Sets.newLinkedHashSet; - -import android.content.Context; -import android.content.res.Resources; -import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.Field; -import com.todoroo.andlib.sql.Join; -import com.todoroo.andlib.sql.Query; -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.data.Task; -import com.todoroo.astrid.data.Task.Priority; -import dagger.hilt.android.qualifiers.ApplicationContext; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; -import org.tasks.R; -import org.tasks.data.CaldavCalendar; -import org.tasks.data.CaldavDaoBlocking; -import org.tasks.data.CaldavTask; -import org.tasks.data.GoogleTask; -import org.tasks.data.GoogleTaskList; -import org.tasks.data.GoogleTaskListDaoBlocking; -import org.tasks.data.Tag; -import org.tasks.data.TagData; -import org.tasks.data.TagDataDaoBlocking; -import org.tasks.data.TaskDao; - -public class FilterCriteriaProvider { - - 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 TagDataDaoBlocking tagDataDao; - private final Resources r; - private final GoogleTaskListDaoBlocking googleTaskListDao; - private final CaldavDaoBlocking caldavDao; - - @Inject - public FilterCriteriaProvider( - @ApplicationContext Context context, - TagDataDaoBlocking tagDataDao, - GoogleTaskListDaoBlocking googleTaskListDao, - CaldavDaoBlocking caldavDao) { - this.context = context; - this.tagDataDao = tagDataDao; - - r = context.getResources(); - this.googleTaskListDao = googleTaskListDao; - 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.BFE_Active), - null, - null, - null, - null, - null); - } - - public List getAll() { - List result = new ArrayList<>(); - - result.add(getTagFilter()); - result.add(getTagNameContainsFilter()); - result.add(getDueDateFilter()); - result.add(getPriorityFilter()); - result.add(getTaskTitleContainsFilter()); - if (!googleTaskListDao.getAccounts().isEmpty()) { - result.add(getGtasksFilterCriteria()); - } - result.add(getCaldavFilterCriteria()); - return result; - } - - private CustomFilterCriterion getTagFilter() { - // TODO: adding to hash set because duplicate tag name bug hasn't been fixed yet - String[] tagNames = - newLinkedHashSet(transform(tagDataDao.tagDataOrderedByName(), TagData::getName)) - .toArray(new String[0]); - Map values = new HashMap<>(); - values.put(Tag.KEY, "?"); - return new MultipleSelectCriterion( - IDENTIFIER_TAG_IS, - context.getString(R.string.CFC_tag_text), - Query.select(Tag.TASK) - .from(Tag.TABLE) - .join(Join.inner(Task.TABLE, Tag.TASK.eq(Task.ID))) - .where(Criterion.and(TaskDao.TaskCriteria.activeAndVisible(), Tag.NAME.eq("?"))) - .toString(), - values, - tagNames, - tagNames, - context.getString(R.string.CFC_tag_name)); - } - - public CustomFilterCriterion getTagNameContainsFilter() { - return new TextInputCriterion( - IDENTIFIER_TAG_CONTAINS, - context.getString(R.string.CFC_tag_contains_text), - Query.select(Tag.TASK) - .from(Tag.TABLE) - .join(Join.inner(Task.TABLE, Tag.TASK.eq(Task.ID))) - .where(Criterion.and(TaskDao.TaskCriteria.activeAndVisible(), Tag.NAME.like("%?%"))) - .toString(), - context.getString(R.string.CFC_tag_contains_name), - "", - context.getString(R.string.CFC_tag_contains_name)); - } - - public CustomFilterCriterion getDueDateFilter() { - String[] entryValues = - new String[] { - "0", - PermaSql.VALUE_EOD_YESTERDAY, - PermaSql.VALUE_EOD, - PermaSql.VALUE_EOD_TOMORROW, - PermaSql.VALUE_EOD_DAY_AFTER, - PermaSql.VALUE_EOD_NEXT_WEEK, - PermaSql.VALUE_EOD_NEXT_MONTH, - }; - Map values = new HashMap<>(); - values.put(Task.DUE_DATE.name, "?"); - return new MultipleSelectCriterion( - IDENTIFIER_DUEDATE, - r.getString(R.string.CFC_dueBefore_text), - Query.select(Task.ID) - .from(Task.TABLE) - .where( - Criterion.and( - TaskDao.TaskCriteria.activeAndVisible(), - Criterion.or(Field.field("?").eq(0), Task.DUE_DATE.gt(0)), - Task.DUE_DATE.lte("?"))) - .toString(), - values, - r.getStringArray(R.array.CFC_dueBefore_entries), - entryValues, - r.getString(R.string.CFC_dueBefore_name)); - } - - public CustomFilterCriterion getPriorityFilter() { - String[] entryValues = - new String[] { - Integer.toString(Priority.HIGH), - Integer.toString(Priority.MEDIUM), - Integer.toString(Priority.LOW), - Integer.toString(Priority.NONE), - }; - String[] entries = new String[] {"!!!", "!!", "!", "o"}; - Map values = new HashMap<>(); - values.put(Task.IMPORTANCE.name, "?"); - return new MultipleSelectCriterion( - IDENTIFIER_IMPORTANCE, - r.getString(R.string.CFC_importance_text), - Query.select(Task.ID) - .from(Task.TABLE) - .where(Criterion.and(TaskDao.TaskCriteria.activeAndVisible(), Task.IMPORTANCE.lte("?"))) - .toString(), - values, - entries, - entryValues, - r.getString(R.string.CFC_importance_name)); - } - - private CustomFilterCriterion getTaskTitleContainsFilter() { - return new TextInputCriterion( - IDENTIFIER_TITLE, - r.getString(R.string.CFC_title_contains_text), - Query.select(Task.ID) - .from(Task.TABLE) - .where(Criterion.and(TaskDao.TaskCriteria.activeAndVisible(), Task.TITLE.like("%?%"))) - .toString(), - r.getString(R.string.CFC_title_contains_name), - "", - r.getString(R.string.CFC_title_contains_name)); - } - - private CustomFilterCriterion getGtasksFilterCriteria() { - List lists = googleTaskListDao.getAllLists(); - - String[] listNames = new String[lists.size()]; - String[] listIds = new String[lists.size()]; - for (int i = 0; i < lists.size(); i++) { - listNames[i] = lists.get(i).getTitle(); - listIds[i] = lists.get(i).getRemoteId(); - } - - Map values = new HashMap<>(); - values.put(GoogleTask.KEY, "?"); - - return new MultipleSelectCriterion( - IDENTIFIER_GTASKS, - context.getString(R.string.CFC_gtasks_list_text), - Query.select(GoogleTask.TASK) - .from(GoogleTask.TABLE) - .join(Join.inner(Task.TABLE, GoogleTask.TASK.eq(Task.ID))) - .where( - Criterion.and( - TaskDao.TaskCriteria.activeAndVisible(), - GoogleTask.DELETED.eq(0), - GoogleTask.LIST.eq("?"))) - .toString(), - values, - listNames, - listIds, - context.getString(R.string.CFC_gtasks_list_name)); - } - - private CustomFilterCriterion getCaldavFilterCriteria() { - List calendars = caldavDao.getCalendars(); - - String[] names = new String[calendars.size()]; - String[] ids = new String[calendars.size()]; - for (int i = 0; i < calendars.size(); i++) { - names[i] = calendars.get(i).getName(); - ids[i] = calendars.get(i).getUuid(); - } - Map values = new HashMap<>(); - values.put(CaldavTask.KEY, "?"); - - return new MultipleSelectCriterion( - IDENTIFIER_CALDAV, - context.getString(R.string.CFC_gtasks_list_text), - Query.select(CaldavTask.TASK) - .from(CaldavTask.TABLE) - .join(Join.inner(Task.TABLE, CaldavTask.TASK.eq(Task.ID))) - .where( - Criterion.and( - TaskDao.TaskCriteria.activeAndVisible(), - CaldavTask.DELETED.eq(0), - CaldavTask.CALENDAR.eq("?"))) - .toString(), - values, - names, - ids, - context.getString(R.string.CFC_list_name)); - } -} diff --git a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt new file mode 100644 index 000000000..8dd0cece2 --- /dev/null +++ b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt @@ -0,0 +1,233 @@ +package org.tasks.filters + +import android.content.Context +import com.todoroo.andlib.sql.Criterion.Companion.and +import com.todoroo.andlib.sql.Criterion.Companion.or +import com.todoroo.andlib.sql.Field.Companion.field +import com.todoroo.andlib.sql.Join.Companion.inner +import com.todoroo.andlib.sql.Query.Companion.select +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.data.Task +import dagger.hilt.android.qualifiers.ApplicationContext +import org.tasks.R +import org.tasks.data.* +import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible +import java.util.* +import javax.inject.Inject + +class FilterCriteriaProvider @Inject constructor( + @param:ApplicationContext private val context: Context, + private val tagDataDao: TagDataDao, + private val googleTaskListDao: GoogleTaskListDao, + private val caldavDao: CaldavDao) { + private val r = context.resources + + suspend fun getFilterCriteria(identifier: String): CustomFilterCriterion { + return when (identifier) { + IDENTIFIER_UNIVERSE -> startingUniverse + IDENTIFIER_TITLE -> taskTitleContainsFilter + IDENTIFIER_IMPORTANCE -> priorityFilter + IDENTIFIER_DUEDATE -> dueDateFilter + IDENTIFIER_GTASKS -> gtasksFilterCriteria() + IDENTIFIER_CALDAV -> caldavFilterCriteria() + IDENTIFIER_TAG_IS -> tagFilter() + IDENTIFIER_TAG_CONTAINS -> tagNameContainsFilter + else -> throw RuntimeException("Unknown identifier: $identifier") + } + } + + val startingUniverse: CustomFilterCriterion + get() = MultipleSelectCriterion( + IDENTIFIER_UNIVERSE, + context.getString(R.string.BFE_Active), + null, + null, + null, + null, + null) + + suspend fun all(): List { + val result: MutableList = ArrayList() + result.add(tagFilter()) + result.add(tagNameContainsFilter) + result.add(dueDateFilter) + result.add(priorityFilter) + result.add(taskTitleContainsFilter) + if (googleTaskListDao.getAccounts().isNotEmpty()) { + result.add(gtasksFilterCriteria()) + } + result.add(caldavFilterCriteria()) + return result + } + + // TODO: adding to hash set because duplicate tag name bug hasn't been fixed yet + private suspend fun tagFilter(): CustomFilterCriterion { + // TODO: adding to hash set because duplicate tag name bug hasn't been fixed yet + val tagNames = tagDataDao + .tagDataOrderedByName() + .map(TagData::name) + .distinct() + .toTypedArray() + val values: MutableMap = HashMap() + values[Tag.KEY] = "?" + return MultipleSelectCriterion( + IDENTIFIER_TAG_IS, + context.getString(R.string.CFC_tag_text), + select(Tag.TASK) + .from(Tag.TABLE) + .join(inner(Task.TABLE, Tag.TASK.eq(Task.ID))) + .where(and(activeAndVisible(), Tag.NAME.eq("?"))) + .toString(), + values, + tagNames, + tagNames, + context.getString(R.string.CFC_tag_name)) + } + + val tagNameContainsFilter: CustomFilterCriterion + get() = TextInputCriterion( + IDENTIFIER_TAG_CONTAINS, + context.getString(R.string.CFC_tag_contains_text), + select(Tag.TASK) + .from(Tag.TABLE) + .join(inner(Task.TABLE, Tag.TASK.eq(Task.ID))) + .where(and(activeAndVisible(), Tag.NAME.like("%?%"))) + .toString(), + context.getString(R.string.CFC_tag_contains_name), + "", + context.getString(R.string.CFC_tag_contains_name)) + + val dueDateFilter: CustomFilterCriterion + get() { + val entryValues = arrayOf( + "0", + PermaSql.VALUE_EOD_YESTERDAY, + PermaSql.VALUE_EOD, + PermaSql.VALUE_EOD_TOMORROW, + PermaSql.VALUE_EOD_DAY_AFTER, + PermaSql.VALUE_EOD_NEXT_WEEK, + PermaSql.VALUE_EOD_NEXT_MONTH) + val values: MutableMap = HashMap() + values[Task.DUE_DATE.name] = "?" + return MultipleSelectCriterion( + IDENTIFIER_DUEDATE, + r.getString(R.string.CFC_dueBefore_text), + select(Task.ID) + .from(Task.TABLE) + .where( + and( + activeAndVisible(), + or(field("?").eq(0), Task.DUE_DATE.gt(0)), + Task.DUE_DATE.lte("?"))) + .toString(), + values, + r.getStringArray(R.array.CFC_dueBefore_entries), + entryValues, + r.getString(R.string.CFC_dueBefore_name)) + } + + val priorityFilter: CustomFilterCriterion + get() { + val entryValues = arrayOf( + Task.Priority.HIGH.toString(), + Task.Priority.MEDIUM.toString(), + Task.Priority.LOW.toString(), + Task.Priority.NONE.toString()) + val entries = arrayOf("!!!", "!!", "!", "o") + val values: MutableMap = HashMap() + values[Task.IMPORTANCE.name] = "?" + return MultipleSelectCriterion( + IDENTIFIER_IMPORTANCE, + r.getString(R.string.CFC_importance_text), + select(Task.ID) + .from(Task.TABLE) + .where(and(activeAndVisible(), Task.IMPORTANCE.lte("?"))) + .toString(), + values, + entries, + entryValues, + r.getString(R.string.CFC_importance_name)) + } + + private val taskTitleContainsFilter: CustomFilterCriterion + get() = TextInputCriterion( + IDENTIFIER_TITLE, + r.getString(R.string.CFC_title_contains_text), + select(Task.ID) + .from(Task.TABLE) + .where(and(activeAndVisible(), Task.TITLE.like("%?%"))) + .toString(), + r.getString(R.string.CFC_title_contains_name), + "", + r.getString(R.string.CFC_title_contains_name)) + + private suspend fun gtasksFilterCriteria(): CustomFilterCriterion { + val lists = googleTaskListDao.getAllLists() + val listNames = arrayOfNulls(lists.size) + val listIds = arrayOfNulls(lists.size) + for (i in lists.indices) { + listNames[i] = lists[i].title + listIds[i] = lists[i].remoteId + } + val values: MutableMap = HashMap() + values[GoogleTask.KEY] = "?" + return MultipleSelectCriterion( + IDENTIFIER_GTASKS, + context.getString(R.string.CFC_gtasks_list_text), + select(GoogleTask.TASK) + .from(GoogleTask.TABLE) + .join(inner(Task.TABLE, GoogleTask.TASK.eq(Task.ID))) + .where( + and( + activeAndVisible(), + GoogleTask.DELETED.eq(0), + GoogleTask.LIST.eq("?"))) + .toString(), + values, + listNames, + listIds, + context.getString(R.string.CFC_gtasks_list_name)) + } + + private suspend fun caldavFilterCriteria(): CustomFilterCriterion { + val calendars = caldavDao.getCalendars() + val names = arrayOfNulls(calendars.size) + val ids = arrayOfNulls(calendars.size) + for (i in calendars.indices) { + names[i] = calendars[i].name + ids[i] = calendars[i].uuid + } + val values: MutableMap = HashMap() + values[CaldavTask.KEY] = "?" + return MultipleSelectCriterion( + IDENTIFIER_CALDAV, + context.getString(R.string.CFC_gtasks_list_text), + select(CaldavTask.TASK) + .from(CaldavTask.TABLE) + .join(inner(Task.TABLE, CaldavTask.TASK.eq(Task.ID))) + .where( + and( + activeAndVisible(), + CaldavTask.DELETED.eq(0), + CaldavTask.CALENDAR.eq("?"))) + .toString(), + values, + names, + ids, + context.getString(R.string.CFC_list_name)) + } + + companion object { + private const val IDENTIFIER_UNIVERSE = "active" + private const val IDENTIFIER_TITLE = "title" + private const val IDENTIFIER_IMPORTANCE = "importance" + private const val IDENTIFIER_DUEDATE = "dueDate" + private const val IDENTIFIER_GTASKS = "gtaskslist" + private const val IDENTIFIER_CALDAV = "caldavlist" + private const val IDENTIFIER_TAG_IS = "tag_is" + private const val IDENTIFIER_TAG_CONTAINS = "tag_contains" + } +} \ No newline at end of file