1 parent
109eda82b2
commit
2dfc676b8f
7 files changed
+219
-7
No files matched your search
@@ -55,14 +55,47 @@ class AndroidTasksDataSource @Inject constructor(
|
||||
// --- reads ----------------------------------------------------------------
|
||||
|
||||
override fun taskLists(): List<TaskList> {
|
||||
val uri = TasksContract.listsUri(authority())
|
||||
val sort = "${Lists.ACCOUNT_NAME}, ${Lists.NAME}"
|
||||
return resolver.query(uri, TaskProjections.LISTS, null, null, sort)?.use { c ->
|
||||
val authority = authority()
|
||||
val lists = resolver.query(TasksContract.listsUri(authority), TaskProjections.LISTS, null, null, null)?.use { c ->
|
||||
val reader = CursorColumnReader(c)
|
||||
buildList { while (c.moveToNext()) add(TaskMapper.taskList(reader)) }
|
||||
} ?: emptyList()
|
||||
buildList<TaskList> { while (c.moveToNext()) add(TaskMapper.taskList(reader)) }
|
||||
} ?: return emptyList()
|
||||
val order = synchronized(listOrderLock) {
|
||||
val stored = storedListOrder(authority)
|
||||
val known = lists.mapTo(HashSet()) { it.id }
|
||||
// A list gone from the provider takes its position along, as its id may be
|
||||
// reused — but not on an empty answer, which is as likely a provider mid-update.
|
||||
if (lists.isNotEmpty() && stored.keys.any { it !in known }) {
|
||||
stored.filterKeys { it in known }.also { storeListOrder(authority, it) }
|
||||
} else {
|
||||
stored
|
||||
}
|
||||
}
|
||||
return ListOrder.sorted(lists, order)
|
||||
}
|
||||
|
||||
/** The order Agendula keeps for [authority]'s lists. */
|
||||
internal fun storedListOrder(authority: String = authority()): Map<Long, Int> =
|
||||
ListOrder.decode(listOrder.getString(authority, null))
|
||||
|
||||
private val listOrder get() = context.getSharedPreferences(LIST_ORDER, Context.MODE_PRIVATE)
|
||||
|
||||
private fun storeListOrder(authority: String, order: Map<Long, Int>) {
|
||||
listOrder.edit().putString(authority, ListOrder.encode(order)).commit()
|
||||
}
|
||||
|
||||
/** Kept by Agendula, as the provider's lists table has no order of its own. */
|
||||
override fun reorderLists(listIds: List<Long>) {
|
||||
val authority = authority()
|
||||
synchronized(listOrderLock) {
|
||||
storeListOrder(authority, ListOrder.reordered(taskLists().map { it.id }, listIds))
|
||||
}
|
||||
// Wakes the observers on the lists without asking any sync adapter to upload: nothing changed in the provider.
|
||||
runCatching { resolver.notifyChange(TasksContract.listsUri(authority), null, 0) }
|
||||
}
|
||||
|
||||
private val listOrderLock = Any()
|
||||
|
||||
override fun tasks(query: TaskQuery): List<Task> {
|
||||
val clauses = mutableListOf<String>()
|
||||
val args = mutableListOf<String>()
|
||||
@@ -787,6 +820,8 @@ class AndroidTasksDataSource @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private const val LIST_ORDER = "provider_list_order"
|
||||
|
||||
/** An instance row of the task, or of an override standing in for one of its occurrences. */
|
||||
private const val OCCURRENCE_OF = "${Instances.TASK_ID} = ? OR ${Tasks.ORIGINAL_INSTANCE_ID} = ?"
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package de.jeanlucmakiola.agendula.data.tasks
|
||||
|
||||
import de.jeanlucmakiola.agendula.domain.TaskList
|
||||
|
||||
/**
|
||||
* Agendula's own order for an external provider's lists, which the provider has
|
||||
* no column for. Stored as `id=position` pairs. A list without one counts as
|
||||
* position 0, as a new list does in Agendula's own store.
|
||||
*/
|
||||
object ListOrder {
|
||||
|
||||
fun decode(raw: String?): Map<Long, Int> = raw.orEmpty().split(',').mapNotNull { pair ->
|
||||
val (id, position) = pair.split('=').takeIf { it.size == 2 } ?: return@mapNotNull null
|
||||
id.toLongOrNull()?.let { key -> position.toIntOrNull()?.let { key to it } }
|
||||
}.toMap()
|
||||
|
||||
fun encode(order: Map<Long, Int>): String = order.entries.joinToString(",") { "${it.key}=${it.value}" }
|
||||
|
||||
/**
|
||||
* Positions for every list in [shown] — the lists in their current order —
|
||||
* after one account's [listIds] were rearranged: those take each other's
|
||||
* slots in their new order, and every other list keeps its own.
|
||||
*/
|
||||
fun reordered(shown: List<Long>, listIds: List<Long>): Map<Long, Int> {
|
||||
val moved = listIds.iterator()
|
||||
val moving = listIds.toSet()
|
||||
val result = shown.map { if (it in moving && moved.hasNext()) moved.next() else it }.toMutableList()
|
||||
moved.forEachRemaining { if (it !in result) result += it }
|
||||
return result.withIndex().associate { it.value to it.index }
|
||||
}
|
||||
|
||||
fun sorted(lists: List<TaskList>, order: Map<Long, Int>): List<TaskList> = lists.sortedWith(
|
||||
compareBy<TaskList>({ it.accountName.lowercase() }, { order[it.id] ?: 0 }, { it.name.lowercase() })
|
||||
.thenBy { it.name },
|
||||
)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ interface TaskListDao {
|
||||
"""
|
||||
SELECT l.*, a.display_name AS account_display_name, a.label AS account_label
|
||||
FROM task_lists l LEFT JOIN accounts a ON a.id = l.account_id
|
||||
ORDER BY a.display_name, l.sort_order, l.name
|
||||
ORDER BY a.display_name COLLATE NOCASE, l.sort_order, l.name COLLATE NOCASE, l.name
|
||||
"""
|
||||
)
|
||||
fun lists(): List<TaskListRow>
|
||||
@@ -32,6 +32,10 @@ interface TaskListDao {
|
||||
)
|
||||
fun list(listId: Long): TaskListRow?
|
||||
|
||||
/** The highest position among device-only lists, -1 when there are none. */
|
||||
@Query("SELECT COALESCE(MAX(sort_order), -1) FROM task_lists WHERE account_id IS NULL")
|
||||
fun lastLocalSortOrder(): Int
|
||||
|
||||
@Query("SELECT * FROM task_lists WHERE id = :listId")
|
||||
fun entity(listId: Long): TaskListEntity?
|
||||
|
||||
|
||||
@@ -159,7 +159,9 @@ class ExternalImport @Inject constructor(
|
||||
val taskDao = database.tasks()
|
||||
val alarmDao = database.alarms()
|
||||
|
||||
val listIds = snapshot.lists.associate { list ->
|
||||
// After the device-only lists already there, which the copies join.
|
||||
val after = listDao.lastLocalSortOrder() + 1
|
||||
val listIds = snapshot.lists.withIndex().associate { (position, list) ->
|
||||
list.id to listDao.insert(
|
||||
TaskListEntity(
|
||||
name = list.name,
|
||||
@@ -168,6 +170,7 @@ class ExternalImport @Inject constructor(
|
||||
isVisible = list.isVisible,
|
||||
isSynced = false,
|
||||
owner = list.owner,
|
||||
sortOrder = after + position,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user