diff --git a/app/src/main/java/com/todoroo/andlib/sql/Operator.kt b/app/src/main/java/com/todoroo/andlib/sql/Operator.kt index bbab3d492..5f38ddd45 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/Operator.kt +++ b/app/src/main/java/com/todoroo/andlib/sql/Operator.kt @@ -6,6 +6,7 @@ class Operator private constructor(private val operator: String) { companion object { val eq = Operator("=") val isNull = Operator("IS NULL") + val isNotNull = Operator("IS NOT NULL") val and = Operator("AND") val or = Operator("OR") val not = Operator("NOT") diff --git a/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.kt b/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.kt index b60b3f0c0..636e3e460 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.kt +++ b/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.kt @@ -25,6 +25,12 @@ open class UnaryCriterion private constructor(private val expression: Field, ope fun lte(field: Field, value: Any?): Criterion = UnaryCriterion(field, Operator.lte, value) + fun isNotNull(field: Field): Criterion { + return object : UnaryCriterion(field, Operator.isNotNull, null) { + override fun populateOperator() = " $operator" + } + } + fun isNull(field: Field): Criterion { return object : UnaryCriterion(field, Operator.isNull, null) { override fun populateOperator() = " $operator" diff --git a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt index 8ee589c1c..61ca4acc4 100644 --- a/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt +++ b/app/src/main/java/org/tasks/filters/FilterCriteriaProvider.kt @@ -7,6 +7,7 @@ import com.todoroo.andlib.sql.Field.Companion.field import com.todoroo.andlib.sql.Join.Companion.inner import com.todoroo.andlib.sql.Join.Companion.left import com.todoroo.andlib.sql.Query.Companion.select +import com.todoroo.andlib.sql.UnaryCriterion.Companion.isNotNull import com.todoroo.astrid.api.* import com.todoroo.astrid.data.Task import dagger.hilt.android.qualifiers.ApplicationContext @@ -36,6 +37,7 @@ class FilterCriteriaProvider @Inject constructor( IDENTIFIER_RECUR -> recurringFilter IDENTIFIER_COMPLETED -> completedFilter IDENTIFIER_HIDDEN -> hiddenFilter + IDENTIFIER_PARENT -> parentFilter IDENTIFIER_SUBTASK -> subtaskFilter else -> throw RuntimeException("Unknown identifier: $identifier") } @@ -65,6 +67,7 @@ class FilterCriteriaProvider @Inject constructor( result.add(recurringFilter) result.add(completedFilter) result.add(hiddenFilter) + result.add(parentFilter) result.add(subtaskFilter) return result } @@ -123,6 +126,21 @@ class FilterCriteriaProvider @Inject constructor( .toString() ) + private val parentFilter: CustomFilterCriterion + get() = BooleanCriterion( + IDENTIFIER_PARENT, + context.getString(R.string.custom_filter_has_subtask), + select(Task.ID) + .from(Task.TABLE) + .join(left(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent")))) + .join(left(GoogleTask.TABLE, GoogleTask.PARENT.eq(Task.ID))) + .where(or( + isNotNull(field("children._id")), + isNotNull(GoogleTask.ID) + )) + .toString() + ) + private val subtaskFilter: CustomFilterCriterion get() = BooleanCriterion( IDENTIFIER_SUBTASK, @@ -282,6 +300,7 @@ class FilterCriteriaProvider @Inject constructor( 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" } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9c1beac76..2fa8493ed 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -656,5 +656,6 @@ File %1$s contained %2$s.\n\n Reschedule Multiple Delete this comment? + Has subtasks Is subtask