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