Merge tag '7.3.2'

gtask_related_email
Alex Baker 6 years ago
commit 769a23dd29

@ -1,28 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="Convert2streamapi" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="EqualsReplaceableByObjectsCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreTrivialFinalizers" value="true" />
</inspection_tool>
<inspection_tool class="Guava" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="OverridableMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
<inspection_tool class="StaticPseudoFunctionalStyleMethod" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="TryWithIdenticalCatches" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="UnnecessarySuperQualifier" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

@ -1,48 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="3">
<item index="0" class="java.lang.String" itemvalue="butterknife.*" />
<item index="1" class="java.lang.String" itemvalue="dagger.Provides" />
<item index="2" class="java.lang.String" itemvalue="javax.inject.Inject" />
</list>
<component name="CMakeSettings">
<configurations>
<configuration PROFILE_NAME="Debug" CONFIG_NAME="Debug" />
</configurations>
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="10">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
<item index="4" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" />
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" />
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="9">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" />
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" />
<item index="6" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" />
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" />
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" />
</list>
</value>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

@ -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

@ -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

@ -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);
}

@ -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()));
}
}

@ -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) {

@ -39,6 +39,10 @@ public class Field extends DBObject<Field> {
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);
}

@ -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;

@ -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);
}

@ -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<String, Object> getValuesForNewTask(TagData tagData) {

@ -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) {

@ -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;

@ -136,9 +136,11 @@ public abstract class TaskDao {
public abstract int clearCompletedCalendarEvents();
@Transaction
public List<TaskContainer> fetchTasks(Function<Boolean, List<String>> getQueries) {
public List<TaskContainer> fetchTasks(QueryCallback callback) {
long start = BuildConfig.DEBUG ? now() : 0;
List<String> queries = getQueries.apply(atLeastLollipop() && hasSubtasks());
boolean includeGoogleSubtasks = atLeastLollipop() && hasGoogleTaskSubtasks();
boolean includeCaldavSubtasks = atLeastLollipop() && hasCaldavSubtasks();
List<String> 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<Long> 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<String> getQueries(boolean includeGoogleTaskSubtasks, boolean includeCaldavSubtasks);
}
}

@ -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<Task> markDeleted(List<Long> taskIds) {
Set<Long> 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<Long> completed = new ArrayList<>();
Filter deleteFilter = new Filter(null, null);
deleteFilter.setFilterQueryOverride(
filter
.getOriginalSqlQuery()
.replace(isVisible().toString(), all.toString())
.replace(notCompleted().toString(), all.toString()));
List<TaskContainer> tasks = taskDao.fetchTasks(
hasSubtasks -> TaskListViewModel.getQuery(preferences, deleteFilter, hasSubtasks));
deleteFilter.setFilterQueryOverride(showHiddenAndCompleted(filter.getOriginalSqlQuery()));
List<TaskContainer> tasks =
taskDao.fetchTasks(
(includeGoogleSubtasks, includeCaldavSubtasks) ->
TaskListViewModel.getQuery(
preferences, deleteFilter, includeGoogleSubtasks, includeCaldavSubtasks));
for (TaskContainer task : tasks) {
if (task.isCompleted()) {
completed.add(task.getId());

@ -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<String> 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<Task> tasks = taskDao.fetchFiltered(sql);
for (Task task : tasks) {
String id = task.getUuid();

@ -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);

@ -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 <F, T> List<T> collect(List<F> items, Function<List<F>, List<T>> func) {
public static <F, T> List<T> collect(Collection<F> items, Function<List<F>, List<T>> func) {
if (items.size() < MAX_SQLITE_ARGS) {
return func.apply(items);
return func.apply(items instanceof List ? (List<F>) items : newArrayList(items));
}
List<T> result = new ArrayList<>();
batch(items, b -> result.addAll(func.apply(b)));

@ -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<?=0");
public static String showHidden(String query) {
return HIDDEN.matcher(query).replaceAll("1");
}
public static String showCompleted(String query) {
return UNCOMPLETED.matcher(query).replaceAll("1");
}
public static String showHiddenAndCompleted(String query) {
return showCompleted(showHidden(query));
}
public static String showRecentlyCompleted(String query) {
return UNCOMPLETED
.matcher(query)
.replaceAll(
Criterion.or(
Task.COMPLETION_DATE.lte(0),
Task.COMPLETION_DATE.gte(DateUtilities.now() - 59999))
.toString());
}
}

@ -87,7 +87,7 @@ public class WorkManager {
.build());
}
public void cleanup(List<Long> ids) {
public void cleanup(Iterable<Long> ids) {
batch(
ids,
MAX_CLEANUP_LENGTH,

@ -37,6 +37,8 @@ import org.tasks.intents.TaskIntents;
public class TaskListRecyclerAdapter extends RecyclerView.Adapter<ViewHolder>
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<ViewHolder>
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<ViewHolder>
Pair<List<TaskContainer>, 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();
}
}

@ -100,10 +100,15 @@ public class TaskListViewModel extends ViewModel {
tasks.observe(owner, observer);
}
public static List<String> getQuery(Preferences preferences, Filter filter, boolean subtasks) {
public static List<String> getQuery(
Preferences preferences,
Filter filter,
boolean includeGoogleTaskSubtasks,
boolean includeCaldavSubtasks) {
List<Field> 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));

@ -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<String> getQuery(Filter filter, boolean hasSubtasks) {
private List<String> 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<String> queries = TaskListViewModel.getQuery(preferences, filter, hasSubtasks);
List<String> queries =
TaskListViewModel.getQuery(
preferences, filter, includeGoogleSubtasks, includeCaldavSubtasks);
int last = queries.size() - 1;
queries.set(last, subtasksHelper.applySubtasksToWidgetFilter(filter, queries.get(last)));
return queries;

@ -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"

Loading…
Cancel
Save