Add new tasks to top or bottom of caldav lists

pull/996/head
Alex Baker 4 years ago
parent 3a7d62f469
commit 09cad40bd3

@ -5,8 +5,7 @@ import com.natpryce.makeiteasy.MakeItEasy.with
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.helper.UUIDHelper
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
import org.tasks.injection.InjectingTestCase
@ -15,8 +14,10 @@ import org.tasks.makers.TagDataMaker.newTagData
import org.tasks.makers.TagMaker.TAGDATA
import org.tasks.makers.TagMaker.TASK
import org.tasks.makers.TagMaker.newTag
import org.tasks.makers.TaskMaker.CREATION_TIME
import org.tasks.makers.TaskMaker.ID
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import javax.inject.Inject
@RunWith(AndroidJUnit4::class)
@ -26,6 +27,71 @@ class CaldavDaoTests : InjectingTestCase() {
@Inject lateinit var tagDataDao: TagDataDao
@Inject lateinit var caldavDao: CaldavDao
@Test
fun insertNewTaskAtTopOfEmptyList() {
val task = newTask()
taskDao.createNew(task)
val caldavTask = CaldavTask(task.id, "calendar")
caldavDao.insert(task, caldavTask, true)
checkOrder(null, task.id)
}
@Test
fun insertNewTaskAboveExistingTask() {
val created = DateTime(2020, 5, 21, 15, 29, 16, 452)
val first = newTask(with(CREATION_TIME, created))
val second = newTask(with(CREATION_TIME, created.plusSeconds(1)))
taskDao.createNew(first)
taskDao.createNew(second)
caldavDao.insert(first, CaldavTask(first.id, "calendar"), true)
caldavDao.insert(second, CaldavTask(second.id, "calendar"), true)
checkOrder(null, first.id)
checkOrder(created.minusSeconds(1), second.id)
}
@Test
fun insertNewTaskBelowExistingTask() {
val created = DateTime(2020, 5, 21, 15, 29, 16, 452)
val first = newTask(with(CREATION_TIME, created))
val second = newTask(with(CREATION_TIME, created.plusSeconds(1)))
taskDao.createNew(first)
taskDao.createNew(second)
caldavDao.insert(first, CaldavTask(first.id, "calendar"), false)
caldavDao.insert(second, CaldavTask(second.id, "calendar"), false)
checkOrder(null, first.id)
checkOrder(null, second.id)
}
@Test
fun insertNewTaskBelowExistingTaskWithSameCreationDate() {
val created = DateTime(2020, 5, 21, 15, 29, 16, 452)
val first = newTask(with(CREATION_TIME, created))
val second = newTask(with(CREATION_TIME, created))
taskDao.createNew(first)
taskDao.createNew(second)
caldavDao.insert(first, CaldavTask(first.id, "calendar"), false)
caldavDao.insert(second, CaldavTask(second.id, "calendar"), false)
checkOrder(null, first.id)
checkOrder(created.plusSeconds(1), second.id)
}
@Test
fun insertNewTaskAtBottomOfEmptyList() {
val task = newTask()
taskDao.createNew(task)
val caldavTask = CaldavTask(task.id, "calendar")
caldavDao.insert(task, caldavTask, false)
checkOrder(null, task.id)
}
@Test
fun getCaldavTasksWithTags() {
val task = newTask(with(ID, 1L))
@ -67,5 +133,16 @@ class CaldavDaoTests : InjectingTestCase() {
assertTrue(caldavDao.getCaldavFilters(caldavAccount.uuid!!, DateUtilities.now()).isEmpty())
}
private fun checkOrder(dateTime: DateTime, task: Long) = checkOrder(dateTime.toAppleEpoch(), task)
private fun checkOrder(order: Long?, task: Long) {
val sortOrder = caldavDao.getTask(task)!!.order
if (order == null) {
assertNull(sortOrder)
} else {
assertEquals(order, sortOrder)
}
}
override fun inject(component: TestComponent) = component.inject(this)
}

@ -3,8 +3,9 @@ package com.todoroo.astrid.adapter
import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.CaldavDao
import org.tasks.data.TaskContainer
import org.tasks.date.DateTimeUtils.toAppleEpoch
open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao, private val caldavDao: CaldavDao) : TaskAdapter() {
open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao, private val caldavDao: CaldavDao, private val newTasksOnTop: Boolean = false) : TaskAdapter() {
override fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int) = !taskIsChild(source, to)
override fun maxIndent(previousPosition: Int, task: TaskContainer) = getTask(previousPosition).getIndent() + 1
@ -21,7 +22,18 @@ open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao,
override fun supportsParentingOrManualSort() = true
override fun moved(from: Int, to: Int, indent: Int) {
changeParent(getTask(from), indent, to)
val task = getTask(from)
val newParent = changeParent(task, indent, to)
val newPosition = if (newTasksOnTop) {
caldavDao.findFirstTask(task.caldav, newParent)
?.takeIf { task.creationDate.toAppleEpoch() >= it}
?.minus(1)
} else {
caldavDao.findLastTask(task.caldav, newParent)
?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1)
}
caldavDao.update(task.caldavTask.cd_id, newPosition)
}
internal fun changeParent(task: TaskContainer, indent: Int, to: Int): Long {
@ -59,7 +71,7 @@ open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao,
caldavTask.cd_remote_parent = parentTask.remoteId
task.parent = newParent
}
caldavDao.updateParent(caldavTask)
caldavDao.update(caldavTask.cd_id, caldavTask.cd_remote_parent)
taskDao.save(task.getTask(), null)
}

@ -74,7 +74,7 @@ public class TaskAdapterProvider {
if (list != null) {
return preferences.isManualSort()
? new GoogleTaskManualSortAdapter(taskDao, googleTaskDao)
: new GoogleTaskAdapter(taskDao, googleTaskDao, preferences.addGoogleTasksToTop());
: new GoogleTaskAdapter(taskDao, googleTaskDao, preferences.addTasksToTop());
}
} else if (filter instanceof CaldavFilter) {
CaldavFilter caldavFilter = (CaldavFilter) filter;
@ -82,7 +82,7 @@ public class TaskAdapterProvider {
if (calendar != null) {
return preferences.isManualSort()
? new CaldavManualSortTaskAdapter(taskDao, caldavDao)
: new CaldavTaskAdapter(taskDao, caldavDao);
: new CaldavTaskAdapter(taskDao, caldavDao, preferences.addTasksToTop());
}
} else {
return subtasksHelper.shouldUseSubtasksFragmentForFilter(filter)

@ -175,7 +175,7 @@ abstract class TaskDao(private val database: Database) {
abstract fun updateParentUids(tasks: List<Long>)
@Query("UPDATE tasks SET parent = :parent, parent_uuid = :parentUuid WHERE _id IN (:children)")
abstract fun setParent(parent: Long, parentUuid: String, children: List<Long>)
abstract fun setParent(parent: Long, parentUuid: String?, children: List<Long>)
@Transaction
open fun fetchChildren(id: Long): List<Task> {

@ -93,21 +93,21 @@ public class TaskCreator {
createTags(task);
boolean addToTop = preferences.addTasksToTop();
if (task.hasTransitory(GoogleTask.KEY)) {
googleTaskDao.insertAndShift(
new GoogleTask(task.getId(), task.getTransitory(GoogleTask.KEY)),
preferences.addGoogleTasksToTop());
new GoogleTask(task.getId(), task.getTransitory(GoogleTask.KEY)), addToTop);
} else if (task.hasTransitory(CaldavTask.KEY)) {
caldavDao.insert(new CaldavTask(task.getId(), task.getTransitory(CaldavTask.KEY)));
caldavDao.insert(
task, new CaldavTask(task.getId(), task.getTransitory(CaldavTask.KEY)), addToTop);
} else {
Filter remoteList = defaultFilterProvider.getDefaultRemoteList();
if (remoteList instanceof GtasksFilter) {
googleTaskDao.insertAndShift(
new GoogleTask(task.getId(), ((GtasksFilter) remoteList).getRemoteId()),
preferences.addGoogleTasksToTop());
new GoogleTask(task.getId(), ((GtasksFilter) remoteList).getRemoteId()), addToTop);
} else if (remoteList instanceof CaldavFilter) {
caldavDao.insert(
new CaldavTask(task.getId(), ((CaldavFilter) remoteList).getUuid()));
task, new CaldavTask(task.getId(), ((CaldavFilter) remoteList).getUuid()), addToTop);
}
}

@ -88,14 +88,14 @@ public class TaskDuplicator {
}
GoogleTask googleTask = googleTaskDao.getByTaskId(originalId);
boolean addToTop = preferences.addTasksToTop();
if (googleTask != null) {
googleTaskDao.insertAndShift(
new GoogleTask(clone.getId(), googleTask.getListId()), preferences.addGoogleTasksToTop());
googleTaskDao.insertAndShift(new GoogleTask(clone.getId(), googleTask.getListId()), addToTop);
}
CaldavTask caldavTask = caldavDao.getTask(originalId);
if (caldavTask != null) {
caldavDao.insert(new CaldavTask(clone.getId(), caldavTask.getCalendar()));
caldavDao.insert(clone, new CaldavTask(clone.getId(), caldavTask.getCalendar()), addToTop);
}
for (Geofence g : locationDao.getGeofencesForTask(originalId)) {

@ -3,7 +3,6 @@ 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;
@ -14,6 +13,7 @@ import com.todoroo.astrid.api.GtasksFilter;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
@ -110,7 +110,7 @@ public class TaskMover {
if (selected instanceof GtasksFilter) {
String listId = ((GtasksFilter) selected).getRemoteId();
googleTaskDao.insertAndShift(new GoogleTask(id, listId), preferences.addGoogleTasksToTop());
googleTaskDao.insertAndShift(new GoogleTask(id, listId), preferences.addTasksToTop());
if (!children.isEmpty()) {
googleTaskDao.insert(
transform(
@ -125,7 +125,7 @@ public class TaskMover {
} else if (selected instanceof CaldavFilter) {
String listId = ((CaldavFilter) selected).getUuid();
CaldavTask newParent = new CaldavTask(id, listId);
caldavDao.insert(newParent);
caldavDao.insert(task, newParent, preferences.addTasksToTop());
caldavDao.insert(
transform(
childIds,
@ -161,7 +161,7 @@ public class TaskMover {
CaldavTask newParent =
new CaldavTask(id1, listId, caldavTask.getRemoteId(), caldavTask.getObject());
newParent.setVtodo(caldavTask.getVtodo());
caldavDao.insert(newParent);
caldavDao.insert(task, newParent, preferences.addTasksToTop());
caldavDao.insert(
transform(
children,
@ -185,13 +185,15 @@ public class TaskMover {
} 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));
Map<Long, CaldavTask> tasks = new HashMap<>();
CaldavTask root = new CaldavTask(id, listId);
for (Task child : taskDao.fetchChildren(task.getId())) {
CaldavTask newTask = new CaldavTask(child.getId(), listId);
newTask.setRemoteParent(tasks.get(child.getParent()).getRemoteId());
long parent = child.getParent();
newTask.setRemoteParent((parent == id ? root : tasks.get(parent)).getRemoteId());
tasks.put(child.getId(), newTask);
}
caldavDao.insert(task, root, preferences.addTasksToTop());
caldavDao.insert(tasks.values());
}
}
@ -199,7 +201,7 @@ public class TaskMover {
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());
googleTaskDao.insertAndShift(new GoogleTask(id, listId), preferences.addTasksToTop());
List<GoogleTask> newChildren = new ArrayList<>();
for (int i = 0; i < children.size(); i++) {
GoogleTask newChild = new GoogleTask(children.get(i), listId);

@ -4,7 +4,9 @@ import androidx.lifecycle.LiveData
import androidx.room.*
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.core.SortHelper.APPLE_EPOCH
import com.todoroo.astrid.data.Task
import io.reactivex.Single
import org.tasks.date.DateTimeUtils.toAppleEpoch
import org.tasks.db.DbUtils
import org.tasks.filters.CaldavFilters
@ -44,6 +46,29 @@ abstract class CaldavDao {
@Update
abstract fun update(caldavCalendar: CaldavCalendar)
@Transaction
open fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long {
if (caldavTask.order != null) {
return insert(caldavTask)
}
if (addToTop) {
caldavTask.order = findFirstTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() >= it }
?.minus(1)
} else {
caldavTask.order = findLastTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1)
}
return insert(caldavTask)
}
@Query("SELECT MIN(IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000)) FROM caldav_tasks INNER JOIN tasks ON _id = cd_task WHERE cd_calendar = :calendar AND cd_deleted = 0 AND deleted = 0 AND parent = :parent")
internal abstract fun findFirstTask(calendar: String, parent: Long): Long?
@Query("SELECT MAX(IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000)) FROM caldav_tasks INNER JOIN tasks ON _id = cd_task WHERE cd_calendar = :calendar AND cd_deleted = 0 AND deleted = 0 AND parent = :parent")
internal abstract fun findLastTask(calendar: String, parent: Long): Long?
@Insert
abstract fun insert(caldavTask: CaldavTask): Long
@ -53,10 +78,6 @@ abstract class CaldavDao {
@Update
abstract fun update(caldavTask: CaldavTask)
fun updateParent(caldavTask: SubsetCaldav) {
update(caldavTask.cd_id, caldavTask.cd_remote_parent)
}
@Query("UPDATE caldav_tasks SET cd_order = :position WHERE cd_id = :id")
internal abstract fun update(id: Long, position: Long?)

@ -80,6 +80,10 @@ public class TaskContainer {
return indent;
}
public long getCreationDate() {
return task.getCreationDate();
}
public void setIndent(int indent) {
this.indent = indent;
targetIndent = indent;

@ -1,28 +0,0 @@
package org.tasks.date;
import java.util.TimeZone;
import org.tasks.time.DateTime;
public class DateTimeUtils {
public static DateTime newDate(int year, int month, int day) {
return new DateTime(year, month, day, 0, 0, 0);
}
public static DateTime newDateUtc(
int year, int month, int day, int hour, int minute, int second) {
return new DateTime(year, month, day, hour, minute, second, 0, TimeZone.getTimeZone("GMT"));
}
public static DateTime newDateTime() {
return new DateTime();
}
public static long midnight() {
return newDateTime().plusDays(1).startOfDay().getMillis();
}
public static DateTime newDateTime(long timestamp) {
return new DateTime(timestamp);
}
}

@ -0,0 +1,26 @@
package org.tasks.date
import org.tasks.time.DateTime
import java.util.*
object DateTimeUtils {
@JvmStatic
fun newDate(year: Int, month: Int, day: Int): DateTime = DateTime(year, month, day, 0, 0, 0)
@JvmStatic
fun newDateUtc(
year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int): DateTime {
return DateTime(year, month, day, hour, minute, second, 0, TimeZone.getTimeZone("GMT"))
}
@JvmStatic
fun newDateTime(): DateTime = DateTime()
@JvmStatic
fun midnight(): Long = newDateTime().plusDays(1).startOfDay().millis
@JvmStatic
fun newDateTime(timestamp: Long): DateTime = DateTime(timestamp)
fun Long.toAppleEpoch(): Long = DateTime(this).toAppleEpoch()
}

@ -64,8 +64,8 @@ public class Preferences {
return context.getPackageName() + "_preferences";
}
public boolean addGoogleTasksToTop() {
return getBoolean(R.string.p_google_tasks_add_to_top, true);
public boolean addTasksToTop() {
return getBoolean(R.string.p_add_to_top, true);
}
public boolean backButtonSavesTask() {

@ -218,11 +218,11 @@ class Synchronization : InjectingPreferenceFragment() {
val hasGoogleAccounts: Boolean = addGoogleTasksAccounts(synchronizationPreferences)
val hasCaldavAccounts = addCaldavAccounts(synchronizationPreferences)
findPreference(R.string.gtasks_GPr_header).isVisible = hasGoogleAccounts
findPreference(R.string.accounts).isVisible = hasGoogleAccounts || hasCaldavAccounts
findPreference(R.string.sync_SPr_interval_title).isVisible =
hasGoogleAccounts || hasCaldavAccounts
findPreference(R.string.p_default_remote_list).isVisible =
hasGoogleAccounts || hasCaldavAccounts
val syncEnabled = hasGoogleAccounts || hasCaldavAccounts
findPreference(R.string.accounts).isVisible = syncEnabled
findPreference(R.string.sync_SPr_interval_title).isVisible = syncEnabled
findPreference(R.string.p_default_remote_list).isVisible = syncEnabled
findPreference(R.string.p_add_to_top).isVisible = syncEnabled
}
private fun updateRemoteListSummary() {

@ -40,7 +40,6 @@ import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.*
import org.tasks.injection.FragmentComponent
import org.tasks.locale.Locale
import org.tasks.preferences.Preferences
import org.tasks.tasklist.SubtaskViewHolder
import org.tasks.tasklist.SubtasksRecyclerAdapter
import java.util.*
@ -59,7 +58,6 @@ class SubtaskControlSet : TaskEditControlFragment(), SubtaskViewHolder.Callbacks
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var toaster: Toaster
@Inject lateinit var preferences: Preferences
@Inject lateinit var taskCreator: TaskCreator
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskDao: TaskDao
@ -127,14 +125,14 @@ class SubtaskControlSet : TaskEditControlFragment(), SubtaskViewHolder.Callbacks
val googleTask = GoogleTask(subtask.id, (remoteList as GtasksFilter).remoteId)
googleTask.parent = task.id
googleTask.isMoved = true
googleTaskDao.insertAndShift(googleTask, preferences.addGoogleTasksToTop())
googleTaskDao.insertAndShift(googleTask, false)
}
is CaldavFilter -> {
val caldavTask = CaldavTask(subtask.id, (remoteList as CaldavFilter).uuid)
subtask.parent = task.id
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.id)
taskDao.save(subtask)
caldavDao.insert(caldavTask)
caldavDao.insert(subtask, caldavTask, false)
}
else -> {
subtask.parent = task.id

@ -341,7 +341,7 @@
<string name="p_map_provider">map_provider</string>
<string name="p_place_provider">place_provider</string>
<string name="preference_screen">preference_screen</string>
<string name="p_google_tasks_add_to_top">google_tasks_add_to_top</string>
<string name="p_add_to_top">google_tasks_add_to_top</string>
<string name="p_google_tasks_position_hack">google_tasks_position_hack</string>
<string name="google_tasks_position_hack">Custom order synchronization fix</string>
<string name="google_tasks_position_hack_summary">Always perform a full synchronization to workaround https://issuetracker.google.com/issues/132432317</string>

@ -9,6 +9,12 @@
android:title="@string/default_sync"
app:isPreferenceVisible="false" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="@string/p_add_to_top"
android:title="@string/google_tasks_add_to_top"
app:isPreferenceVisible="false"/>
<PreferenceCategory
android:key="@string/accounts"
android:title="@string/accounts"
@ -24,11 +30,6 @@
android:title="@string/gtasks_GPr_header"
app:isPreferenceVisible="false">
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="@string/p_google_tasks_add_to_top"
android:title="@string/google_tasks_add_to_top" />
<SwitchPreferenceCompat
android:key="@string/google_tasks_position_hack"
android:summary="@string/google_tasks_position_hack_summary"

Loading…
Cancel
Save