Implement drag and drop reordering for CalDAV

pull/996/head
Alex Baker 4 years ago
parent bb84f0d3c3
commit e6beaddbd6

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 75, "version": 75,
"identityHash": "4a32edd266822b077c230270a019d9b3", "identityHash": "ff70ddcfc863c0ebc97522480d78d23d",
"entities": [ "entities": [
{ {
"tableName": "notification", "tableName": "notification",
@ -935,7 +935,7 @@
}, },
{ {
"tableName": "caldav_tasks", "tableName": "caldav_tasks",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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, `cd_order` INTEGER, `cd_remote_order` INTEGER, `cd_moved` INTEGER NOT NULL)", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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, `cd_order` INTEGER)",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -1002,18 +1002,6 @@
"columnName": "cd_order", "columnName": "cd_order",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": false "notNull": false
},
{
"fieldPath": "remoteOrder",
"columnName": "cd_remote_order",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "moved",
"columnName": "cd_moved",
"affinity": "INTEGER",
"notNull": true
} }
], ],
"primaryKey": { "primaryKey": {
@ -1162,7 +1150,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4a32edd266822b077c230270a019d9b3')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ff70ddcfc863c0ebc97522480d78d23d')"
] ]
} }
} }

@ -25,35 +25,49 @@ open class CaldavManualSortTaskAdapter internal constructor(private val taskDao:
override fun moved(from: Int, to: Int, indent: Int) { override fun moved(from: Int, to: Int, indent: Int) {
val task = getTask(from) val task = getTask(from)
val previous = if (to > 0) getTask(to - 1) else null val previous = if (to > 0) getTask(to - 1) else null
var newParent = task.parent val newParent = findNewParent(indent, to)
if (indent == 0) {
newParent = 0
} else if (previous != null) {
when {
indent == previous.getIndent() -> newParent = previous.parent
indent > previous.getIndent() -> newParent = previous.id
indent < previous.getIndent() -> {
newParent = previous.parent
var currentIndex = to
for (i in 0 until previous.getIndent() - indent) {
var thisParent = newParent
while (newParent == thisParent) {
thisParent = getTask(--currentIndex).parent
}
newParent = thisParent
}
}
}
}
// If nothing is changing, return if (newParent == task.parent && from == to) {
if (newParent == task.parent) {
return return
} }
changeParent(task, newParent)
if (newParent != task.parent) {
changeParent(task, newParent)
}
if (from != to) {
val newPosition = when {
previous == null -> 1
indent > previous.getIndent() -> 1
indent == previous.getIndent() -> previous.caldavSortOrder + 1
else -> getTask((to - 1 downTo 0).find { getTask(it).indent == indent }!!).caldavSortOrder + 1
}
caldavDao.move(task, newParent, newPosition)
}
taskDao.touch(task.id) taskDao.touch(task.id)
} }
internal fun findNewParent(indent: Int, to: Int): Long {
val previous = if (to > 0) getTask(to - 1) else null
return when {
indent == 0 || previous == null -> 0
indent == previous.getIndent() -> previous.parent
indent > previous.getIndent() -> previous.id
else -> {
var newParent = previous.parent
var currentIndex = to
for (i in 0 until previous.getIndent() - indent) {
var thisParent = newParent
while (newParent == thisParent) {
thisParent = getTask(--currentIndex).parent
}
newParent = thisParent
}
newParent
}
}
}
internal fun changeParent(task: TaskContainer, newParent: Long) { internal fun changeParent(task: TaskContainer, newParent: Long) {
val caldavTask = task.getCaldavTask() val caldavTask = task.getCaldavTask()
if (newParent == 0L) { if (newParent == 0L) {
@ -64,9 +78,8 @@ open class CaldavManualSortTaskAdapter internal constructor(private val taskDao:
caldavTask.cd_remote_parent = parentTask.remoteId caldavTask.cd_remote_parent = parentTask.remoteId
task.parent = newParent task.parent = newParent
} }
caldavDao.update(caldavTask) caldavDao.updateParent(caldavTask)
taskDao.save(task.getTask(), null) taskDao.save(task.getTask(), null)
taskDao.touch(task.id)
} }
private fun taskIsChild(source: TaskContainer, destinationIndex: Int): Boolean { private fun taskIsChild(source: TaskContainer, destinationIndex: Int): Boolean {

@ -8,36 +8,9 @@ class CaldavTaskAdapter internal constructor(taskDao: TaskDao, caldavDao: Caldav
override fun moved(from: Int, to: Int, indent: Int) { override fun moved(from: Int, to: Int, indent: Int) {
val task = getTask(from) val task = getTask(from)
val previous = if (to > 0) getTask(to - 1) else null val newParent = findNewParent(indent, to)
var newParent = task.parent if (newParent != task.parent) {
if (indent == 0) { changeParent(task, newParent)
newParent = 0
} else if (previous != null) {
when {
indent == previous.getIndent() -> {
newParent = previous.parent
}
indent > previous.getIndent() -> {
newParent = previous.id
}
indent < previous.getIndent() -> {
newParent = previous.parent
var currentIndex = to
for (i in 0 until previous.getIndent() - indent) {
var thisParent = newParent
while (newParent == thisParent) {
thisParent = getTask(--currentIndex).parent
}
newParent = thisParent
}
}
}
} }
// If nothing is changing, return
if (newParent == task.parent) {
return
}
changeParent(task, newParent)
} }
} }

@ -213,7 +213,6 @@ public class Upgrader {
Long order = iCalendar.Companion.getOrder(remoteTask); Long order = iCalendar.Companion.getOrder(remoteTask);
if (order != null) { if (order != null) {
task.setOrder(order); task.setOrder(order);
task.setRemoteOrder(order);
caldavDao.update(task); caldavDao.update(task);
} }
} }

@ -7,9 +7,11 @@ import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.helper.UUIDHelper import com.todoroo.astrid.helper.UUIDHelper
import com.todoroo.astrid.service.TaskCreator import com.todoroo.astrid.service.TaskCreator
import net.fortuna.ical4j.model.Parameter import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.parameter.RelType import net.fortuna.ical4j.model.parameter.RelType
import net.fortuna.ical4j.model.property.Geo import net.fortuna.ical4j.model.property.Geo
import net.fortuna.ical4j.model.property.RelatedTo import net.fortuna.ical4j.model.property.RelatedTo
import net.fortuna.ical4j.model.property.XProperty
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.GeoUtils.equalish import org.tasks.caldav.GeoUtils.equalish
import org.tasks.caldav.GeoUtils.toGeo import org.tasks.caldav.GeoUtils.toGeo
@ -36,10 +38,16 @@ class iCalendar @Inject constructor(
private val caldavDao: CaldavDao) { private val caldavDao: CaldavDao) {
companion object { companion object {
private const val APPLE_SORT_ORDER = "X-APPLE-SORT-ORDER"
private val IS_PARENT = { r: RelatedTo? -> private val IS_PARENT = { r: RelatedTo? ->
r!!.parameters.isEmpty || r.getParameter(Parameter.RELTYPE) === RelType.PARENT r!!.parameters.isEmpty || r.getParameter(Parameter.RELTYPE) === RelType.PARENT
} }
private val IS_APPLE_SORT_ORDER = { x: Property? ->
x?.name.equals(APPLE_SORT_ORDER, true)
}
fun fromVtodo(vtodo: String): Task? { fun fromVtodo(vtodo: String): Task? {
try { try {
val tasks = tasksFromReader(StringReader(vtodo)) val tasks = tasksFromReader(StringReader(vtodo))
@ -70,10 +78,24 @@ class iCalendar @Inject constructor(
} }
} }
val Task.order: Long? var Task.order: Long?
get() = unknownProperties get() = unknownProperties
.find { it.name?.equals("x-apple-sort-order", true) == true } .find { it.name?.equals(APPLE_SORT_ORDER, true) == true }
.let { it?.value?.toLong() } .let { it?.value?.toLongOrNull() }
set(order) {
if (order == null) {
unknownProperties.removeAll(unknownProperties.filter(IS_APPLE_SORT_ORDER))
} else {
val existingOrder = unknownProperties
.find { it.name?.equals(APPLE_SORT_ORDER, true) == true }
if (existingOrder != null) {
existingOrder.value = order.toString()
} else {
unknownProperties.add(XProperty(APPLE_SORT_ORDER, order.toString()))
}
}
}
} }
fun setPlace(taskId: Long, geo: Geo) { fun setPlace(taskId: Long, geo: Geo) {
@ -116,6 +138,7 @@ class iCalendar @Inject constructor(
fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task): ByteArray { fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task): ByteArray {
val remoteModel = CaldavConverter.toCaldav(caldavTask, task) val remoteModel = CaldavConverter.toCaldav(caldavTask, task)
remoteModel.order = caldavTask.order
val categories = remoteModel.categories val categories = remoteModel.categories
categories.clear() categories.clear()
categories.addAll(tagDataDao.getTagDataForTask(task.id).map { it.name!! }) categories.addAll(tagDataDao.getTagDataForTask(task.id).map { it.name!! })
@ -155,8 +178,7 @@ class iCalendar @Inject constructor(
caldavTask = existing caldavTask = existing
} }
CaldavConverter.apply(task, remote) CaldavConverter.apply(task, remote)
caldavTask.remoteOrder = remote.order caldavTask.order = remote.order
caldavTask.order = caldavTask.remoteOrder // TODO: remove me
val geo = remote.geoPosition val geo = remote.geoPosition
if (geo == null) { if (geo == null) {
locationDao.getActiveGeofences(task.id).forEach { locationDao.getActiveGeofences(task.id).forEach {

@ -53,12 +53,15 @@ abstract class CaldavDao {
@Update @Update
abstract fun update(caldavTask: CaldavTask) abstract fun update(caldavTask: CaldavTask)
fun update(caldavTask: SubsetCaldav) { fun updateParent(caldavTask: SubsetCaldav) {
update(caldavTask.cd_id, caldavTask.cd_remote_parent) 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)
@Query("UPDATE caldav_tasks SET cd_remote_parent = :remoteParent WHERE cd_id = :id") @Query("UPDATE caldav_tasks SET cd_remote_parent = :remoteParent WHERE cd_id = :id")
abstract fun update(id: Long, remoteParent: String?) internal abstract fun update(id: Long, remoteParent: String?)
@Update @Update
abstract fun update(tasks: Iterable<CaldavTask>) abstract fun update(tasks: Iterable<CaldavTask>)
@ -167,6 +170,20 @@ abstract class CaldavDao {
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)") + "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)")
abstract fun updateParents(calendar: String) abstract fun updateParents(calendar: String)
@Transaction
open fun move(task: TaskContainer, newParent: Long, newPosition: Long) {
val previousParent = task.parent
val caldavTask = task.caldavTask
val previousPosition = task.caldavSortOrder
if (newParent == previousParent && newPosition < previousPosition) {
shiftDown(task.caldav, newParent, newPosition, previousPosition)
} else {
shiftDown(task.caldav, newParent, newPosition)
}
caldavTask.cd_order = newPosition
update(caldavTask.cd_id, caldavTask.cd_order)
}
@Transaction @Transaction
open fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) { open fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) {
val updated = ArrayList<CaldavTask>() val updated = ArrayList<CaldavTask>()

@ -44,13 +44,6 @@ class CaldavTask {
@Transient @Transient
var order: Long? = null var order: Long? = null
@ColumnInfo(name = "cd_remote_order")
var remoteOrder: Long? = null
@ColumnInfo(name = "cd_moved")
@Transient
var moved = false
constructor() constructor()
@Ignore @Ignore
@ -72,7 +65,7 @@ class CaldavTask {
fun isDeleted() = deleted > 0 fun isDeleted() = deleted > 0
override fun toString(): String { override fun toString(): String {
return "CaldavTask(id=$id, task=$task, calendar=$calendar, `object`=$`object`, remoteId=$remoteId, etag=$etag, lastSync=$lastSync, deleted=$deleted, vtodo=$vtodo, remoteParent=$remoteParent, order=$order, remoteOrder=$remoteOrder, moved=$moved)" return "CaldavTask(id=$id, task=$task, calendar=$calendar, `object`=$`object`, remoteId=$remoteId, etag=$etag, lastSync=$lastSync, deleted=$deleted, vtodo=$vtodo, remoteParent=$remoteParent, order=$order)"
} }
companion object { companion object {

@ -197,4 +197,8 @@ public class TaskContainer {
public boolean isCollapsed() { public boolean isCollapsed() {
return task.isCollapsed(); return task.isCollapsed();
} }
public long getCaldavSortOrder() {
return indent == 0 ? primarySort : secondarySort;
}
} }

@ -433,8 +433,6 @@ public class Migrations {
@Override @Override
public void migrate(@NonNull SupportSQLiteDatabase database) { public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_order` INTEGER"); database.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_order` INTEGER");
database.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_remote_order` INTEGER");
database.execSQL("ALTER TABLE `caldav_tasks` ADD COLUMN `cd_moved` INTEGER NOT NULL DEFAULT 0");
} }
}; };

Loading…
Cancel
Save