Add support for offline multi-level subtasks

gtask_related_email
Alex Baker 6 years ago
parent 066b92c19f
commit 9e85cc5b01

File diff suppressed because it is too large Load Diff

@ -6,12 +6,20 @@
package com.todoroo.astrid.dao;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static org.tasks.makers.TaskMaker.ID;
import static org.tasks.makers.TaskMaker.PARENT;
import static org.tasks.makers.TaskMaker.newTask;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Longs;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskDeleter;
@ -155,6 +163,34 @@ public class TaskDaoTests extends InjectingTestCase {
assertEquals(0, taskDao.getAll().size());
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void findChildrenInList() {
taskDao.createNew(newTask(with(ID, 1L)));
taskDao.createNew(newTask(with(ID, 2L), with(PARENT, 1L)));
assertEquals(singletonList(2L), taskDao.findChildrenInList(Longs.asList(1, 2)));
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void findRecursiveChildrenInList() {
taskDao.createNew(newTask(with(ID, 1L)));
taskDao.createNew(newTask(with(ID, 2L), with(PARENT, 1L)));
taskDao.createNew(newTask(with(ID, 3L), with(PARENT, 2L)));
assertEquals(asList(2L, 3L), taskDao.findChildrenInList(Longs.asList(1, 2, 3)));
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void findRecursiveChildrenInListAfterSkippingParent() {
taskDao.createNew(newTask(with(ID, 1L)));
taskDao.createNew(newTask(with(ID, 2L), with(PARENT, 1L)));
taskDao.createNew(newTask(with(ID, 3L), with(PARENT, 2L)));
assertEquals(singletonList(3L), taskDao.findChildrenInList(Longs.asList(1, 3)));
}
@Override
protected void inject(TestComponent component) {
component.inject(this);

@ -26,6 +26,7 @@ import com.google.common.primitives.Longs;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.util.List;
import javax.inject.Inject;
import org.junit.Before;
@ -41,6 +42,7 @@ import org.tasks.injection.TestComponent;
import org.tasks.jobs.WorkManager;
import org.tasks.makers.CaldavTaskMaker;
import org.tasks.makers.GtaskListMaker;
import org.tasks.makers.TaskMaker;
@RunWith(AndroidJUnit4.class)
public class TaskMoverTest extends InjectingTestCase {
@ -125,7 +127,9 @@ public class TaskMoverTest extends InjectingTestCase {
@Test
@SdkSuppress(minSdkVersion = 21)
public void moveRecursiveCaldavChildren() {
createTasks(1, 2, 3);
createTasks(1);
createSubtask(2, 1);
createSubtask(3, 2);
caldavDao.insert(
asList(
newCaldavTask(
@ -133,13 +137,11 @@ public class TaskMoverTest extends InjectingTestCase {
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 2L),
with(REMOTE_PARENT, "b"))));
moveToCaldavList("2", 1);
@ -148,7 +150,7 @@ public class TaskMoverTest extends InjectingTestCase {
assertEquals(3, deleted.size());
CaldavTask task = caldavDao.getTask(3);
assertEquals("2", task.getCalendar());
assertEquals(2, task.getParent());
assertEquals(2, taskDao.fetch(3).getParent());
}
@Test
@ -160,13 +162,49 @@ public class TaskMoverTest extends InjectingTestCase {
moveToCaldavList("1", 1);
CaldavTask task = caldavDao.getTask(2);
assertEquals(1L, task.getParent());
assertEquals("1", task.getCalendar());
assertEquals(1, taskDao.fetch(2).getParent());
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void flattenLocalSubtasksWhenMovingToGoogleTasks() {
createTasks(1);
createSubtask(2, 1);
createSubtask(3, 2);
moveToGoogleTasks("1", 1);
assertEquals(1, googleTaskDao.getByTaskId(3).getParent());
assertEquals(0, taskDao.fetch(3).getParent());
}
@Test
public void moveLocalChildToGoogleTasks() {
createTasks(1);
createSubtask(2, 1);
moveToGoogleTasks("1", 2);
assertEquals(0, taskDao.fetch(2).getParent());
}
@Test
public void moveLocalChildToCaldav() {
createTasks(1);
createSubtask(2, 1);
moveToCaldavList("1", 2);
assertEquals(0, taskDao.fetch(2).getParent());
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void flattenCaldavSubtasksWhenMovingToGoogleTasks() {
createTasks(1, 2, 3);
createTasks(1);
createSubtask(2, 1);
createSubtask(3, 2);
caldavDao.insert(
asList(
newCaldavTask(
@ -174,13 +212,11 @@ public class TaskMoverTest extends InjectingTestCase {
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 2L),
with(REMOTE_PARENT, "b"))));
moveToGoogleTasks("1", 1);
@ -204,7 +240,8 @@ public class TaskMoverTest extends InjectingTestCase {
@Test
public void moveCaldavChildWithoutParent() {
createTasks(1, 2);
createTasks(1);
createSubtask(2, 1);
caldavDao.insert(
asList(
newCaldavTask(
@ -212,13 +249,12 @@ public class TaskMoverTest extends InjectingTestCase {
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_PARENT, "a"))));
moveToCaldavList("2", 2);
CaldavTask task = caldavDao.getTask(2);
assertEquals(0, task.getParent());
assertEquals("2", caldavDao.getTask(2).getCalendar());
assertEquals(0, taskDao.fetch(2).getParent());
}
@Test
@ -241,6 +277,18 @@ public class TaskMoverTest extends InjectingTestCase {
assertEquals("2", googleTaskDao.getByTaskId(1L).getListId());
}
@Test
public void moveLocalToCaldav() {
createTasks(1);
createSubtask(2, 1);
createSubtask(3, 2);
moveToCaldavList("1", 1);
assertEquals("1", caldavDao.getTask(3).getCalendar());
assertEquals(2, taskDao.fetch(3).getParent());
}
@Test
public void dontSyncGoogleTask() {
createTasks(1);
@ -272,12 +320,17 @@ public class TaskMoverTest extends InjectingTestCase {
dontSync(1);
assertNull(googleTaskDao.getByTaskId(2));
assertFalse(taskDao.fetch(2).isDeleted());
Task task = taskDao.fetch(2);
assertFalse(task.isDeleted());
assertEquals(1, task.getParent());
assertEquals(taskDao.fetch(1).getUuid(), task.getParentUuid());
}
@Test
public void dontSyncCaldavWithSubtasks() {
createTasks(1, 2);
createTasks(1);
createSubtask(2, 1);
createSubtask(3, 2);
caldavDao.insert(
asList(
newCaldavTask(
@ -285,13 +338,20 @@ public class TaskMoverTest extends InjectingTestCase {
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_PARENT, "a"))));
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "b"))));
dontSync(2);
dontSync(1);
assertNull(caldavDao.getTask(2));
assertFalse(taskDao.fetch(2).isDeleted());
assertNull(caldavDao.getTask(3));
Task task = taskDao.fetch(3);
assertFalse(task.isDeleted());
assertEquals(2, task.getParent());
assertEquals(taskDao.fetch(2).getUuid(), task.getParentUuid());
}
@Test
@ -329,7 +389,8 @@ public class TaskMoverTest extends InjectingTestCase {
@Test
public void dontDuplicateWhenParentAndChildCaldavMoved() {
createTasks(1, 2);
createTasks(1);
createSubtask(2, 1);
caldavDao.insert(
asList(
newCaldavTask(
@ -337,7 +398,6 @@ public class TaskMoverTest extends InjectingTestCase {
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_PARENT, "a"))));
moveToCaldavList("2", 1, 2);
@ -351,6 +411,10 @@ public class TaskMoverTest extends InjectingTestCase {
}
}
private void createSubtask(long id, long parent) {
taskDao.createNew(newTask(with(ID, id), with(TaskMaker.PARENT, parent)));
}
private void moveToGoogleTasks(String list, long... tasks) {
taskMover.move(
Longs.asList(tasks), new GtasksFilter(newGtaskList(with(GtaskListMaker.REMOTE_ID, list))));

@ -1,14 +1,9 @@
package org.tasks.data;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.tasks.makers.CaldavTaskMaker.CALENDAR;
import static org.tasks.makers.CaldavTaskMaker.REMOTE_ID;
import static org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT;
import static org.tasks.makers.CaldavTaskMaker.newCaldavTask;
import static org.tasks.makers.TagDataMaker.newTagData;
import static org.tasks.makers.TagMaker.TAGDATA;
import static org.tasks.makers.TagMaker.TASK;
@ -17,8 +12,6 @@ import static org.tasks.makers.TaskMaker.ID;
import static org.tasks.makers.TaskMaker.newTask;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import com.google.common.primitives.Longs;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import javax.inject.Inject;
@ -26,7 +19,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.tasks.injection.InjectingTestCase;
import org.tasks.injection.TestComponent;
import org.tasks.makers.CaldavTaskMaker;
@RunWith(AndroidJUnit4.class)
public class CaldavDaoTests extends InjectingTestCase {
@ -71,74 +63,6 @@ public class CaldavDaoTests extends InjectingTestCase {
assertTrue(caldavDao.getTasksWithTags().isEmpty());
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void findChildrenInList() {
taskDao.createNew(newTask(with(ID, 1L)));
taskDao.createNew(newTask(with(ID, 2L)));
caldavDao.insert(
asList(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_PARENT, "a"))));
assertEquals(singletonList(2L), caldavDao.findChildrenInList(Longs.asList(1, 2)));
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void findRecursiveChildrenInList() {
taskDao.createNew(newTask(with(ID, 1L)));
taskDao.createNew(newTask(with(ID, 2L)));
taskDao.createNew(newTask(with(ID, 3L)));
caldavDao.insert(
asList(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 2L),
with(REMOTE_PARENT, "b"))));
assertEquals(asList(2L, 3L), caldavDao.findChildrenInList(Longs.asList(1, 2, 3)));
}
@Test
@SdkSuppress(minSdkVersion = 21)
public void findRecursiveChildrenInListAfterSkippingParent() {
taskDao.createNew(newTask(with(ID, 1L)));
taskDao.createNew(newTask(with(ID, 2L)));
taskDao.createNew(newTask(with(ID, 3L)));
caldavDao.insert(
asList(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 1L),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(CALENDAR, "1"),
with(CaldavTaskMaker.PARENT, 2L),
with(REMOTE_PARENT, "b"))));
assertEquals(singletonList(3L), caldavDao.findChildrenInList(Longs.asList(1, 3)));
}
@Override
protected void inject(TestComponent component) {
component.inject(this);

@ -3,7 +3,6 @@ package org.tasks.makers;
import static com.natpryce.makeiteasy.Property.newProperty;
import static org.tasks.makers.Maker.make;
import com.google.common.base.Strings;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Property;
import com.natpryce.makeiteasy.PropertyValue;
@ -13,7 +12,6 @@ public class CaldavTaskMaker {
public static final Property<CaldavTask, String> CALENDAR = newProperty();
public static final Property<CaldavTask, Long> TASK = newProperty();
public static final Property<CaldavTask, Long> PARENT = newProperty();
public static final Property<CaldavTask, String> REMOTE_ID = newProperty();
public static final Property<CaldavTask, String> REMOTE_PARENT = newProperty();
@ -21,7 +19,6 @@ public class CaldavTaskMaker {
lookup -> {
CaldavTask task =
new CaldavTask(lookup.valueOf(TASK, 1L), lookup.valueOf(CALENDAR, "calendar"));
task.setParent(lookup.valueOf(PARENT, 0L));
task.setRemoteId(lookup.valueOf(REMOTE_ID, task.getRemoteId()));
task.setRemoteParent(lookup.valueOf(REMOTE_PARENT, (String) null));
return task;

@ -29,6 +29,8 @@ public class TaskMaker {
public static final Property<Task, Boolean> AFTER_COMPLETE = newProperty();
private static final Property<Task, String> TITLE = newProperty();
private static final Property<Task, Integer> PRIORITY = newProperty();
public static final Property<Task, Long> PARENT = newProperty();
private static final Instantiator<Task> instantiator =
lookup -> {
Task task = new Task();
@ -101,6 +103,8 @@ public class TaskMaker {
DateTime creationTime = lookup.valueOf(CREATION_TIME, newDateTime());
task.setCreationDate(creationTime.getMillis());
task.setParent(lookup.valueOf(PARENT, 0L));
return task;
};

@ -85,16 +85,17 @@ public final class CaldavTaskAdapter extends TaskAdapter {
if (newParent == 0) {
caldavTask.setRemoteParent("");
caldavTask.setParent(0);
task.setParent(0);
} else {
CaldavTask parentTask = caldavDao.getTask(newParent);
if (parentTask == null) {
return;
}
caldavTask.setRemoteParent(parentTask.getRemoteId());
caldavTask.setParent(newParent);
task.setParent(newParent);
}
caldavDao.update(caldavTask);
taskDao.save(task.getTask());
}
private boolean taskIsChild(TaskContainer source, int destinationIndex) {

@ -58,7 +58,7 @@ import org.tasks.notifications.NotificationDao;
CaldavAccount.class,
GoogleTaskAccount.class
},
version = 69)
version = 70)
public abstract class Database extends RoomDatabase {
public static final String NAME = "database";

@ -8,6 +8,7 @@ package com.todoroo.astrid.dao;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static com.todoroo.andlib.sql.SqlConstants.COUNT;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
@ -32,6 +33,7 @@ import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.helper.UUIDHelper;
import java.util.Collections;
import java.util.List;
import org.tasks.BuildConfig;
import org.tasks.data.Place;
@ -100,7 +102,7 @@ public abstract class TaskDao {
"SELECT tasks.* FROM tasks "
+ "LEFT JOIN google_tasks ON tasks._id = google_tasks.gt_task "
+ "WHERE gt_list_id IN (SELECT gtl_remote_id FROM google_task_lists WHERE gtl_account = :account)"
+ "AND (tasks.modified > google_tasks.gt_last_sync OR google_tasks.gt_remote_id = '') "
+ "AND (tasks.modified > google_tasks.gt_last_sync OR google_tasks.gt_remote_id = '' OR google_tasks.gt_deleted > 0) "
+ "ORDER BY CASE WHEN gt_parent = 0 THEN 0 ELSE 1 END, gt_order ASC")
public abstract List<Task> getGoogleTasksToPush(String account);
@ -141,7 +143,7 @@ public abstract class TaskDao {
public List<TaskContainer> fetchTasks(QueryCallback callback) {
long start = BuildConfig.DEBUG ? now() : 0;
boolean includeGoogleSubtasks = atLeastLollipop() && hasGoogleTaskSubtasks();
boolean includeCaldavSubtasks = atLeastLollipop() && hasCaldavSubtasks();
boolean includeCaldavSubtasks = atLeastLollipop() && hasSubtasks();
List<String> queries = callback.getQueries(includeGoogleSubtasks, includeCaldavSubtasks);
SupportSQLiteDatabase db = database.getOpenHelper().getWritableDatabase();
int last = queries.size() - 1;
@ -159,11 +161,8 @@ public abstract class TaskDao {
@RawQuery
abstract int count(SimpleSQLiteQuery query);
@Query(
"SELECT EXISTS(SELECT 1 FROM caldav_tasks "
+ "INNER JOIN tasks ON cd_task = _id "
+ "WHERE deleted = 0 AND cd_parent > 0 AND cd_deleted = 0)")
abstract boolean hasCaldavSubtasks();
@Query("SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0)")
abstract boolean hasSubtasks();
@Query(
"SELECT EXISTS(SELECT 1 FROM google_tasks "
@ -187,6 +186,58 @@ public abstract class TaskDao {
@Query("UPDATE tasks SET modified = strftime('%s','now')*1000 WHERE _id in (:ids)")
abstract void touchInternal(List<Long> ids);
@Query(
"UPDATE tasks SET parent = IFNULL(("
+ " SELECT parent._id FROM tasks AS parent"
+ " WHERE parent.remoteId = tasks.parent_uuid AND parent.deleted = 0), 0)"
+ "WHERE parent_uuid IS NOT NULL AND parent_uuid != ''")
public abstract void updateParents();
@Query(
"UPDATE tasks SET parent_uuid = "
+ " (SELECT parent.remoteId FROM tasks AS parent WHERE parent._id = tasks.parent)"
+ " WHERE parent > 0 AND _id IN (:tasks)")
public abstract void updateParentUids(List<Long> tasks);
@Query("UPDATE tasks SET parent = :parent, parent_uuid = :parentUuid WHERE _id IN (:children)")
public abstract void setParent(long parent, String parentUuid, List<Long> children);
@Transaction
public List<Task> fetchChildren(long id) {
return fetch(getChildren(id));
}
public List<Long> getChildren(long id) {
return getChildren(Collections.singletonList(id));
}
public List<Long> getChildren(List<Long> ids) {
return atLeastLollipop()
? getChildrenRecursive(ids)
: Collections.emptyList();
}
@Query(
"WITH RECURSIVE "
+ " recursive_tasks (task) AS ( "
+ " SELECT _id "
+ " FROM tasks "
+ "WHERE parent IN (:ids)"
+ "UNION ALL "
+ " SELECT _id "
+ " FROM tasks "
+ " INNER JOIN recursive_tasks "
+ " ON recursive_tasks.task = tasks.parent"
+ " WHERE tasks.deleted = 0)"
+ "SELECT task FROM recursive_tasks")
abstract List<Long> getChildrenRecursive(List<Long> ids);
public List<Long> findChildrenInList(List<Long> ids) {
List<Long> result = newArrayList(ids);
result.retainAll(getChildren(ids));
return result;
}
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id")
public abstract void setCollapsed(long id, boolean collapsed);

@ -20,6 +20,7 @@ import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.ical.values.RRule;
import com.todoroo.andlib.data.Property.IntegerProperty;
@ -65,6 +66,7 @@ public class Task implements Parcelable {
public static final LongProperty DELETION_DATE = new LongProperty(TABLE, "deleted");
public static final StringProperty NOTES = new StringProperty(TABLE, "notes");
public static final LongProperty TIMER_START = new LongProperty(TABLE, "timerStart");
public static final LongProperty PARENT = new LongProperty(TABLE, "parent");
/** constant value for no uuid */
public static final String NO_UUID = "0"; // $NON-NLS-1$
@ -187,6 +189,12 @@ public class Task implements Parcelable {
@ColumnInfo(name = "collapsed")
public boolean collapsed;
@ColumnInfo(name = "parent")
public transient long parent;
@ColumnInfo(name = "parent_uuid")
public String parentUuid;
// --- due and hide until date management
@Ignore private transient HashMap<String, Object> transitoryData = null;
@ -241,6 +249,8 @@ public class Task implements Parcelable {
remoteId = parcel.readString();
transitoryData = parcel.readHashMap(ContentValues.class.getClassLoader());
collapsed = ParcelCompat.readBoolean(parcel);
parent = parcel.readLong();
parentUuid = parcel.readString();
}
/**
@ -600,6 +610,22 @@ public class Task implements Parcelable {
this.calendarUri = calendarUri;
}
public long getParent() {
return parent;
}
public void setParent(long parent) {
this.parent = parent;
}
public String getParentUuid() {
return parentUuid;
}
public void setParentUuid(String parentUuid) {
this.parentUuid = parentUuid;
}
public boolean isNotifyModeNonstop() {
return isReminderFlagSet(Task.NOTIFY_MODE_NONSTOP);
}
@ -660,6 +686,8 @@ public class Task implements Parcelable {
dest.writeString(remoteId);
dest.writeMap(transitoryData);
ParcelCompat.writeBoolean(dest, collapsed);
dest.writeLong(parent);
dest.writeString(parentUuid);
}
@Override
@ -714,6 +742,11 @@ public class Task implements Parcelable {
+ '\''
+ ", collapsed="
+ collapsed
+ ", parent="
+ parent
+ ", parentUuid='"
+ parentUuid
+ '\''
+ ", transitoryData="
+ transitoryData
+ '}';
@ -786,6 +819,12 @@ public class Task implements Parcelable {
if (calendarUri != null ? !calendarUri.equals(task.calendarUri) : task.calendarUri != null) {
return false;
}
if (parent != task.parent) {
return false;
}
if (!Objects.equal(parentUuid, task.parentUuid)) {
return false;
}
return remoteId != null ? remoteId.equals(task.remoteId) : task.remoteId == null;
}
@ -809,6 +848,9 @@ public class Task implements Parcelable {
if (deleted != null ? !deleted.equals(original.deleted) : original.deleted != null) {
return false;
}
if (parent != original.parent) {
return false;
}
return notes != null ? notes.equals(original.notes) : original.notes == null;
}
@ -843,6 +885,9 @@ public class Task implements Parcelable {
: original.recurrence != null) {
return false;
}
if (parent != original.parent) {
return false;
}
return repeatUntil != null
? repeatUntil.equals(original.repeatUntil)
: original.repeatUntil == null;
@ -902,6 +947,9 @@ public class Task implements Parcelable {
if (collapsed != task.collapsed) {
return false;
}
if (parent != task.parent) {
return false;
}
if (id != null ? !id.equals(task.id) : task.id != null) {
return false;
}
@ -932,31 +980,26 @@ public class Task implements Parcelable {
if (notes != null ? !notes.equals(task.notes) : task.notes != null) {
return false;
}
if (estimatedSeconds != null
? !estimatedSeconds.equals(task.estimatedSeconds)
if (estimatedSeconds != null ? !estimatedSeconds.equals(task.estimatedSeconds)
: task.estimatedSeconds != null) {
return false;
}
if (elapsedSeconds != null
? !elapsedSeconds.equals(task.elapsedSeconds)
if (elapsedSeconds != null ? !elapsedSeconds.equals(task.elapsedSeconds)
: task.elapsedSeconds != null) {
return false;
}
if (timerStart != null ? !timerStart.equals(task.timerStart) : task.timerStart != null) {
return false;
}
if (notificationFlags != null
? !notificationFlags.equals(task.notificationFlags)
if (notificationFlags != null ? !notificationFlags.equals(task.notificationFlags)
: task.notificationFlags != null) {
return false;
}
if (notifications != null
? !notifications.equals(task.notifications)
if (notifications != null ? !notifications.equals(task.notifications)
: task.notifications != null) {
return false;
}
if (lastNotified != null
? !lastNotified.equals(task.lastNotified)
if (lastNotified != null ? !lastNotified.equals(task.lastNotified)
: task.lastNotified != null) {
return false;
}
@ -975,8 +1018,10 @@ public class Task implements Parcelable {
if (remoteId != null ? !remoteId.equals(task.remoteId) : task.remoteId != null) {
return false;
}
return transitoryData != null
? transitoryData.equals(task.transitoryData)
if (parentUuid != null ? !parentUuid.equals(task.parentUuid) : task.parentUuid != null) {
return false;
}
return transitoryData != null ? transitoryData.equals(task.transitoryData)
: task.transitoryData == null;
}
@ -1004,6 +1049,8 @@ public class Task implements Parcelable {
result = 31 * result + (calendarUri != null ? calendarUri.hashCode() : 0);
result = 31 * result + (remoteId != null ? remoteId.hashCode() : 0);
result = 31 * result + (collapsed ? 1 : 0);
result = 31 * result + (int) (parent ^ (parent >>> 32));
result = 31 * result + (parentUuid != null ? parentUuid.hashCode() : 0);
result = 31 * result + (transitoryData != null ? transitoryData.hashCode() : 0);
return result;
}

@ -17,13 +17,11 @@ public class TaskCompleter {
private final TaskDao taskDao;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
@Inject
TaskCompleter(TaskDao taskDao, GoogleTaskDao googleTaskDao, CaldavDao caldavDao) {
TaskCompleter(TaskDao taskDao, GoogleTaskDao googleTaskDao) {
this.taskDao = taskDao;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
}
public void setComplete(long taskId) {
@ -39,7 +37,7 @@ public class TaskCompleter {
long completionDate = completed ? now() : 0L;
setComplete(Collections.singletonList(item), completionDate);
List<Task> tasks = newArrayList(googleTaskDao.getChildTasks(item.getId()));
List<Long> caldavChildren = caldavDao.getChildren(item.getId());
List<Long> caldavChildren = taskDao.getChildren(item.getId());
if (!caldavChildren.isEmpty()) {
tasks.addAll(taskDao.fetch(caldavChildren));
}

@ -15,7 +15,6 @@ import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavAccount;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavDao;
import org.tasks.data.DeletionDao;
import org.tasks.data.GoogleTaskAccount;
import org.tasks.data.GoogleTaskDao;
@ -31,7 +30,6 @@ public class TaskDeleter {
private final TaskDao taskDao;
private final LocalBroadcastManager localBroadcastManager;
private final GoogleTaskDao googleTaskDao;
private final CaldavDao caldavDao;
private final Preferences preferences;
private final DeletionDao deletionDao;
@ -42,14 +40,12 @@ public class TaskDeleter {
TaskDao taskDao,
LocalBroadcastManager localBroadcastManager,
GoogleTaskDao googleTaskDao,
CaldavDao caldavDao,
Preferences preferences) {
this.deletionDao = deletionDao;
this.workManager = workManager;
this.taskDao = taskDao;
this.localBroadcastManager = localBroadcastManager;
this.googleTaskDao = googleTaskDao;
this.caldavDao = caldavDao;
this.preferences = preferences;
}
@ -66,7 +62,7 @@ public class TaskDeleter {
public List<Task> markDeleted(List<Long> taskIds) {
Set<Long> ids = new HashSet<>(taskIds);
ids.addAll(collect(taskIds, googleTaskDao::getChildren));
ids.addAll(collect(taskIds, caldavDao::getChildren));
ids.addAll(collect(taskIds, taskDao::getChildren));
deletionDao.markDeleted(ids);
workManager.cleanup(ids);
workManager.sync(false);

@ -1,17 +1,22 @@
package com.todoroo.astrid.service;
import static com.google.common.collect.FluentIterable.from;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.transform;
import static com.google.common.collect.Maps.newHashMap;
import static com.todoroo.andlib.utility.DateUtilities.now;
import static java.util.Collections.emptyList;
import androidx.annotation.Nullable;
import com.google.common.collect.Lists;
import com.todoroo.astrid.api.CaldavFilter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.SyncFlags;
import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.tasks.LocalBroadcastManager;
import org.tasks.data.CaldavDao;
@ -45,20 +50,6 @@ public class TaskMover {
this.localBroadcastManager = localBroadcastManager;
}
public void move(List<Long> tasks, Filter selectedList) {
tasks = newArrayList(tasks);
tasks.removeAll(googleTaskDao.findChildrenInList(tasks));
tasks.removeAll(caldavDao.findChildrenInList(tasks));
for (Task task : taskDao.fetch(tasks)) {
performMove(task, selectedList);
}
if (selectedList instanceof CaldavFilter) {
caldavDao.updateParents((((CaldavFilter) selectedList).getUuid()));
}
taskDao.touch(tasks);
localBroadcastManager.broadcastRefresh();
}
public Filter getSingleFilter(List<Long> tasks) {
List<String> caldavCalendars = caldavDao.getCalendars(tasks);
List<String> googleTaskLists = googleTaskDao.getLists(tasks);
@ -74,44 +65,57 @@ public class TaskMover {
return null;
}
private void performMove(Task task, Filter selectedList) {
public void move(List<Long> tasks, Filter selectedList) {
tasks = newArrayList(tasks);
tasks.removeAll(googleTaskDao.findChildrenInList(tasks));
tasks.removeAll(taskDao.findChildrenInList(tasks));
taskDao.setParent(0, null, tasks);
for (Task task : taskDao.fetch(tasks)) {
performMove(task, selectedList);
}
if (selectedList instanceof CaldavFilter) {
caldavDao.updateParents((((CaldavFilter) selectedList).getUuid()));
}
taskDao.touch(tasks);
localBroadcastManager.broadcastRefresh();
}
private void performMove(Task task, @Nullable Filter selectedList) {
long id = task.getId();
GoogleTask googleTask = googleTaskDao.getByTaskId(id);
List<GoogleTask> googleTaskChildren = emptyList();
List<CaldavTask> caldavChildren = emptyList();
if (googleTask != null
&& selectedList instanceof GtasksFilter
&& googleTask.getListId().equals(((GtasksFilter) selectedList).getRemoteId())) {
if (googleTask != null) {
moveGoogleTask(task, googleTask, selectedList);
return;
}
CaldavTask caldavTask = caldavDao.getTask(id);
if (caldavTask != null
&& selectedList instanceof CaldavFilter
&& caldavTask.getCalendar().equals(((CaldavFilter) selectedList).getUuid())) {
if (caldavTask != null) {
moveCaldavTask(task, caldavTask, selectedList);
return;
}
if (googleTask != null) {
googleTaskChildren = googleTaskDao.getChildren(id);
googleTaskDao.markDeleted(now(), id);
}
if (caldavTask != null) {
List<Long> toDelete = newArrayList(caldavTask.getTask());
List<Long> childIds = caldavDao.getChildren(caldavTask.getTask());
if (!childIds.isEmpty()) {
caldavChildren = caldavDao.getTasks(childIds);
toDelete.addAll(childIds);
}
caldavDao.markDeleted(now(), toDelete);
moveLocalTask(task, selectedList);
}
private void moveGoogleTask(Task task, GoogleTask googleTask, Filter selected) {
if (selected instanceof GtasksFilter
&& googleTask.getListId().equals(((GtasksFilter) selected).getRemoteId())) {
return;
}
if (selectedList instanceof GtasksFilter) {
String listId = ((GtasksFilter) selectedList).getRemoteId();
long id = googleTask.getTask();
List<GoogleTask> children = googleTaskDao.getChildren(id);
List<Long> childIds = from(children).transform(GoogleTask::getTask).toList();
googleTaskDao.markDeleted(now(), id);
if (selected instanceof GtasksFilter) {
String listId = ((GtasksFilter) selected).getRemoteId();
googleTaskDao.insertAndShift(new GoogleTask(id, listId), preferences.addGoogleTasksToTop());
if (!googleTaskChildren.isEmpty()) {
if (!children.isEmpty()) {
googleTaskDao.insert(
transform(
googleTaskChildren,
children,
child -> {
GoogleTask newChild = new GoogleTask(child.getTask(), listId);
newChild.setOrder(child.getOrder());
@ -119,37 +123,91 @@ public class TaskMover {
return newChild;
}));
}
if (!caldavChildren.isEmpty()) {
List<GoogleTask> children = newArrayList();
for (int i = 0 ; i < caldavChildren.size() ; i++) {
CaldavTask child = caldavChildren.get(i);
GoogleTask newChild = new GoogleTask(child.getTask(), listId);
newChild.setOrder(i);
newChild.setParent(id);
children.add(newChild);
}
googleTaskDao.insert(children);
}
} else if (selectedList instanceof CaldavFilter) {
String listId = ((CaldavFilter) selectedList).getUuid();
CaldavTask newParent = caldavTask == null
? new CaldavTask(id, listId)
: new CaldavTask(id, listId, caldavTask.getRemoteId(), caldavTask.getObject());
if (caldavTask != null) {
newParent.setVtodo(caldavTask.getVtodo());
}
} else if (selected instanceof CaldavFilter) {
String listId = ((CaldavFilter) selected).getUuid();
CaldavTask newParent = new CaldavTask(id, listId);
caldavDao.insert(newParent);
caldavDao.insert(
transform(
childIds,
child -> {
CaldavTask newChild = new CaldavTask(child, listId);
newChild.setRemoteParent(newParent.getRemoteId());
return newChild;
}));
} else {
taskDao.setParent(task.getId(), task.getUuid(), childIds);
}
}
private void moveCaldavTask(Task task, CaldavTask caldavTask, Filter selected) {
if (selected instanceof CaldavFilter
&& caldavTask.getCalendar().equals(((CaldavFilter) selected).getUuid())) {
return;
}
long id = task.getId();
List<Long> childIds = taskDao.getChildren(id);
List<Long> toDelete = newArrayList(id);
List<CaldavTask> children = emptyList();
if (!childIds.isEmpty()) {
children = caldavDao.getTasks(childIds);
toDelete.addAll(childIds);
}
caldavDao.markDeleted(now(), toDelete);
if (selected instanceof CaldavFilter) {
long id1 = caldavTask.getTask();
String listId = ((CaldavFilter) selected).getUuid();
CaldavTask newParent =
new CaldavTask(id1, listId, caldavTask.getRemoteId(), caldavTask.getObject());
newParent.setVtodo(caldavTask.getVtodo());
caldavDao.insert(newParent);
caldavDao.insert(transform(googleTaskChildren, child -> {
CaldavTask newChild = new CaldavTask(child.getTask(), listId);
newChild.setRemoteParent(newParent.getRemoteId());
return newChild;
}));
caldavDao.insert(transform(caldavChildren, child -> {
CaldavTask newChild = new CaldavTask(child.getTask(), listId, child.getRemoteId(), child.getObject());
newChild.setVtodo(child.getVtodo());
newChild.setRemoteParent(child.getRemoteParent());
return newChild;
}));
caldavDao.insert(
transform(
children,
child -> {
CaldavTask newChild =
new CaldavTask(child.getTask(), listId, child.getRemoteId(), child.getObject());
newChild.setVtodo(child.getVtodo());
newChild.setRemoteParent(child.getRemoteParent());
return newChild;
}));
} else if (selected instanceof GtasksFilter) {
moveToGoogleTasks(id, childIds, (GtasksFilter) selected);
} else {
taskDao.updateParentUids(from(children).transform(CaldavTask::getTask).toList());
}
}
private void moveLocalTask(Task task, @Nullable Filter selected) {
if (selected instanceof GtasksFilter) {
moveToGoogleTasks(task.getId(), taskDao.getChildren(task.getId()), (GtasksFilter) selected);
} else if (selected instanceof CaldavFilter) {
long id = task.getId();
String listId = ((CaldavFilter) selected).getUuid();
Map<Long, CaldavTask> tasks = newHashMap();
tasks.put(id, new CaldavTask(id, listId));
for (Task child : taskDao.fetchChildren(task.getId())) {
CaldavTask newTask = new CaldavTask(child.getId(), listId);
newTask.setRemoteParent(tasks.get(child.parent).getRemoteId());
tasks.put(child.getId(), newTask);
}
caldavDao.insert(tasks.values());
}
}
private void moveToGoogleTasks(long id, List<Long> children, GtasksFilter filter) {
taskDao.setParent(0, null, children);
String listId = filter.getRemoteId();
googleTaskDao.insertAndShift(new GoogleTask(id, listId), preferences.addGoogleTasksToTop());
List<GoogleTask> newChildren = new ArrayList<>();
for (int i = 0; i < children.size(); i++) {
GoogleTask newChild = new GoogleTask(children.get(i), listId);
newChild.setOrder(i);
newChild.setParent(id);
newChildren.add(newChild);
}
googleTaskDao.insert(newChildren);
}
}

@ -243,6 +243,7 @@ public class TasksJsonImporter {
googleTaskDao.updateParents();
caldavDao.updateParents();
taskDao.updateParents();
for (Entry<String, Integer> entry : backupContainer.getIntPrefs().entrySet()) {
preferences.setInt(entry.getKey(), entry.getValue());

@ -157,7 +157,7 @@ public class CaldavConverter {
}
remote.setLastModified(newDateTime(task.getModificationDate()).toUTC().getMillis());
remote.setPriority(toRemote(remote.getPriority(), task.getPriority()));
setParent(remote, caldavTask.getParent() == 0 ? null : caldavTask.getRemoteParent());
setParent(remote, task.getParent() == 0 ? null : caldavTask.getRemoteParent());
return remote;
}

@ -1,7 +1,5 @@
package org.tasks.data;
import static com.google.common.collect.Lists.newArrayList;
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastLollipop;
import static org.tasks.db.DbUtils.collect;
import androidx.lifecycle.LiveData;
@ -11,7 +9,6 @@ import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import io.reactivex.Single;
import java.util.Collections;
import java.util.List;
import org.tasks.filters.CaldavFilters;
@ -59,12 +56,12 @@ public abstract class CaldavDao {
public abstract void update(CaldavTask caldavTask);
public void update(SubsetCaldav caldavTask) {
update(caldavTask.getId(), caldavTask.getParent(), caldavTask.getRemoteParent());
update(caldavTask.getId(), caldavTask.getRemoteParent());
}
@Query(
"UPDATE caldav_tasks SET cd_parent = :parent, cd_remote_parent = :remoteParent WHERE cd_id = :id")
abstract void update(long id, long parent, String remoteParent);
"UPDATE caldav_tasks SET cd_remote_parent = :remoteParent WHERE cd_id = :id")
abstract void update(long id,String remoteParent);
@Update
public abstract void update(Iterable<CaldavTask> tasks);
@ -152,51 +149,24 @@ public abstract class CaldavDao {
public abstract List<Long> getTasksWithTags();
@Query(
"UPDATE caldav_tasks"
+ " SET cd_parent = IFNULL(("
+ " SELECT cd_task FROM caldav_tasks AS p "
+ " WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent"
+ " AND p.cd_calendar = caldav_tasks.cd_calendar"
+ " AND p.cd_deleted = 0),"
+ " 0)")
"UPDATE tasks SET parent = IFNULL(("
+ " SELECT p.cd_task FROM caldav_tasks AS p"
+ " INNER JOIN caldav_tasks ON caldav_tasks.cd_task = tasks._id"
+ " WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent"
+ " AND p.cd_calendar = caldav_tasks.cd_calendar"
+ " AND p.cd_deleted = 0), 0)"
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0)")
public abstract void updateParents();
@Query("UPDATE caldav_tasks SET cd_parent = IFNULL((SELECT cd_task FROM caldav_tasks AS p WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent), 0) WHERE cd_calendar = :calendar")
@Query(
"UPDATE tasks SET parent = IFNULL(("
+ " SELECT p.cd_task FROM caldav_tasks AS p"
+ " INNER JOIN caldav_tasks "
+ " ON caldav_tasks.cd_task = tasks._id"
+ " AND caldav_tasks.cd_calendar = :calendar"
+ " WHERE p.cd_remote_id = caldav_tasks.cd_remote_parent"
+ " AND p.cd_calendar = caldav_tasks.cd_calendar"
+ " AND caldav_tasks.cd_deleted = 0), 0)"
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)")
public abstract void updateParents(String calendar);
public List<Long> getChildren(long id) {
return getChildren(Collections.singletonList(id));
}
public List<Long> getChildren(List<Long> ids) {
return atLeastLollipop()
? getChildrenRecursive(ids)
: Collections.emptyList();
}
@Query("WITH RECURSIVE "
+ " recursive_caldav (cd_task) AS ( "
+ " SELECT cd_task "
+ " FROM tasks "
+ " INNER JOIN caldav_tasks "
+ " ON _id = cd_task "
+ " WHERE cd_parent IN (:ids) "
+ " AND tasks.deleted = 0 AND caldav_tasks.cd_deleted = 0 "
+ "UNION ALL "
+ " SELECT caldav_tasks.cd_task "
+ " FROM tasks "
+ " INNER JOIN caldav_tasks "
+ " ON _id = caldav_tasks.cd_task "
+ " INNER JOIN recursive_caldav "
+ " ON recursive_caldav.cd_task = caldav_tasks.cd_parent "
+ " WHERE tasks.deleted = 0 AND caldav_tasks.cd_deleted = 0 "
+ " ) "
+ "SELECT cd_task FROM recursive_caldav")
abstract List<Long> getChildrenRecursive(List<Long> ids);
public List<Long> findChildrenInList(List<Long> ids) {
List<Long> result = newArrayList(ids);
result.retainAll(getChildren(ids));
return result;
}
}

@ -10,23 +10,13 @@ import androidx.room.PrimaryKey;
import com.todoroo.andlib.data.Property;
import com.todoroo.andlib.data.Table;
@Entity(
tableName = "caldav_tasks",
indices = {
@Index(name = "cd_task", value = "cd_task"),
@Index(
name = "cd_calendar_parent",
value = {"cd_calendar", "cd_parent"})
})
@Entity(tableName = "caldav_tasks", indices = @Index(name = "cd_task", value = "cd_task"))
public class CaldavTask {
public static final String KEY = "caldav";
public static final Table TABLE = new Table("caldav_tasks");
public static final Property.IntegerProperty PARENT =
new Property.IntegerProperty(TABLE, "cd_parent");
public static final Property.IntegerProperty TASK =
new Property.IntegerProperty(TABLE, "cd_task");
@ -64,9 +54,6 @@ public class CaldavTask {
@ColumnInfo(name = "cd_vtodo")
private String vtodo;
@ColumnInfo(name = "cd_parent")
private transient long parent;
@ColumnInfo(name = "cd_remote_parent")
private String remoteParent;
@ -160,14 +147,6 @@ public class CaldavTask {
this.vtodo = vtodo;
}
public long getParent() {
return parent;
}
public void setParent(long parent) {
this.parent = parent;
}
public String getRemoteParent() {
return remoteParent;
}
@ -202,9 +181,6 @@ public class CaldavTask {
+ ", vtodo='"
+ vtodo
+ '\''
+ ", parent='"
+ parent
+ '\''
+ ", remoteParent='"
+ remoteParent
+ '\''

@ -1,5 +1,6 @@
package org.tasks.data;
import androidx.annotation.Nullable;
import androidx.room.Embedded;
import com.todoroo.astrid.data.Task;
@ -189,19 +190,19 @@ public class TaskContainer {
public long getParent() {
if (googletask != null) {
return googletask.getParent();
} else if (caldavTask != null) {
return caldavTask.getParent();
} else {
return 0;
return task.getParent();
}
}
public void setParent(long parent) {
if (googletask != null) {
task.setParent(0);
googletask.setParent(parent);
} else if (caldavTask != null) {
caldavTask.setParent(parent);
} else {
task.setParent(parent);
}
task.setParentUuid(null);
}
public boolean hasParent() {

@ -356,6 +356,31 @@ public class Migrations {
}
};
private static final Migration MIGRATION_69_70 =
new Migration(69, 70) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `tasks` ADD COLUMN `parent` INTEGER NOT NULL DEFAULT 0");
database.execSQL("ALTER TABLE `tasks` ADD COLUMN `parent_uuid` TEXT");
database.execSQL(
"UPDATE `tasks` SET `parent` = IFNULL(("
+ " SELECT p.cd_task FROM caldav_tasks"
+ " INNER JOIN caldav_tasks AS p ON p.cd_remote_id = caldav_tasks.cd_remote_parent"
+ " WHERE caldav_tasks.cd_task = tasks._id"
+ " AND caldav_tasks.cd_deleted = 0"
+ " AND p.cd_calendar = caldav_tasks.cd_calendar"
+ " AND p.cd_deleted = 0), 0)");
database.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav_tasks-temp`");
database.execSQL(
"CREATE TABLE IF NOT EXISTS `caldav_tasks` (`cd_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `cd_task` INTEGER NOT NULL, `cd_calendar` TEXT, `cd_object` TEXT, `cd_remote_id` TEXT, `cd_etag` TEXT, `cd_last_sync` INTEGER NOT NULL, `cd_deleted` INTEGER NOT NULL, `cd_vtodo` TEXT, `cd_remote_parent` TEXT)");
database.execSQL(
"INSERT INTO `caldav_tasks` (`cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`, `cd_remote_parent`) "
+ "SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_vtodo`, `cd_remote_parent` FROM `caldav_tasks-temp`");
database.execSQL("DROP TABLE `caldav_tasks-temp`");
database.execSQL("CREATE INDEX `cd_task` ON `caldav_tasks` (`cd_task`)");
}
};
public static final Migration[] MIGRATIONS =
new Migration[] {
MIGRATION_35_36,
@ -382,7 +407,8 @@ public class Migrations {
MIGRATION_65_66,
MIGRATION_66_67,
MIGRATION_67_68,
MIGRATION_68_69
MIGRATION_68_69,
MIGRATION_69_70
};
private static Migration NOOP(int from, int to) {

@ -72,9 +72,9 @@ public class SubtaskViewHolder extends RecyclerView.ViewHolder {
return Math.round(indent * getShiftSize());
}
void bindView(TaskContainer task, boolean multiLevelSubtasks) {
void bindView(TaskContainer task) {
this.task = task;
setIndent(multiLevelSubtasks ? task.indent : 0);
setIndent(task.indent);
if (task.hasChildren()) {
chip.setText(locale.formatNumber(task.children));
chip.setVisibility(View.VISIBLE);

@ -50,7 +50,8 @@ public class SubtasksRecyclerAdapter extends RecyclerView.Adapter<SubtaskViewHol
public void onBindViewHolder(@NonNull SubtaskViewHolder holder, int position) {
TaskContainer task = getItem(position);
if (task != null) {
holder.bindView(task, multiLevelSubtasks);
task.setIndent(multiLevelSubtasks ? task.indent : 0);
holder.bindView(task);
}
}
@ -88,6 +89,9 @@ public class SubtasksRecyclerAdapter extends RecyclerView.Adapter<SubtaskViewHol
}
public void setMultiLevelSubtasksEnabled(boolean enabled) {
multiLevelSubtasks = enabled;
if (multiLevelSubtasks != enabled) {
multiLevelSubtasks = enabled;
notifyItemRangeChanged(0, differ.getCurrentList().size());
}
}
}

@ -127,16 +127,10 @@ public class SubtaskControlSet extends TaskEditControlFragment implements Callba
GoogleTask.PARENT.eq(task.getId()),
GoogleTask.TASK.eq(Task.ID),
GoogleTask.DELETED.eq(0))))
.join(
Join.left(
CaldavTask.TABLE,
Criterion.and(
CaldavTask.PARENT.eq(task.getId()),
CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0))))
.where(Criterion.and(TaskCriteria.activeAndVisible(),
Criterion.or(CaldavTask.TASK.gt(0), GoogleTask.TASK.gt(0))));
.where(
Criterion.and(
TaskCriteria.activeAndVisible(),
Criterion.or(Task.PARENT.eq(task.getId()), GoogleTask.TASK.gt(0))));
}
@Override
@ -176,16 +170,21 @@ public class SubtaskControlSet extends TaskEditControlFragment implements Callba
} else if (remoteList instanceof CaldavFilter) {
CaldavTask caldavTask =
new CaldavTask(subtask.getId(), ((CaldavFilter) remoteList).getUuid());
caldavTask.setParent(task.getId());
subtask.setParent(task.getId());
caldavTask.setRemoteParent(caldavDao.getRemoteIdForTask(task.getId()));
taskDao.save(subtask);
caldavDao.insert(caldavTask);
} else {
subtask.setParent(task.getId());
subtask.setParentUuid(task.getUuid());
taskDao.save(subtask);
}
}
}
@Override
public boolean hasChanges(Task original) {
return remoteList != null && !getNewSubtasks().isEmpty();
return !getNewSubtasks().isEmpty();
}
private ArrayList<Task> getNewSubtasks() {
@ -224,10 +223,6 @@ public class SubtaskControlSet extends TaskEditControlFragment implements Callba
@OnClick(R.id.add_subtask)
void addSubtask() {
if (remoteList == null) {
toaster.longToast(R.string.subtasks_enable_synchronization);
return;
}
if (isGoogleTaskChild()) {
toaster.longToast(R.string.subtasks_multilevel_google_task);
return;
@ -290,7 +285,7 @@ public class SubtaskControlSet extends TaskEditControlFragment implements Callba
}
private void updateUI() {
if (remoteList == null || isGoogleTaskChild()) {
if (isGoogleTaskChild()) {
recyclerView.setVisibility(View.GONE);
newSubtaskContainer.setVisibility(View.GONE);
} else {

@ -157,19 +157,12 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
CaldavTask.TABLE,
Criterion.and(
CaldavTask.CALENDAR.eq(calendar.getUuid()),
CaldavTask.PARENT.eq(0),
CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0))))
.where(TaskCriteria.activeAndVisible())
.where(Criterion.and(TaskCriteria.activeAndVisible(), Task.PARENT.eq(0)))
.toString();
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))))
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.where(TaskCriteria.activeAndVisible());
} else if (filter instanceof GtasksFilter) {
GoogleTaskList list = ((GtasksFilter) filter).getList();
@ -212,7 +205,7 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
String sortSelect = SortHelper.orderSelectForSortTypeRecursive(preferences.getSortMode());
String withClause = "CREATE TEMPORARY TABLE `recursive_tasks` AS\n"
+ "WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField) AS (\n"
+ " SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(title) AS sort_title, "
+ " SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, "
+ sortSelect
+ " FROM tasks\n"
+ parentQuery
@ -282,13 +275,7 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
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))));
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)));
}
private static void addGoogleAndCaldavSubtasks(QueryTemplate subtaskQuery) {
@ -298,7 +285,7 @@ public class TaskListViewModel extends ViewModel implements Observer<PagedList<T
RECURSIVE,
Criterion.or(
GoogleTask.PARENT.eq(RECURSIVE_TASK),
CaldavTask.PARENT.eq(RECURSIVE_TASK))))
Task.PARENT.eq(RECURSIVE_TASK))))
.join(
Join.left(
GoogleTask.TABLE,

@ -550,7 +550,6 @@
<string name="collapse_subtasks">Unteraufgaben einklappen</string>
<string name="TEA_add_subtask">Unteraufgabe hinzufügen</string>
<string name="subtasks">Unteraufgaben</string>
<string name="subtasks_enable_synchronization">Synchronisation aktivieren, um Unteraufgaben hinzuzufügen</string>
<string name="subtasks_multilevel_google_task">Mehrere Unteraufgaben-Ebenen werden von Google Tasks nicht unterstützt</string>
<string name="subtasks_multilevel_google_task">Mehrere Unteraufgaben-Ebenen werden von Google Tasks nicht unterstützt</string>
<string name="enter_title_hint">Titel eingeben</string>
</resources>

@ -550,8 +550,7 @@
<string name="collapse_subtasks">Contraer subtareas</string>
<string name="TEA_add_subtask">Añadir subtarea</string>
<string name="subtasks">Subtareas</string>
<string name="subtasks_enable_synchronization">Habilitar la sincronización para añadir subtareas</string>
<string name="subtasks_multilevel_google_task">Subtareas multinivel no compatibles con Google Tasks</string>
<string name="subtasks_multilevel_google_task">Subtareas multinivel no compatibles con Google Tasks</string>
<string name="enter_title_hint">Introducir título</string>
<string name="show_subtasks">Mostrar subtareas</string>
<string name="show_subtasks_summary">La visualización de subtareas degradará el rendimiento de la aplicación</string>

@ -557,7 +557,6 @@
<string name="collapse_subtasks">Tolestu azpi-zereginak</string>
<string name="TEA_add_subtask">Gehitu azpi-zeregina</string>
<string name="subtasks">Azpi-zereginak</string>
<string name="subtasks_enable_synchronization">Gaitu sinkronizazioa azpi-zereginak gehitzeko</string>
<string name="subtasks_multilevel_google_task">Google Tasks-ek ez ditu hainbat mailako azpi-zereginak onartzen</string>
<string name="enter_title_hint">Sartu izenburua</string>
<string name="show_subtasks">Erakutsi azpi-zereginak</string>

@ -551,8 +551,7 @@ est configuré correctement</string>
<string name="collapse_subtasks">Réduire les sous-tâches</string>
<string name="TEA_add_subtask">Ajouter sous-tâche</string>
<string name="subtasks">Sous-tâches</string>
<string name="subtasks_enable_synchronization">Activer la synchronisation pour ajouter des sous-tâches</string>
<string name="subtasks_multilevel_google_task">Les sous-tâches multi-niveaux ne sont pas prises en charge par Google Tasks</string>
<string name="subtasks_multilevel_google_task">Les sous-tâches multi-niveaux ne sont pas prises en charge par Google Tasks</string>
<string name="enter_title_hint">Entrer le titre</string>
<string name="show_subtasks">Afficher les sous-tâches</string>
<string name="show_subtasks_summary">L\'affichage des sous-tâches dégradera les performances de l\'application</string>

@ -550,8 +550,7 @@
<string name="collapse_subtasks">Deeltaken inklappen</string>
<string name="TEA_add_subtask">Deeltaak toevoegen</string>
<string name="subtasks">Deeltaken</string>
<string name="subtasks_enable_synchronization">Zet synchronisatie aan om deeltaken toe te voegen</string>
<string name="subtasks_multilevel_google_task">Deeltaken met meerdere niveau\'s worden niet ondersteund door Google Taken</string>
<string name="subtasks_multilevel_google_task">Deeltaken met meerdere niveau\'s worden niet ondersteund door Google Taken</string>
<string name="enter_title_hint">Voer titel in</string>
<string name="show_subtasks">Deeltaken tonen</string>
<string name="show_subtasks_summary">Deeltaken tonen vermindert prestaties van de app</string>

@ -546,8 +546,7 @@
<string name="collapse_subtasks">缩回子任务列表</string>
<string name="TEA_add_subtask">添加子任务</string>
<string name="subtasks">子任务</string>
<string name="subtasks_enable_synchronization">启用同步来添加子任务</string>
<string name="subtasks_multilevel_google_task">Google Tasks不支持多层子任务</string>
<string name="subtasks_multilevel_google_task">Google Tasks不支持多层子任务</string>
<string name="enter_title_hint">输入标题</string>
<string name="show_subtasks">显示子任务</string>
<string name="show_subtasks_summary">显示子任务将降级应用性能</string>

@ -558,7 +558,6 @@ File %1$s contained %2$s.\n\n
<string name="caldav_account_repeating_tasks">Let server schedule recurring tasks</string>
<string name="expand_subtasks">Expand subtasks</string>
<string name="collapse_subtasks">Collapse subtasks</string>
<string name="subtasks_enable_synchronization">Enable synchronization to add subtasks</string>
<string name="subtasks_multilevel_google_task">Multi-level subtasks not supported by Google Tasks</string>
<string name="enter_title_hint">Enter title</string>
<string name="show_subtasks">Show subtasks</string>

Loading…
Cancel
Save