mirror of https://github.com/tasks/tasks
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
418 lines
17 KiB
Kotlin
418 lines
17 KiB
Kotlin
package org.tasks.filters
|
|
|
|
import android.content.Context
|
|
import com.todoroo.andlib.sql.Criterion.Companion.and
|
|
import com.todoroo.andlib.sql.Criterion.Companion.exists
|
|
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.andlib.sql.UnaryCriterion.Companion.isNotNull
|
|
import com.todoroo.andlib.utility.AndroidUtilities
|
|
import com.todoroo.astrid.api.BooleanCriterion
|
|
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.CriterionInstance
|
|
import com.todoroo.astrid.data.Task
|
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
import org.tasks.R
|
|
import org.tasks.Strings
|
|
import org.tasks.activities.FilterSettingsActivity.Companion.sql
|
|
import org.tasks.data.Alarm
|
|
import org.tasks.data.CaldavDao
|
|
import org.tasks.data.CaldavTask
|
|
import org.tasks.data.Filter
|
|
import org.tasks.data.GoogleTask
|
|
import org.tasks.data.GoogleTaskListDao
|
|
import org.tasks.data.Tag
|
|
import org.tasks.data.TagData
|
|
import org.tasks.data.TagDataDao
|
|
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
|
|
import timber.log.Timber
|
|
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 rebuildFilter(filter: Filter) {
|
|
val serialized = filter.criterion?.takeIf { it.isNotBlank() }
|
|
val criterion = fromString(serialized)
|
|
filter.setSql(criterion.sql)
|
|
filter.criterion = CriterionInstance.serialize(criterion)
|
|
}
|
|
|
|
suspend fun fromString(criterion: String?): List<CriterionInstance> {
|
|
if (criterion.isNullOrBlank()) {
|
|
return emptyList()
|
|
}
|
|
val entries: MutableList<CriterionInstance> = java.util.ArrayList()
|
|
for (row in criterion.trim().split("\n")) {
|
|
val split = row
|
|
.split(AndroidUtilities.SERIALIZATION_SEPARATOR)
|
|
.map { unescape(it) }
|
|
if (split.size != 4 && split.size != 5) {
|
|
Timber.e("invalid row: %s", row)
|
|
return emptyList()
|
|
}
|
|
val entry = CriterionInstance()
|
|
entry.criterion = 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 = multipleSelectCriterion.entryValues.indexOf(value)
|
|
}
|
|
} else {
|
|
Timber.d("Ignored value %s for %s", value, entry.criterion)
|
|
}
|
|
entry.type = split[3].toInt()
|
|
Timber.d("%s -> %s", row, entry)
|
|
entries.add(entry)
|
|
}
|
|
return entries
|
|
}
|
|
|
|
private suspend fun getFilterCriteria(identifier: String): CustomFilterCriterion = when (identifier) {
|
|
IDENTIFIER_UNIVERSE -> startingUniverse
|
|
IDENTIFIER_TITLE -> taskTitleContainsFilter
|
|
IDENTIFIER_IMPORTANCE -> priorityFilter
|
|
IDENTIFIER_STARTDATE -> startDateFilter
|
|
IDENTIFIER_DUEDATE -> dueDateFilter
|
|
IDENTIFIER_GTASKS -> gtasksFilterCriteria()
|
|
IDENTIFIER_CALDAV -> caldavFilterCriteria()
|
|
IDENTIFIER_TAG_IS -> tagFilter()
|
|
IDENTIFIER_TAG_CONTAINS -> tagNameContainsFilter
|
|
IDENTIFIER_RECUR -> recurringFilter
|
|
IDENTIFIER_COMPLETED -> completedFilter
|
|
IDENTIFIER_HIDDEN -> hiddenFilter
|
|
IDENTIFIER_PARENT -> parentFilter
|
|
IDENTIFIER_SUBTASK -> subtaskFilter
|
|
IDENTIFIER_REMINDERS -> reminderFilter
|
|
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<CustomFilterCriterion> {
|
|
val result: MutableList<CustomFilterCriterion> = ArrayList()
|
|
with(result) {
|
|
add(tagFilter())
|
|
add(tagNameContainsFilter)
|
|
add(startDateFilter)
|
|
add(dueDateFilter)
|
|
add(priorityFilter)
|
|
add(taskTitleContainsFilter)
|
|
if (googleTaskListDao.getAccounts().isNotEmpty()) {
|
|
add(gtasksFilterCriteria())
|
|
}
|
|
add(caldavFilterCriteria())
|
|
add(recurringFilter)
|
|
add(completedFilter)
|
|
add(hiddenFilter)
|
|
add(parentFilter)
|
|
add(subtaskFilter)
|
|
add(reminderFilter)
|
|
}
|
|
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<String, Any> = 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))
|
|
}
|
|
|
|
private val recurringFilter: CustomFilterCriterion
|
|
get() = BooleanCriterion(
|
|
IDENTIFIER_RECUR,
|
|
context.getString(R.string.repeats_single, "").trim(),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.where(field("LENGTH(${Task.RECURRENCE})>0").eq(1))
|
|
.toString()
|
|
)
|
|
|
|
private val completedFilter: CustomFilterCriterion
|
|
get() = BooleanCriterion(
|
|
IDENTIFIER_COMPLETED,
|
|
context.getString(R.string.rmd_NoA_done),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.where(field("${Task.COMPLETION_DATE.lt(1)}").eq(0))
|
|
.toString()
|
|
)
|
|
|
|
private val hiddenFilter: CustomFilterCriterion
|
|
get() = BooleanCriterion(
|
|
IDENTIFIER_HIDDEN,
|
|
context.getString(R.string.filter_criteria_unstarted),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.where(field("${Task.HIDE_UNTIL.gt(PermaSql.VALUE_NOW)}").eq(1))
|
|
.toString()
|
|
)
|
|
|
|
private val parentFilter: CustomFilterCriterion
|
|
get() = BooleanCriterion(
|
|
IDENTIFIER_PARENT,
|
|
context.getString(R.string.custom_filter_has_subtask),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.join(inner(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent"))))
|
|
.where(isNotNull(field("children._id")))
|
|
.toString()
|
|
)
|
|
|
|
private val subtaskFilter: CustomFilterCriterion
|
|
get() = BooleanCriterion(
|
|
IDENTIFIER_SUBTASK,
|
|
context.getString(R.string.custom_filter_is_subtask),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.where(field("${Task.PARENT}>0").eq(1))
|
|
.toString()
|
|
)
|
|
|
|
private val reminderFilter: CustomFilterCriterion
|
|
get() {
|
|
return BooleanCriterion(
|
|
IDENTIFIER_REMINDERS,
|
|
context.getString(R.string.custom_filter_has_reminder),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.where(exists(select(ONE).from(Alarm.TABLE).where(Alarm.TASK.eq(Task.ID))))
|
|
.toString()
|
|
)
|
|
}
|
|
|
|
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,
|
|
PermaSql.VALUE_NOW)
|
|
val values: MutableMap<String?, Any> = 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)),
|
|
// find tasks that have due dates before the specified
|
|
// date, or all-day tasks that have due dates before
|
|
// EOD today if the specified date is NOW
|
|
or(Task.DUE_DATE.lte("?"),
|
|
and(field("${Task.DUE_DATE} / 1000 % 60").eq(0),
|
|
field("?").eq(field(PermaSql.VALUE_NOW)),
|
|
Task.DUE_DATE.lte(PermaSql.VALUE_EOD)))))
|
|
.toString(),
|
|
values,
|
|
r.getStringArray(R.array.CFC_dueBefore_entries),
|
|
entryValues,
|
|
r.getString(R.string.CFC_dueBefore_name))
|
|
}
|
|
|
|
val startDateFilter: 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,
|
|
PermaSql.VALUE_NOW)
|
|
val values: MutableMap<String?, Any> = HashMap()
|
|
values[Task.HIDE_UNTIL.name] = "?"
|
|
return MultipleSelectCriterion(
|
|
IDENTIFIER_STARTDATE,
|
|
r.getString(R.string.CFC_startBefore_text),
|
|
select(Task.ID)
|
|
.from(Task.TABLE)
|
|
.where(
|
|
and(
|
|
activeAndVisible(),
|
|
or(field("?").eq(0), Task.HIDE_UNTIL.gt(0)),
|
|
Task.HIDE_UNTIL.lte("?")))
|
|
.toString(),
|
|
values,
|
|
r.getStringArray(R.array.CFC_startBefore_entries),
|
|
entryValues,
|
|
r.getString(R.string.CFC_startBefore_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<String?, Any> = 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 = caldavDao.getGoogleTaskLists()
|
|
val listNames = arrayOfNulls<String>(lists.size)
|
|
val listIds = arrayOfNulls<String>(lists.size)
|
|
for (i in lists.indices) {
|
|
listNames[i] = lists[i].name
|
|
listIds[i] = lists[i].uuid
|
|
}
|
|
val values: MutableMap<String, Any> = HashMap()
|
|
values[GoogleTask.KEY] = "?"
|
|
return MultipleSelectCriterion(
|
|
IDENTIFIER_GTASKS,
|
|
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,
|
|
listNames,
|
|
listIds,
|
|
context.getString(R.string.CFC_gtasks_list_name))
|
|
}
|
|
|
|
private suspend fun caldavFilterCriteria(): CustomFilterCriterion {
|
|
val calendars = caldavDao.getCalendars()
|
|
val names = arrayOfNulls<String>(calendars.size)
|
|
val ids = arrayOfNulls<String>(calendars.size)
|
|
for (i in calendars.indices) {
|
|
names[i] = calendars[i].name
|
|
ids[i] = calendars[i].uuid
|
|
}
|
|
val values: MutableMap<String, Any> = 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_STARTDATE = "startDate"
|
|
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"
|
|
private const val IDENTIFIER_RECUR = "recur"
|
|
private const val IDENTIFIER_COMPLETED = "completed"
|
|
private const val IDENTIFIER_HIDDEN = "hidden"
|
|
private const val IDENTIFIER_PARENT = "parent"
|
|
private const val IDENTIFIER_SUBTASK = "subtask"
|
|
private const val IDENTIFIER_REMINDERS = "reminders"
|
|
|
|
private val ONE = field("1")
|
|
|
|
private fun unescape(item: String?): String {
|
|
return if (Strings.isNullOrEmpty(item)) {
|
|
""
|
|
} else item!!.replace(
|
|
AndroidUtilities.SEPARATOR_ESCAPE, AndroidUtilities.SERIALIZATION_SEPARATOR)
|
|
}
|
|
}
|
|
} |