Sync subtask expand/collapse state

pull/1360/head
Alex Baker 3 years ago
parent 99dee06c64
commit e4e37b22f1

@ -6,7 +6,9 @@ import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.tasks.caldav.iCalendar.Companion.collapsed
import org.tasks.caldav.iCalendar.Companion.getParent
import org.tasks.caldav.iCalendar.Companion.order
import org.tasks.data.TagDao
@ -23,6 +25,7 @@ import org.tasks.makers.TagMaker.TAGDATA
import org.tasks.makers.TagMaker.TASK
import org.tasks.makers.TagMaker.newTag
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.COLLAPSED
import org.tasks.makers.TaskMaker.newTask
import javax.inject.Inject
@ -143,6 +146,34 @@ class OpenTasksPropertiesTests : OpenTasksTest() {
)
}
@Test
fun readCollapsedState() = runBlocking {
val (_, list) = withVtodo(HIDE_SUBTASKS)
synchronizer.sync()
val task = caldavDao
.getTaskByRemoteId(list.uuid!!, "2822976a-b71e-4962-92e4-db7297789c20")
?.let { taskDao.fetch(it.task) }
assertTrue(task!!.isCollapsed)
}
@Test
fun pushCollapsedState() = runBlocking {
val (listId, list) = openTaskDao.insertList()
val taskId = taskDao.createNew(newTask(with(COLLAPSED, true)))
caldavDao.insert(newCaldavTask(
with(CALENDAR, list.uuid),
with(CaldavTaskMaker.TASK, taskId),
with(REMOTE_ID, "abcd")
))
synchronizer.sync()
assertTrue(openTaskDao.getTask(listId, "abcd")?.task!!.collapsed)
}
private suspend fun insertTag(task: Task, name: String) =
newTagData(with(NAME, name))
.apply { tagDataDao.createNew(this) }
@ -195,5 +226,20 @@ class OpenTasksPropertiesTests : OpenTasksTest() {
END:VTODO
END:VCALENDAR
""".trimIndent()
private val HIDE_SUBTASKS = """
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Nextcloud Tasks v0.13.6
BEGIN:VTODO
UID:2822976a-b71e-4962-92e4-db7297789c20
CREATED:20210209T104536
LAST-MODIFIED:20210209T104548
DTSTAMP:20210209T104548
SUMMARY:Parent
X-OC-HIDESUBTASKS:1
END:VTODO
END:VCALENDAR
""".trimIndent()
}
}

@ -31,6 +31,7 @@ object TaskMaker {
val PRIORITY: Property<Task, Int> = newProperty()
val PARENT: Property<Task, Long> = newProperty()
val UUID: Property<Task, String> = newProperty()
val COLLAPSED: Property<Task, Boolean> = newProperty()
private val instantiator = Instantiator { lookup: PropertyLookup<Task> ->
val task = Task()
@ -85,6 +86,7 @@ object TaskMaker {
lookup.valueOf(RECUR, null as String?)?.let {
task.setRecurrence(it, lookup.valueOf(AFTER_COMPLETE, false))
}
task.isCollapsed = lookup.valueOf(COLLAPSED, false)
task.uuid = lookup.valueOf(UUID, NO_UUID)
val creationTime = lookup.valueOf(CREATION_TIME, DateTimeUtils.newDateTime())
task.creationDate = creationTime.millis

@ -71,10 +71,15 @@ class TaskDao @Inject constructor(
suspend fun getChildren(id: Long): List<Long> = taskDao.getChildren(id)
suspend fun setCollapsed(id: Long, collapsed: Boolean) = taskDao.setCollapsed(id, collapsed)
suspend fun setCollapsed(id: Long, collapsed: Boolean) {
taskDao.setCollapsed(listOf(id), collapsed)
syncAdapters.sync()
}
suspend fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) =
taskDao.setCollapsed(preferences, filter, collapsed)
suspend fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) {
taskDao.setCollapsed(preferences, filter, collapsed)
syncAdapters.sync()
}
// --- save
// TODO: get rid of this super-hack

@ -349,6 +349,7 @@ class Task : Parcelable {
&& recurrence == original.recurrence
&& parent == original.parent
&& repeatUntil == original.repeatUntil
&& isCollapsed == original.isCollapsed
}
val isSaved: Boolean

@ -119,7 +119,6 @@ class iCalendar @Inject constructor(
suspend fun toVtodo(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task, remoteModel: Task) {
remoteModel.applyLocal(caldavTask, task)
remoteModel.order = caldavTask.order
val categories = remoteModel.categories
categories.clear()
categories.addAll(tagDataDao.getTagDataForTask(task.id).map { it.name!! })
@ -172,6 +171,8 @@ class iCalendar @Inject constructor(
companion object {
private const val APPLE_SORT_ORDER = "X-APPLE-SORT-ORDER"
private const val OC_HIDESUBTASKS = "X-OC-HIDESUBTASKS"
private const val HIDE_SUBTASKS = "1"
private val IS_PARENT = { r: RelatedTo ->
r.parameters.getParameter<RelType>(Parameter.RELTYPE).let {
it === RelType.PARENT || it == null || it.value.isNullOrBlank()
@ -182,6 +183,10 @@ class iCalendar @Inject constructor(
x?.name.equals(APPLE_SORT_ORDER, true)
}
private val IS_OC_HIDESUBTASKS = { x: Property? ->
x?.name.equals(OC_HIDESUBTASKS, true)
}
fun Due?.apply(task: com.todoroo.astrid.data.Task) {
task.dueDate = when (this?.date) {
null -> 0
@ -244,21 +249,28 @@ class iCalendar @Inject constructor(
}
var Task.order: Long?
get() = unknownProperties
.find { it.name?.equals(APPLE_SORT_ORDER, true) == true }
.let { it?.value?.toLongOrNull() }
get() = unknownProperties.find(IS_APPLE_SORT_ORDER).let { it?.value?.toLongOrNull() }
set(order) {
if (order == null) {
unknownProperties.removeAll(unknownProperties.filter(IS_APPLE_SORT_ORDER))
unknownProperties.removeIf(IS_APPLE_SORT_ORDER)
} else {
val existingOrder = unknownProperties
.find { it.name?.equals(APPLE_SORT_ORDER, true) == true }
unknownProperties
.find(IS_APPLE_SORT_ORDER)
?.let { it.value = order.toString() }
?: unknownProperties.add(XProperty(APPLE_SORT_ORDER, order.toString()))
}
}
if (existingOrder != null) {
existingOrder.value = order.toString()
} else {
unknownProperties.add(XProperty(APPLE_SORT_ORDER, order.toString()))
}
var Task.collapsed: Boolean
get() = unknownProperties.find(IS_OC_HIDESUBTASKS).let { it?.value == HIDE_SUBTASKS }
set(collapsed) {
if (collapsed) {
unknownProperties
.find(IS_OC_HIDESUBTASKS)
?.let { it.value = HIDE_SUBTASKS }
?: unknownProperties.add(XProperty(OC_HIDESUBTASKS, HIDE_SUBTASKS))
} else {
unknownProperties.removeIf(IS_OC_HIDESUBTASKS)
}
}
@ -288,6 +300,7 @@ class iCalendar @Inject constructor(
setRecurrence(remote.rRule?.recur)
remote.due.apply(this)
remote.dtStart.apply(this)
isCollapsed = remote.collapsed
}
fun Task.applyLocal(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task) {
@ -347,6 +360,8 @@ class iCalendar @Inject constructor(
else -> if (priority > 5) min(9, priority) else 9
}
setParent(if (task.parent == 0L) null else caldavTask.remoteParent)
order = caldavTask.order
collapsed = task.isCollapsed
}
private fun getDate(timestamp: Long): Date {

@ -6,7 +6,7 @@ import androidx.sqlite.db.SimpleSQLiteQuery
import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Field
import com.todoroo.andlib.sql.Functions
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.dao.Database
@ -24,7 +24,7 @@ import timber.log.Timber
abstract class TaskDao(private val database: Database) {
@Query("SELECT * FROM tasks WHERE completed = 0 AND deleted = 0 AND (hideUntil > :now OR dueDate > :now)")
internal abstract suspend fun needsRefresh(now: Long = DateUtilities.now()): List<Task>
internal abstract suspend fun needsRefresh(now: Long = now()): List<Task>
@Query("SELECT * FROM tasks WHERE _id = :id LIMIT 1")
abstract suspend fun fetch(id: Long): Task?
@ -101,14 +101,14 @@ abstract class TaskDao(private val database: Database) {
open suspend fun fetchTasks(callback: suspend (SubtaskInfo) -> List<String>, subtasks: SubtaskInfo): List<TaskContainer> =
database.withTransaction {
val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0
val start = if (BuildConfig.DEBUG) now() else 0
val queries = callback(subtasks)
val last = queries.size - 1
for (i in 0 until last) {
query(SimpleSQLiteQuery(queries[i]))
}
val result = fetchTasks(SimpleSQLiteQuery(queries[last]))
Timber.v("%sms: %s", DateUtilities.now() - start, queries.joinToString(";\n"))
Timber.v("%sms: %s", now() - start, queries.joinToString(";\n"))
result
}
@ -169,25 +169,22 @@ FROM recursive_tasks
""")
abstract suspend fun getChildren(ids: List<Long>): List<Long>
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id = :id")
abstract suspend fun setCollapsed(id: Long, collapsed: Boolean)
open suspend fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) {
internal suspend fun setCollapsed(preferences: Preferences, filter: Filter, collapsed: Boolean) {
fetchTasks(preferences, filter)
.filter(TaskContainer::hasChildren)
.map(TaskContainer::getId)
.eachChunk { collapse(it, collapsed) }
.eachChunk { setCollapsed(it, collapsed) }
}
@Query("UPDATE tasks SET collapsed = :collapsed WHERE _id IN (:ids)")
internal abstract suspend fun collapse(ids: List<Long>, collapsed: Boolean)
@Query("UPDATE tasks SET collapsed = :collapsed, modified = :now WHERE _id IN (:ids)")
internal abstract suspend fun setCollapsed(ids: List<Long>, collapsed: Boolean, now: Long = now())
@Insert
abstract suspend fun insert(task: Task): Long
suspend fun update(task: Task, original: Task? = null): Boolean {
if (!task.insignificantChange(original)) {
task.modificationDate = DateUtilities.now()
task.modificationDate = now()
}
if (task.dueDate != original?.dueDate) {
task.reminderSnooze = 0
@ -201,7 +198,7 @@ FROM recursive_tasks
suspend fun createNew(task: Task): Long {
task.id = NO_ID
if (task.creationDate == 0L) {
task.creationDate = DateUtilities.now()
task.creationDate = now()
}
if (Task.isUuidEmpty(task.remoteId)) {
task.remoteId = UUIDHelper.newUUID()
@ -216,9 +213,9 @@ FROM recursive_tasks
suspend fun count(filter: Filter): Int {
val query = getQuery(filter.sqlQuery, Field.COUNT)
val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0
val start = if (BuildConfig.DEBUG) now() else 0
val count = count(query)
Timber.v("%sms: %s", DateUtilities.now() - start, query.sql)
Timber.v("%sms: %s", now() - start, query.sql)
return count
}
@ -226,9 +223,9 @@ FROM recursive_tasks
suspend fun fetchFiltered(queryTemplate: String): List<Task> {
val query = getQuery(queryTemplate, Task.FIELDS)
val start = if (BuildConfig.DEBUG) DateUtilities.now() else 0
val start = if (BuildConfig.DEBUG) now() else 0
val tasks = fetchTasks(query)
Timber.v("%sms: %s", DateUtilities.now() - start, query.sql)
Timber.v("%sms: %s", now() - start, query.sql)
return tasks.map(TaskContainer::getTask)
}

@ -29,6 +29,7 @@ import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.service.TaskCompleter
import com.todoroo.astrid.service.TaskCreator

@ -2,6 +2,7 @@ package org.tasks.widget
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.service.TaskCompleter
import dagger.hilt.android.AndroidEntryPoint
@ -9,7 +10,6 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.data.TaskDao
import org.tasks.dialogs.BaseDateTimePicker.OnDismissHandler
import org.tasks.dialogs.DateTimePicker.Companion.newDateTimePicker
import org.tasks.injection.InjectingAppCompatActivity

Loading…
Cancel
Save