diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index b420d1e3e..000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index ba4c49124..703e5d4b8 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,48 +1,11 @@ - - - - - - + + + + - - - - - + diff --git a/CHANGELOG.md b/CHANGELOG.md index e0e2c3f21..4fa287372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ Change Log --- +### 7.3.2 (2019-12-12) + +* Fix slow query for subtasks +* Fix duplicated multi-level subtask count +* Fix setting icon on new CalDAV list +* Fix clear completed for subtasks +* Fix crash when clearing 1000+ tasks + ### 7.3.1 (2019-12-05) * Fix crash on missing filter diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 10470d559..ee85ef122 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,8 +36,8 @@ android { defaultConfig { testApplicationId = "org.tasks.test" applicationId = "org.tasks" - versionCode = 632 - versionName = "7.3.1" + versionCode = 633 + versionName = "7.3.2" targetSdkVersion(Versions.targetSdk) minSdkVersion(Versions.minSdk) multiDexEnabled = true diff --git a/app/src/androidTest/java/org/tasks/TestUtilities.java b/app/src/androidTest/java/org/tasks/TestUtilities.java index bcae02589..f978b2d93 100644 --- a/app/src/androidTest/java/org/tasks/TestUtilities.java +++ b/app/src/androidTest/java/org/tasks/TestUtilities.java @@ -59,7 +59,7 @@ public class TestUtilities { private static at.bitfire.ical4android.Task fromString(String task) { try { - return at.bitfire.ical4android.Task.Companion.fromReader(new StringReader(task)).get(0); + return at.bitfire.ical4android.Task.Companion.tasksFromReader(new StringReader(task)).get(0); } catch (Exception e) { throw new IllegalArgumentException(e); } diff --git a/app/src/androidTest/java/org/tasks/db/QueryUtilsTest.java b/app/src/androidTest/java/org/tasks/db/QueryUtilsTest.java new file mode 100644 index 000000000..f972ad0a1 --- /dev/null +++ b/app/src/androidTest/java/org/tasks/db/QueryUtilsTest.java @@ -0,0 +1,42 @@ +package org.tasks.db; + +import static org.junit.Assert.assertEquals; +import static org.tasks.db.QueryUtils.showCompleted; +import static org.tasks.db.QueryUtils.showHidden; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.todoroo.andlib.sql.Functions; +import com.todoroo.astrid.data.Task; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class QueryUtilsTest { + @Test + public void replaceHiddenLT() { + assertEquals( + "(1)", + showHidden(Task.HIDE_UNTIL.lt(Functions.now()).toString())); + } + + @Test + public void replaceHiddenLTE() { + assertEquals( + "(1)", + showHidden(Task.HIDE_UNTIL.lte(Functions.now()).toString())); + } + + @Test + public void replaceUncompletedEQ() { + assertEquals( + "(1)", + showCompleted(Task.COMPLETION_DATE.eq(0).toString())); + } + + @Test + public void replaceUncompletedLTE() { + assertEquals( + "(1)", + showCompleted(Task.COMPLETION_DATE.lte(0).toString())); + } +} diff --git a/app/src/main/java/com/todoroo/andlib/sql/Criterion.java b/app/src/main/java/com/todoroo/andlib/sql/Criterion.java index 5a3ef4a91..9fef13186 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/Criterion.java +++ b/app/src/main/java/com/todoroo/andlib/sql/Criterion.java @@ -15,13 +15,6 @@ import static com.todoroo.andlib.sql.SqlConstants.SPACE; public abstract class Criterion { - public static final Criterion all = - new Criterion(Operator.exists) { - @Override - protected void populate(StringBuilder sb) { - sb.append(1); - } - }; final Operator operator; public Criterion(Operator operator) { diff --git a/app/src/main/java/com/todoroo/andlib/sql/Field.java b/app/src/main/java/com/todoroo/andlib/sql/Field.java index 511e8e63d..c35b4998f 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/Field.java +++ b/app/src/main/java/com/todoroo/andlib/sql/Field.java @@ -39,6 +39,10 @@ public class Field extends DBObject { return UnaryCriterion.gt(this, value); } + public Criterion gte(Object value) { + return UnaryCriterion.gte(this, value); + } + public Criterion lt(final Object value) { return UnaryCriterion.lt(this, value); } diff --git a/app/src/main/java/com/todoroo/andlib/sql/Operator.java b/app/src/main/java/com/todoroo/andlib/sql/Operator.java index 4c6deae3b..ff312e12d 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/Operator.java +++ b/app/src/main/java/com/todoroo/andlib/sql/Operator.java @@ -19,6 +19,7 @@ public final class Operator { static final Operator neq = new Operator("<>"); static final Operator isNotNull = new Operator("IS NOT NULL"); static final Operator gt = new Operator(">"); + static final Operator gte = new Operator(">="); static final Operator lt = new Operator("<"); static final Operator lte = new Operator("<="); private final String operator; diff --git a/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.java b/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.java index c87b32f5d..118e75013 100644 --- a/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.java +++ b/app/src/main/java/com/todoroo/andlib/sql/UnaryCriterion.java @@ -36,6 +36,10 @@ public class UnaryCriterion extends Criterion { return new UnaryCriterion(field, Operator.gt, value); } + static Criterion gte(Field field, Object value) { + return new UnaryCriterion(field, Operator.gte, value); + } + static Criterion lt(Field field, Object value) { return new UnaryCriterion(field, Operator.lt, value); } diff --git a/app/src/main/java/com/todoroo/astrid/api/TagFilter.java b/app/src/main/java/com/todoroo/astrid/api/TagFilter.java index 722c759dd..e74e9e316 100644 --- a/app/src/main/java/com/todoroo/astrid/api/TagFilter.java +++ b/app/src/main/java/com/todoroo/astrid/api/TagFilter.java @@ -4,7 +4,6 @@ import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; import com.todoroo.andlib.sql.Criterion; -import com.todoroo.andlib.sql.Field; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.astrid.dao.TaskDao; @@ -51,10 +50,8 @@ public class TagFilter extends Filter { private static QueryTemplate queryTemplate(String uuid) { return new QueryTemplate() - .join(Join.inner(Tag.TABLE.as("mtags"), Task.UUID.eq(Field.field("mtags.task_uid")))) - .where( - Criterion.and( - Field.field("mtags.tag_uid").eq(uuid), TaskDao.TaskCriteria.activeAndVisible())); + .join(Join.inner(Tag.TABLE, Task.ID.eq(Tag.TASK))) + .where(Criterion.and(Tag.TAG_UID.eq(uuid), TaskDao.TaskCriteria.activeAndVisible())); } private static Map getValuesForNewTask(TagData tagData) { diff --git a/app/src/main/java/com/todoroo/astrid/core/BuiltInFilterExposer.java b/app/src/main/java/com/todoroo/astrid/core/BuiltInFilterExposer.java index d949dd005..d1b035e86 100644 --- a/app/src/main/java/com/todoroo/astrid/core/BuiltInFilterExposer.java +++ b/app/src/main/java/com/todoroo/astrid/core/BuiltInFilterExposer.java @@ -80,8 +80,7 @@ public final class BuiltInFilterExposer { Criterion.and( Criterion.not( Task.UUID.in(Query.select(Field.field("task_uid")).from(Tag.TABLE))), - TaskCriteria.isActive(), - TaskCriteria.isVisible()))); + TaskCriteria.activeAndVisible()))); } public static boolean isInbox(Context context, Filter filter) { diff --git a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java index 5afe3c72f..35c35ddec 100644 --- a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java +++ b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java @@ -6,7 +6,9 @@ package com.todoroo.astrid.core; -import static com.todoroo.astrid.dao.TaskDao.TaskCriteria.isVisible; +import static org.tasks.db.QueryUtils.showCompleted; +import static org.tasks.db.QueryUtils.showHidden; +import static org.tasks.db.QueryUtils.showRecentlyCompleted; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Functions; @@ -57,19 +59,12 @@ public class SortHelper { // flags if (preferences.getBoolean(R.string.p_show_completed_tasks, false)) { - adjustedSql = - adjustedSql.replace(Task.COMPLETION_DATE.eq(0).toString(), Criterion.all.toString()); + adjustedSql = showCompleted(adjustedSql); } else if (preferences.getBoolean(R.string.p_temporarily_show_completed_tasks, false)) { - adjustedSql = - adjustedSql.replace( - Task.COMPLETION_DATE.eq(0).toString(), - Criterion.or( - Task.COMPLETION_DATE.lte(0), - Task.COMPLETION_DATE.gt(DateUtilities.now() - 60000)) - .toString()); + adjustedSql = showRecentlyCompleted(adjustedSql); } if (preferences.getBoolean(R.string.p_show_hidden_tasks, false)) { - adjustedSql = adjustedSql.replace(isVisible().toString(), Criterion.all.toString()); + adjustedSql = showHidden(adjustedSql); } return adjustedSql; diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java index 5fba30da7..3b6ca46b2 100644 --- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java +++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java @@ -136,9 +136,11 @@ public abstract class TaskDao { public abstract int clearCompletedCalendarEvents(); @Transaction - public List fetchTasks(Function> getQueries) { + public List fetchTasks(QueryCallback callback) { long start = BuildConfig.DEBUG ? now() : 0; - List queries = getQueries.apply(atLeastLollipop() && hasSubtasks()); + boolean includeGoogleSubtasks = atLeastLollipop() && hasGoogleTaskSubtasks(); + boolean includeCaldavSubtasks = atLeastLollipop() && hasCaldavSubtasks(); + List queries = callback.getQueries(includeGoogleSubtasks, includeCaldavSubtasks); SupportSQLiteDatabase db = database.getOpenHelper().getWritableDatabase(); int last = queries.size() - 1; for (int i = 0 ; i < last ; i++) { @@ -155,12 +157,11 @@ public abstract class TaskDao { @RawQuery abstract int count(SimpleSQLiteQuery query); - @Query( - "SELECT EXISTS(" - + "SELECT 1 FROM google_tasks WHERE gt_parent > 0 AND gt_deleted = 0" - + " UNION ALL " - + "SELECT 1 FROM caldav_tasks WHERE cd_parent > 0 AND cd_deleted = 0);") - public abstract boolean hasSubtasks(); + @Query("SELECT EXISTS(SELECT 1 FROM caldav_tasks WHERE cd_parent > 0 AND cd_deleted = 0)") + abstract boolean hasCaldavSubtasks(); + + @Query("SELECT EXISTS(SELECT 1 FROM google_tasks WHERE gt_parent > 0 AND gt_deleted = 0)") + abstract boolean hasGoogleTaskSubtasks(); @Query("UPDATE tasks SET modified = datetime('now', 'localtime') WHERE _id in (:ids)") public abstract void touch(List ids); @@ -258,31 +259,16 @@ public abstract class TaskDao { /** Generates SQL clauses */ public static class TaskCriteria { - /** @return tasks that were not deleted */ - public static Criterion notDeleted() { - return Task.DELETION_DATE.eq(0); - } - - public static Criterion notCompleted() { - return Task.COMPLETION_DATE.eq(0); - } - /** @return tasks that have not yet been completed or deleted */ public static Criterion activeAndVisible() { return Criterion.and( - Task.COMPLETION_DATE.eq(0), - Task.DELETION_DATE.eq(0), + Task.COMPLETION_DATE.lte(0), + Task.DELETION_DATE.lte(0), Task.HIDE_UNTIL.lt(Functions.now())); } + } - /** @return tasks that have not yet been completed or deleted */ - public static Criterion isActive() { - return Criterion.and(Task.COMPLETION_DATE.eq(0), Task.DELETION_DATE.eq(0)); - } - - /** @return tasks that are not hidden at current time */ - public static Criterion isVisible() { - return Task.HIDE_UNTIL.lt(Functions.now()); - } + public interface QueryCallback { + List getQueries(boolean includeGoogleTaskSubtasks, boolean includeCaldavSubtasks); } } diff --git a/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.java b/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.java index 708910118..ae7a6c2cd 100644 --- a/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.java +++ b/app/src/main/java/com/todoroo/astrid/service/TaskDeleter.java @@ -1,9 +1,7 @@ package com.todoroo.astrid.service; -import static com.todoroo.andlib.sql.Criterion.all; -import static com.todoroo.astrid.dao.TaskDao.TaskCriteria.isVisible; -import static com.todoroo.astrid.dao.TaskDao.TaskCriteria.notCompleted; -import static org.tasks.db.DbUtils.batch; +import static org.tasks.db.DbUtils.collect; +import static org.tasks.db.QueryUtils.showHiddenAndCompleted; import com.google.common.collect.ImmutableList; import com.todoroo.astrid.api.Filter; @@ -67,13 +65,13 @@ public class TaskDeleter { public List markDeleted(List taskIds) { Set ids = new HashSet<>(taskIds); - batch(taskIds, i -> ids.addAll(googleTaskDao.getChildren(i))); - batch(taskIds, i -> ids.addAll(caldavDao.getChildren(i))); + ids.addAll(collect(taskIds, googleTaskDao::getChildren)); + ids.addAll(collect(taskIds, caldavDao::getChildren)); deletionDao.markDeleted(ids); - workManager.cleanup(taskIds); + workManager.cleanup(ids); workManager.sync(false); localBroadcastManager.broadcastRefresh(); - return taskDao.fetch(taskIds); + return collect(ids, taskDao::fetch); } public void delete(Task task) { @@ -89,13 +87,12 @@ public class TaskDeleter { public int clearCompleted(Filter filter) { List completed = new ArrayList<>(); Filter deleteFilter = new Filter(null, null); - deleteFilter.setFilterQueryOverride( - filter - .getOriginalSqlQuery() - .replace(isVisible().toString(), all.toString()) - .replace(notCompleted().toString(), all.toString())); - List tasks = taskDao.fetchTasks( - hasSubtasks -> TaskListViewModel.getQuery(preferences, deleteFilter, hasSubtasks)); + deleteFilter.setFilterQueryOverride(showHiddenAndCompleted(filter.getOriginalSqlQuery())); + List tasks = + taskDao.fetchTasks( + (includeGoogleSubtasks, includeCaldavSubtasks) -> + TaskListViewModel.getQuery( + preferences, deleteFilter, includeGoogleSubtasks, includeCaldavSubtasks)); for (TaskContainer task : tasks) { if (task.isCompleted()) { completed.add(task.getId()); diff --git a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java index 390d16cbc..458aee468 100644 --- a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java +++ b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksFilterUpdater.java @@ -1,5 +1,7 @@ package com.todoroo.astrid.subtasks; +import static org.tasks.db.QueryUtils.showHiddenAndCompleted; + import android.text.TextUtils; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.dao.TaskDao; @@ -133,10 +135,7 @@ public class SubtasksFilterUpdater { query = query.replaceAll("ORDER BY .*", ""); query = query + String.format("ORDER BY %s", getOrderString()); - query = - query.replace( - TaskDao.TaskCriteria.activeAndVisible().toString(), - TaskDao.TaskCriteria.notDeleted().toString()); + query = showHiddenAndCompleted(query); filter.setFilterQueryOverride(query); } @@ -165,10 +164,7 @@ public class SubtasksFilterUpdater { Set idsInQuery = new HashSet<>(); String sql = filter.getSqlQuery().replaceAll("ORDER BY .*", ""); // $NON-NLS-1$//$NON-NLS-2$ sql = sql + " ORDER BY created"; // $NON-NLS-1$ - sql = - sql.replace( - TaskDao.TaskCriteria.activeAndVisible().toString(), - TaskDao.TaskCriteria.notDeleted().toString()); + sql = showHiddenAndCompleted(sql); List tasks = taskDao.fetchFiltered(sql); for (Task task : tasks) { String id = task.getUuid(); diff --git a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksHelper.java b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksHelper.java index 0e0f8423f..d11be95fc 100644 --- a/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksHelper.java +++ b/app/src/main/java/com/todoroo/astrid/subtasks/SubtasksHelper.java @@ -1,12 +1,14 @@ package com.todoroo.astrid.subtasks; +import static org.tasks.db.QueryUtils.showHidden; + import android.content.Context; import android.text.TextUtils; -import com.todoroo.andlib.sql.Criterion; import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.GtasksFilter; import com.todoroo.astrid.core.BuiltInFilterExposer; import com.todoroo.astrid.dao.TaskDao; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.subtasks.SubtasksFilterUpdater.Node; import java.util.ArrayList; @@ -146,8 +148,7 @@ public class SubtasksHelper { query = query.replaceAll("ORDER BY .*", ""); query = query + String.format(" ORDER BY %s", getOrderString(tagData, tlm)); - query = - query.replace(TaskDao.TaskCriteria.isVisible().toString(), Criterion.all.toString()); + query = showHidden(query); } filter.setFilterQueryOverride(query); diff --git a/app/src/main/java/org/tasks/db/DbUtils.java b/app/src/main/java/org/tasks/db/DbUtils.java index e4f05f017..26df62ab0 100644 --- a/app/src/main/java/org/tasks/db/DbUtils.java +++ b/app/src/main/java/org/tasks/db/DbUtils.java @@ -1,10 +1,12 @@ package org.tasks.db; +import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.partition; import com.google.common.base.Function; import com.google.common.collect.Iterables; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.tasks.Callback; @@ -12,9 +14,9 @@ public class DbUtils { private static final int MAX_SQLITE_ARGS = 990; - public static List collect(List items, Function, List> func) { + public static List collect(Collection items, Function, List> func) { if (items.size() < MAX_SQLITE_ARGS) { - return func.apply(items); + return func.apply(items instanceof List ? (List) items : newArrayList(items)); } List result = new ArrayList<>(); batch(items, b -> result.addAll(func.apply(b))); diff --git a/app/src/main/java/org/tasks/db/QueryUtils.java b/app/src/main/java/org/tasks/db/QueryUtils.java new file mode 100644 index 000000000..34f89bb73 --- /dev/null +++ b/app/src/main/java/org/tasks/db/QueryUtils.java @@ -0,0 +1,36 @@ +package org.tasks.db; + +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.utility.DateUtilities; +import com.todoroo.astrid.data.Task; +import java.util.regex.Pattern; + +public class QueryUtils { + + private static final Pattern HIDDEN = + Pattern.compile("tasks\\.hideUntil<=?\\(strftime\\('%s','now'\\)\\*1000\\)"); + + private static final Pattern UNCOMPLETED = Pattern.compile("tasks\\.completed ids) { + public void cleanup(Iterable ids) { batch( ids, MAX_CLEANUP_LENGTH, diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java index ab9336054..1112408ed 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.java @@ -37,6 +37,8 @@ import org.tasks.intents.TaskIntents; public class TaskListRecyclerAdapter extends RecyclerView.Adapter implements ViewHolder.ViewHolderCallbacks, ListUpdateCallback { + private static final int LONG_LIST_SIZE = 500; + private final TaskAdapter adapter; private final TaskListFragment taskList; private final RecyclerView recyclerView; @@ -227,7 +229,9 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter assertNotMainThread(); DiffCallback cb = new DiffCallback(last.first, next, adapter); - DiffResult result = DiffUtil.calculateDiff(cb, true); + boolean shortList = next.size() < LONG_LIST_SIZE; + boolean calculateDiff = last.first.size() != next.size() || shortList; + DiffResult result = calculateDiff ? DiffUtil.calculateDiff(cb, shortList) : null; return Pair.create(next, result); } @@ -248,7 +252,11 @@ public class TaskListRecyclerAdapter extends RecyclerView.Adapter Pair, DiffResult> update = updates.poll(); while (update != null) { list = update.first; - update.second.dispatchUpdatesTo((ListUpdateCallback) this); + if (update.second == null) { + notifyDataSetChanged(); + } else { + update.second.dispatchUpdatesTo((ListUpdateCallback) this); + } update = updates.poll(); } } diff --git a/app/src/main/java/org/tasks/ui/TaskListViewModel.java b/app/src/main/java/org/tasks/ui/TaskListViewModel.java index 9bc489c9a..89fe6a076 100644 --- a/app/src/main/java/org/tasks/ui/TaskListViewModel.java +++ b/app/src/main/java/org/tasks/ui/TaskListViewModel.java @@ -100,10 +100,15 @@ public class TaskListViewModel extends ViewModel { tasks.observe(owner, observer); } - public static List getQuery(Preferences preferences, Filter filter, boolean subtasks) { + public static List getQuery( + Preferences preferences, + Filter filter, + boolean includeGoogleTaskSubtasks, + boolean includeCaldavSubtasks) { List fields = newArrayList(TASKS, GTASK, CALDAV, GEOFENCE, PLACE); - if (subtasks && !(preferences.isManualSort() && filter.supportsManualSort())) { + if ((includeGoogleTaskSubtasks || includeCaldavSubtasks) + && !(preferences.isManualSort() && filter.supportsManualSort())) { String tagQuery = Query.select(field("group_concat(distinct(tag_uid))")) .from(Tag.TABLE) @@ -118,10 +123,12 @@ public class TaskListViewModel extends ViewModel { + Tag.TASK; fields.add(field("(" + tagQuery + ")").as("tags")); fields.add(INDENT); - fields.add(field("(SELECT count(distinct task) FROM recursive_tasks WHERE parent = tasks._id GROUP BY parent)").as("children")); + fields.add(CHILDREN); - String joinedQuery = Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK)) + JOINS + - " WHERE recursive_tasks.hidden = 0"; + String joinedQuery = Join.inner(RECURSIVE, Task.ID.eq(RECURSIVE_TASK)) + + " LEFT JOIN (SELECT parent, count(recursive_tasks.task) AS children FROM recursive_tasks GROUP BY parent) AS recursive_children ON recursive_children.parent = tasks._id " + + JOINS; + String where = " WHERE recursive_tasks.hidden = 0"; String parentQuery; QueryTemplate subtaskQuery = new QueryTemplate(); if (filter instanceof CaldavFilter) { @@ -144,8 +151,6 @@ public class TaskListViewModel extends ViewModel { Join.inner( CaldavTask.TABLE, Criterion.and( - CaldavTask.CALENDAR.eq(calendar.getUuid()), - CaldavTask.PARENT.gt(0), CaldavTask.TASK.eq(Task.ID), CaldavTask.DELETED.eq(0)))) .where(TaskCriteria.activeAndVisible()); @@ -169,37 +174,23 @@ public class TaskListViewModel extends ViewModel { Join.inner( GoogleTask.TABLE, Criterion.and( - GoogleTask.LIST.eq(list.getRemoteId()), - GoogleTask.PARENT.gt(0), GoogleTask.TASK.eq(Task.ID), GoogleTask.DELETED.eq(0)))) .where(TaskCriteria.activeAndVisible()); } else { parentQuery = PermaSql.replacePlaceholdersForQuery(filter.getSqlQuery()); - subtaskQuery - .join( - Join.left( - GoogleTask.TABLE, - Criterion.and( - GoogleTask.PARENT.gt(0), - GoogleTask.TASK.eq(Task.ID), - GoogleTask.DELETED.eq(0)))) - .join( - Join.left( - CaldavTask.TABLE, - Criterion.and( - CaldavTask.PARENT.gt(0), - CaldavTask.TASK.eq(Task.ID), - CaldavTask.DELETED.eq(0)))) - .join( - Join.inner( - RECURSIVE, - Criterion.or( - GoogleTask.PARENT.eq(RECURSIVE_TASK), - CaldavTask.PARENT.eq(RECURSIVE_TASK)))) - .where(TaskCriteria.activeAndVisible()); - joinedQuery += " AND indent = (select max(indent) from recursive_tasks where tasks._id = recursive_tasks.task) "; + if (includeGoogleTaskSubtasks && includeCaldavSubtasks) { + addGoogleAndCaldavSubtasks(subtaskQuery); + } else if (includeGoogleTaskSubtasks) { + addGoogleSubtasks(subtaskQuery); + } else { + addCaldavSubtasks(subtaskQuery); + } + subtaskQuery.where(TaskCriteria.activeAndVisible()); + joinedQuery += " LEFT JOIN (SELECT task, max(indent) AS max_indent FROM recursive_tasks GROUP BY task) AS recursive_indents ON recursive_indents.task = tasks._id "; + where += " AND indent = max_indent "; } + joinedQuery += where; String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode()); String withClause = "CREATE TEMPORARY TABLE `recursive_tasks` AS\n" @@ -266,6 +257,50 @@ public class TaskListViewModel extends ViewModel { } } + private static void addGoogleSubtasks(QueryTemplate subtaskQuery) { + subtaskQuery + .join(Join.inner(RECURSIVE, GoogleTask.PARENT.eq(RECURSIVE_TASK))) + .join( + Join.inner( + GoogleTask.TABLE, + Criterion.and( + GoogleTask.TASK.eq(Task.ID), + GoogleTask.DELETED.eq(0)))); + } + + private static void addCaldavSubtasks(QueryTemplate subtaskQuery) { + subtaskQuery + .join(Join.inner(RECURSIVE, CaldavTask.PARENT.eq(RECURSIVE_TASK))) + .join( + Join.inner( + CaldavTask.TABLE, + Criterion.and( + CaldavTask.TASK.eq(Task.ID), + CaldavTask.DELETED.eq(0)))); + } + + private static void addGoogleAndCaldavSubtasks(QueryTemplate subtaskQuery) { + subtaskQuery + .join( + Join.inner( + RECURSIVE, + Criterion.or( + GoogleTask.PARENT.eq(RECURSIVE_TASK), + CaldavTask.PARENT.eq(RECURSIVE_TASK)))) + .join( + Join.left( + GoogleTask.TABLE, + Criterion.and( + GoogleTask.TASK.eq(Task.ID), + GoogleTask.DELETED.eq(0)))) + .join( + Join.left( + CaldavTask.TABLE, + Criterion.and( + CaldavTask.TASK.eq(Task.ID), + CaldavTask.DELETED.eq(0)))); + } + public void searchByFilter(Filter filter) { this.filter = filter; invalidate(); @@ -279,7 +314,13 @@ public class TaskListViewModel extends ViewModel { disposable.add( Single.fromCallable( () -> - taskDao.fetchTasks(hasSubtasks -> getQuery(preferences, filter, hasSubtasks))) + taskDao.fetchTasks( + ((includeGoogleSubtasks, includeCaldavSubtasks) -> + getQuery( + preferences, + filter, + includeGoogleSubtasks, + includeCaldavSubtasks)))) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(tasks::postValue, Timber::e)); diff --git a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.java b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.java index 062911f97..e3aed1674 100644 --- a/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.java +++ b/app/src/main/java/org/tasks/widget/ScrollableViewsFactory.java @@ -82,7 +82,10 @@ class ScrollableViewsFactory implements RemoteViewsService.RemoteViewsFactory { @Override public void onDataSetChanged() { updateSettings(); - tasks = taskDao.fetchTasks(hasSubtasks -> getQuery(filter, hasSubtasks)); + tasks = + taskDao.fetchTasks( + (includeGoogleSubtasks, includeCaldavSubtasks) -> + getQuery(filter, includeGoogleSubtasks, includeCaldavSubtasks)); } @Override @@ -196,7 +199,8 @@ class ScrollableViewsFactory implements RemoteViewsService.RemoteViewsFactory { return position < tasks.size() ? tasks.get(position) : null; } - private List getQuery(Filter filter, boolean hasSubtasks) { + private List getQuery( + Filter filter, boolean includeGoogleSubtasks, boolean includeCaldavSubtasks) { AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.scrollable_widget); rv.setTextViewText(R.id.widget_title, filter.listingTitle); @@ -204,7 +208,9 @@ class ScrollableViewsFactory implements RemoteViewsService.RemoteViewsFactory { rv.setInt(R.id.widget, "setLayoutDirection", Locale.getInstance(context).getDirectionality()); } appWidgetManager.partiallyUpdateAppWidget(widgetId, rv); - List queries = TaskListViewModel.getQuery(preferences, filter, hasSubtasks); + List queries = + TaskListViewModel.getQuery( + preferences, filter, includeGoogleSubtasks, includeCaldavSubtasks); int last = queries.size() - 1; queries.set(last, subtasksHelper.applySubtasksToWidgetFilter(filter, queries.get(last))); return queries; diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index beb6fde67..ec71966aa 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -1,5 +1,5 @@ object Versions { - const val kotlin = "1.3.40" + const val kotlin = "1.3.61" const val targetSdk = 29 const val minSdk = 16 const val dagger = "2.25.2"