Merge branch 'consolidate_lists'

pull/2146/head
Alex Baker 1 year ago
commit 417f5ac224

@ -54,8 +54,8 @@ android {
defaultConfig {
testApplicationId = "org.tasks.test"
applicationId = "org.tasks"
versionCode = 130102
versionName = "13.1.2"
versionCode = 130200
versionName = "13.2"
targetSdk = Versions.targetSdk
minSdk = Versions.minSdk
testInstrumentationRunner = "org.tasks.TestRunner"

File diff suppressed because it is too large Load Diff

@ -224,7 +224,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
private fun checkOrder(dateTime: DateTime, index: Int) = checkOrder(dateTime.toAppleEpoch(), index)
private fun checkOrder(order: Long?, index: Int) = runBlocking {
val sortOrder = caldavDao.getTask(adapter.getTask(index).id)!!.order
val sortOrder = taskDao.fetch(adapter.getTask(index).id)!!.order
if (order == null) {
assertNull(sortOrder)
} else {

@ -202,14 +202,6 @@ class CaldavTaskAdapterTest : InjectingTestCase() {
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.parent)
}
caldavTask.id = caldavDao.insert(caldavTask)
t.caldavTask = caldavTask.toSubset()
}
private fun CaldavTask.toSubset(): SubsetCaldav {
val result = SubsetCaldav()
result.cd_id = id
result.cd_calendar = calendar
result.cd_remote_parent = remoteParent
return result
t.caldavTask = caldavTask
}
}

@ -19,12 +19,11 @@ import org.tasks.data.TaskContainer
import org.tasks.data.TaskListQuery.getQuery
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskListMaker.REMOTE_ID
import org.tasks.makers.GoogleTaskListMaker.newGoogleTaskList
import org.tasks.makers.GoogleTaskMaker
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.CaldavCalendarMaker
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.newTask
import org.tasks.preferences.Preferences
@ -41,7 +40,7 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() {
private lateinit var adapter: GoogleTaskManualSortAdapter
private val tasks = ArrayList<TaskContainer>()
private val filter = GtasksFilter(newGoogleTaskList(with(REMOTE_ID, "1234")))
private val filter = GtasksFilter(newCaldavCalendar(with(CaldavCalendarMaker.UUID, "1234")))
private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position]
@ -427,22 +426,22 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() {
}
private fun checkOrder(order: Long, index: Int, parent: Long = 0) = runBlocking {
val googleTask = googleTaskDao.getByTaskId(adapter.getTask(index).id)!!
val googleTask = taskDao.fetch(adapter.getTask(index).id)!!
assertEquals(order, googleTask.order)
assertEquals(parent, googleTask.parent)
}
private fun addTask(vararg properties: PropertyValue<in Task?, *>): Long = runBlocking {
val task = newTask(*properties)
val parent = task.parent
task.parent = 0
taskDao.createNew(task)
googleTaskDao.insertAndShift(
newGoogleTask(
with(TASK, task.id),
with(LIST, "1234"),
with(GoogleTaskMaker.PARENT, parent)),
false)
task,
newCaldavTask(
with(TASK, task.id),
with(CALENDAR, "1234"),
),
false
)
task.id
}
}

@ -11,14 +11,15 @@ import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.tasks.LocalBroadcastManager
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GtaskListMaker.ID
import org.tasks.makers.GtaskListMaker.NAME
import org.tasks.makers.GtaskListMaker.REMOTE_ID
import org.tasks.makers.GtaskListMaker.newGtaskList
import org.tasks.makers.CaldavCalendarMaker.ID
import org.tasks.makers.CaldavCalendarMaker.NAME
import org.tasks.makers.CaldavCalendarMaker.UUID
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.RemoteGtaskListMaker
import org.tasks.makers.RemoteGtaskListMaker.newRemoteList
import javax.inject.Inject
@ -29,6 +30,7 @@ class GtasksListServiceTest : InjectingTestCase() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var caldavDao: CaldavDao
private lateinit var gtasksListService: GtasksListService
@ -44,13 +46,13 @@ class GtasksListServiceTest : InjectingTestCase() {
newRemoteList(
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.NAME, "Default")))
assertEquals(
newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"), with(NAME, "Default")),
newCaldavCalendar(with(ID, 1L), with(UUID, "1"), with(NAME, "Default")),
googleTaskListDao.getById(1L))
}
@Test
fun testGetListByRemoteId() = runBlocking {
val list = newGtaskList(with(REMOTE_ID, "1"))
val list = newCaldavCalendar(with(UUID, "1"))
list.id = googleTaskListDao.insertOrReplace(list)
assertEquals(list, googleTaskListDao.getByRemoteId("1"))
}
@ -62,22 +64,23 @@ class GtasksListServiceTest : InjectingTestCase() {
@Test
fun testDeleteMissingList() = runBlocking {
googleTaskListDao.insertOrReplace(newGtaskList(with(ID, 1L), with(REMOTE_ID, "1")))
googleTaskListDao.insertOrReplace(newCaldavCalendar(with(ID, 1L), with(UUID, "1")))
val taskList = newRemoteList(with(RemoteGtaskListMaker.REMOTE_ID, "2"))
setLists(taskList)
assertEquals(
listOf(newGtaskList(with(ID, 2L), with(REMOTE_ID, "2"))),
listOf(newCaldavCalendar(with(ID, 2L), with(UUID, "2"), with(NAME, "Default"))),
googleTaskListDao.getLists("account"))
}
@Test
fun testUpdateListName() = runBlocking {
googleTaskListDao.insertOrReplace(
newGtaskList(with(ID, 1L), with(REMOTE_ID, "1"), with(NAME, "oldName")))
newCaldavCalendar(with(ID, 1L), with(UUID, "1"), with(NAME, "oldName"))
)
setLists(
newRemoteList(
with(RemoteGtaskListMaker.REMOTE_ID, "1"), with(RemoteGtaskListMaker.NAME, "newName")))
assertEquals("newName", googleTaskListDao.getById(1)!!.title)
assertEquals("newName", googleTaskListDao.getById(1)!!.name)
}
@Test
@ -87,8 +90,11 @@ class GtasksListServiceTest : InjectingTestCase() {
}
private suspend fun setLists(vararg list: TaskList) {
val account = GoogleTaskAccount("account")
googleTaskListDao.insert(account)
val account = CaldavAccount().apply {
username = "account"
uuid = "account"
}
caldavDao.insert(account)
gtasksListService.updateLists(account, listOf(*list))
}
}

@ -1,81 +0,0 @@
/*
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.gtasks
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class GtasksMetadataServiceTest : InjectingTestCase() {
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var googleTaskDao: GoogleTaskDao
private var task: Task? = null
private var metadata: GoogleTask? = null
@Test
fun testMetadataFound() = runBlocking {
givenTask(taskWithMetadata(null))
whenSearchForMetadata()
thenExpectMetadataFound()
}
@Test
fun testMetadataDoesntExist() = runBlocking {
givenTask(taskWithoutMetadata())
whenSearchForMetadata()
thenExpectNoMetadataFound()
}
private fun thenExpectNoMetadataFound() {
assertNull(metadata)
}
private fun thenExpectMetadataFound() {
assertNotNull(metadata)
}
// --- helpers
private suspend fun whenSearchForMetadata() {
metadata = googleTaskDao.getByTaskId(task!!.id)
}
private suspend fun taskWithMetadata(id: String?): Task {
val task = Task()
task.title = "cats"
taskDao.createNew(task)
val metadata = GoogleTask(task.id, "")
if (id != null) {
metadata.remoteId = id
}
metadata.task = task.id
googleTaskDao.insert(metadata)
return task
}
private fun givenTask(taskToTest: Task) {
task = taskToTest
}
private suspend fun taskWithoutMetadata(): Task {
val task = Task()
task.title = "dogs"
taskDao.createNew(task)
return task
}
}

@ -12,9 +12,6 @@ import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskMaker
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.RECUR
@ -96,42 +93,6 @@ class TaskDeleterTest : InjectingTestCase() {
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun dontClearCompletedGoogleTaskWithRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask(with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1")))
val child = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
googleTaskDao.insert(newGoogleTask(with(TASK, child), with(GoogleTaskMaker.PARENT, parent)))
clearCompleted()
assertFalse(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearCompletedGoogleTaskWithNonRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask())
val child = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
googleTaskDao.insert(newGoogleTask(with(TASK, child), with(GoogleTaskMaker.PARENT, parent)))
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
@Test
fun clearCompletedGoogleTaskWithCompletedRecurringParent() = runBlocking {
val parent = taskDao.createNew(newTask(
with(RECUR, "RRULE:FREQ=DAILY;INTERVAL=1"),
with(COMPLETION_TIME, DateTime())
))
val child = taskDao.createNew(newTask(with(COMPLETION_TIME, DateTime())))
googleTaskDao.insert(newGoogleTask(with(TASK, child), with(GoogleTaskMaker.PARENT, parent)))
clearCompleted()
assertTrue(taskDao.fetch(child)!!.isDeleted)
}
private suspend fun clearCompleted() =
taskDeleter.clearCompleted(getMyTasksFilter(context.resources))
}

@ -9,28 +9,29 @@ import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.jobs.WorkManager
import org.tasks.makers.CaldavCalendarMaker.UUID
import org.tasks.makers.CaldavAccountMaker
import org.tasks.makers.CaldavAccountMaker.ACCOUNT_TYPE
import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.CaldavCalendarMaker
import org.tasks.makers.CaldavCalendarMaker.ACCOUNT
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.PARENT
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.GtaskListMaker
import org.tasks.makers.GtaskListMaker.newGtaskList
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.ID
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.newTask
import javax.inject.Inject
@ -44,18 +45,28 @@ class TaskMoverTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskMover: TaskMover
@Before
fun setup() {
runBlocking {
caldavDao.insert(newCaldavCalendar(with(CaldavCalendarMaker.UUID, "1"), with(ACCOUNT, "account1")))
caldavDao.insert(newCaldavCalendar(with(CaldavCalendarMaker.UUID, "2"), with(ACCOUNT, "account2")))
}
}
@Test
fun moveBetweenGoogleTaskLists() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1)
assertEquals("2", googleTaskDao.getByTaskId(1)!!.listId)
assertEquals("2", googleTaskDao.getByTaskId(1)?.calendar)
}
@Test
fun deleteGoogleTaskAfterMove() = runBlocking {
createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1)
val deleted = googleTaskDao.getDeletedByTaskId(1)
assertEquals(1, deleted.size.toLong())
@ -65,24 +76,25 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveChildrenBetweenGoogleTaskLists() = runBlocking {
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1)
val deleted = googleTaskDao.getDeletedByTaskId(2)
assertEquals(1, deleted.size.toLong())
assertEquals(2, deleted[0].task)
assertTrue(deleted[0].deleted > 0)
val task = googleTaskDao.getByTaskId(2)!!
assertEquals(1, task.parent)
assertEquals("2", task.listId)
assertEquals(1L, taskDao.fetch(2)?.parent)
assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
}
@Test
fun moveBetweenCaldavList() = runBlocking {
createTasks(1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar)
}
@ -90,8 +102,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun deleteCaldavTaskAfterMove() = runBlocking {
createTasks(1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1)
val deleted = caldavDao.getMoved("1")
assertEquals(1, deleted.size.toLong())
@ -104,18 +115,17 @@ class TaskMoverTest : InjectingTestCase() {
createTasks(1)
createSubtask(2, 1)
createSubtask(3, 2)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(
listOf(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(TASK, 3L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "b"))))
moveToCaldavList("2", 1)
@ -128,13 +138,16 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveGoogleTaskChildrenToCaldav() = runBlocking {
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_CALDAV)
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
moveToCaldavList("1", 1)
val task = caldavDao.getTask(2)
assertEquals("1", task!!.calendar)
assertEquals(1, taskDao.fetch(2)!!.parent)
assertEquals(1L, taskDao.fetch(2)?.parent)
}
@Test
@ -143,8 +156,7 @@ class TaskMoverTest : InjectingTestCase() {
createSubtask(2, 1)
createSubtask(3, 2)
moveToGoogleTasks("1", 1)
assertEquals(1, googleTaskDao.getByTaskId(3)!!.parent)
assertEquals(0, taskDao.fetch(3)!!.parent)
assertEquals(1L, taskDao.fetch(3)?.parent)
}
@Test
@ -152,7 +164,7 @@ class TaskMoverTest : InjectingTestCase() {
createTasks(1)
createSubtask(2, 1)
moveToGoogleTasks("1", 2)
assertEquals(0, taskDao.fetch(2)!!.parent)
assertEquals(0L, taskDao.fetch(2)?.parent)
}
@Test
@ -171,43 +183,43 @@ class TaskMoverTest : InjectingTestCase() {
caldavDao.insert(
listOf(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 3L),
with(TASK, 3L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "b"))))
moveToGoogleTasks("1", 1)
val task = googleTaskDao.getByTaskId(3L)!!
assertEquals(1, task.parent)
val task = taskDao.fetch(3L)
assertEquals(1L, task?.parent)
}
@Test
fun moveGoogleTaskChildWithoutParent() = runBlocking {
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
moveToGoogleTasks("2", 2)
val task = googleTaskDao.getByTaskId(2)!!
assertEquals(0L, task.parent)
assertEquals("2", task.listId)
assertEquals(0L, taskDao.fetch(2)?.parent)
assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
}
@Test
fun moveCaldavChildWithoutParent() = runBlocking {
createTasks(1)
createSubtask(2, 1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(
listOf(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "a"))))
moveToCaldavList("2", 2)
@ -218,17 +230,19 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveGoogleTaskToCaldav() = runBlocking {
createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar)
}
@Test
fun moveCaldavToGoogleTask() = runBlocking {
setAccountType("account1", TYPE_CALDAV)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1)
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1)
assertEquals("2", googleTaskDao.getByTaskId(1L)!!.listId)
assertEquals("2", googleTaskDao.getByTaskId(1L)?.calendar)
}
@Test
@ -237,14 +251,15 @@ class TaskMoverTest : InjectingTestCase() {
createSubtask(2, 1)
createSubtask(3, 2)
moveToCaldavList("1", 1)
assertEquals("1", caldavDao.getTask(3)!!.calendar)
assertEquals(2, taskDao.fetch(3)!!.parent)
assertEquals("1", caldavDao.getTask(3)?.calendar)
assertEquals(2L, taskDao.fetch(3)?.parent)
}
@Test
fun moveToSameGoogleTaskListIsNoop() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToGoogleTasks("1", 1)
assertTrue(googleTaskDao.getDeletedByTaskId(1).isEmpty())
assertEquals(1, googleTaskDao.getAllByTaskId(1).size.toLong())
@ -253,7 +268,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun moveToSameCaldavListIsNoop() = runBlocking {
createTasks(1)
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("1", 1)
assertTrue(caldavDao.getMoved("1").isEmpty())
assertEquals(1, caldavDao.getTasks(1).size.toLong())
@ -261,9 +276,10 @@ class TaskMoverTest : InjectingTestCase() {
@Test
fun dontDuplicateWhenParentAndChildGoogleTaskMoved() = runBlocking {
createTasks(1, 2)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
createTasks(1)
createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1, 2)
assertEquals(1, googleTaskDao.getAllByTaskId(2).filter { it.deleted == 0L }.size)
}
@ -272,13 +288,12 @@ class TaskMoverTest : InjectingTestCase() {
fun dontDuplicateWhenParentAndChildCaldavMoved() = runBlocking {
createTasks(1)
createSubtask(2, 1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert(
listOf(
newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask(
with(CaldavTaskMaker.TASK, 2L),
with(TASK, 2L),
with(CALENDAR, "1"),
with(REMOTE_PARENT, "a"))))
moveToCaldavList("2", 1, 2)
@ -292,14 +307,18 @@ class TaskMoverTest : InjectingTestCase() {
}
private suspend fun createSubtask(id: Long, parent: Long) {
taskDao.createNew(newTask(with(ID, id), with(TaskMaker.PARENT, parent)))
taskDao.createNew(newTask(with(ID, id), with(PARENT, parent)))
}
private suspend fun moveToGoogleTasks(list: String, vararg tasks: Long) {
taskMover.move(tasks.toList(), GtasksFilter(newGtaskList(with(GtaskListMaker.REMOTE_ID, list))))
taskMover.move(tasks.toList(), GtasksFilter(newCaldavCalendar(with(CaldavCalendarMaker.UUID, list))))
}
private suspend fun moveToCaldavList(calendar: String, vararg tasks: Long) {
taskMover.move(tasks.toList(), CaldavFilter(CaldavCalendar(name = "", uuid = calendar)))
}
private suspend fun setAccountType(account: String, type: Int) {
caldavDao.insert(newCaldavAccount(with(CaldavAccountMaker.UUID, account), with(ACCOUNT_TYPE, type)))
}
}

@ -105,7 +105,7 @@ class CaldavDaoShiftTests : InjectingTestCase() {
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
assertNull(caldavDao.getTasks(tasks[0].id)[0].order)
assertNull(taskDao.fetch(tasks[0].id)!!.order)
}
@Test
@ -116,7 +116,7 @@ class CaldavDaoShiftTests : InjectingTestCase() {
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
assertNull(caldavDao.getTasks(tasks[0].id)[0].order)
assertNull(taskDao.fetch(tasks[0].id)!!.order)
}
@Test
@ -134,10 +134,11 @@ class CaldavDaoShiftTests : InjectingTestCase() {
}
private suspend fun checkOrder(dateTime: DateTime?, task: TaskContainer) {
val order = taskDao.fetch(task.id)!!.order
if (dateTime == null) {
assertNull(caldavDao.getTask(task.id)!!.order)
assertNull(order)
} else {
assertEquals(dateTime.toAppleEpoch(), caldavDao.getTask(task.id)!!.order)
assertEquals(dateTime.toAppleEpoch(), order)
}
}
@ -153,14 +154,6 @@ class CaldavDaoShiftTests : InjectingTestCase() {
caldavTask.remoteParent = caldavDao.getRemoteIdForTask(task.parent)
}
caldavTask.id = caldavDao.insert(caldavTask)
t.caldavTask = caldavTask.toSubset()
}
private fun CaldavTask.toSubset(): SubsetCaldav {
val result = SubsetCaldav()
result.cd_id = id
result.cd_calendar = calendar
result.cd_remote_parent = remoteParent
return result
t.caldavTask = caldavTask
}
}

@ -99,7 +99,7 @@ class CaldavDaoTests : InjectingTestCase() {
private suspend fun checkOrder(dateTime: DateTime, task: Long) = checkOrder(dateTime.toAppleEpoch(), task)
private suspend fun checkOrder(order: Long?, task: Long) {
val sortOrder = caldavDao.getTask(task)!!.order
val sortOrder = taskDao.fetch(task)!!.order
if (order == null) {
assertNull(sortOrder)
} else {

@ -9,15 +9,17 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Before
import org.junit.Test
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.PARENT
import org.tasks.makers.GoogleTaskMaker.REMOTE_ID
import org.tasks.makers.GoogleTaskMaker.REMOTE_PARENT
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.GtaskListMaker.newGtaskList
import org.tasks.makers.CaldavAccountMaker.ACCOUNT_TYPE
import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker.newTask
import javax.inject.Inject
@ -27,140 +29,135 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var caldavDao: CaldavDao
@Before
override fun setUp() {
super.setUp()
runBlocking {
googleTaskListDao.insert(newGtaskList())
caldavDao.insert(newCaldavAccount(with(ACCOUNT_TYPE, TYPE_GOOGLE_TASKS)))
caldavDao.insert(newCaldavCalendar())
}
}
@Test
fun insertAtTopOfEmptyList() = runBlocking {
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("1")
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
assertEquals(1, tasks.size.toLong())
val task = tasks[0]
assertEquals("1234", task.remoteId)
assertEquals(0, task.order)
assertEquals("1234", googleTaskDao.getByTaskId(task.id)?.remoteId)
assertEquals(0L, task.order)
}
@Test
fun insertAtBottomOfEmptyList() = runBlocking {
insertBottom(newGoogleTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("1")
insertBottom(newCaldavTask(with(REMOTE_ID, "1234")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
assertEquals(1, tasks.size.toLong())
val task = tasks[0]
assertEquals("1234", task.remoteId)
assertEquals(0, task.order)
assertEquals("1234", googleTaskDao.getByTaskId(task.id)?.remoteId)
assertEquals(0L, task.order)
}
@Test
fun getPreviousIsNullForTopTask() = runBlocking {
insert(newGoogleTask())
insert(newCaldavTask())
assertNull(googleTaskDao.getPrevious("1", 0, 0))
}
@Test
fun getPrevious() = runBlocking {
insertTop(newGoogleTask())
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
assertEquals("1234", googleTaskDao.getPrevious("1", 0, 1))
insertTop(newCaldavTask())
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
assertEquals("1234", googleTaskDao.getPrevious("calendar", 0, 1))
}
@Test
fun insertAtTopOfList() = runBlocking {
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
insertTop(newGoogleTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("1")
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
insertTop(newCaldavTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
assertEquals(2, tasks.size.toLong())
val top = tasks[0]
assertEquals("5678", top.remoteId)
assertEquals(0, top.order)
assertEquals("5678", googleTaskDao.getByTaskId(top.id)?.remoteId)
assertEquals(0L, top.order)
}
@Test
fun insertAtTopOfListShiftsExisting() = runBlocking {
insertTop(newGoogleTask(with(REMOTE_ID, "1234")))
insertTop(newGoogleTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("1")
insertTop(newCaldavTask(with(REMOTE_ID, "1234")))
insertTop(newCaldavTask(with(REMOTE_ID, "5678")))
val tasks = googleTaskDao.getByLocalOrder("calendar")
assertEquals(2, tasks.size.toLong())
val bottom = tasks[1]
assertEquals("1234", bottom.remoteId)
assertEquals(1, bottom.order)
assertEquals("1234", googleTaskDao.getByTaskId(bottom.id)?.remoteId)
assertEquals(1L, bottom.order)
}
@Test
fun getTaskFromRemoteId() = runBlocking {
insert(newGoogleTask(with(REMOTE_ID, "1234")))
insert(newCaldavTask(with(REMOTE_ID, "1234")))
assertEquals(1L, googleTaskDao.getTask("1234"))
}
@Test
fun getRemoteIdForTask() = runBlocking {
insert(newGoogleTask(with(REMOTE_ID, "1234")))
insert(newCaldavTask(with(REMOTE_ID, "1234")))
assertEquals("1234", googleTaskDao.getRemoteId(1L))
}
@Test
fun moveDownInList() = runBlocking {
insert(newGoogleTask(with(REMOTE_ID, "1")))
insert(newGoogleTask(with(REMOTE_ID, "2")))
insert(newGoogleTask(with(REMOTE_ID, "3")))
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
val two = getByRemoteId("2")
googleTaskDao.move(two, 0, 0)
assertEquals(0, googleTaskDao.getByRemoteId("2")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("1")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("3")!!.order)
googleTaskDao.move(taskDao.fetch(two.task)!!, "calendar", 0, 0)
assertEquals(0L, getOrder("2"))
assertEquals(1L, getOrder("1"))
assertEquals(2L, getOrder("3"))
}
@Test
fun moveUpInList() = runBlocking {
insert(newGoogleTask(with(REMOTE_ID, "1")))
insert(newGoogleTask(with(REMOTE_ID, "2")))
insert(newGoogleTask(with(REMOTE_ID, "3")))
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
val one = getByRemoteId("1")
googleTaskDao.move(one, 0, 1)
assertEquals(0, googleTaskDao.getByRemoteId("2")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("1")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("3")!!.order)
googleTaskDao.move(taskDao.fetch(one.task)!!, "calendar", 0, 1)
assertEquals(0L, getOrder("2"))
assertEquals(1L, getOrder("1"))
assertEquals(2L, getOrder("3"))
}
@Test
fun moveToTop() = runBlocking {
insert(newGoogleTask(with(REMOTE_ID, "1")))
insert(newGoogleTask(with(REMOTE_ID, "2")))
insert(newGoogleTask(with(REMOTE_ID, "3")))
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
val three = getByRemoteId("3")
googleTaskDao.move(three, 0, 0)
assertEquals(0, googleTaskDao.getByRemoteId("3")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("1")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("2")!!.order)
googleTaskDao.move(taskDao.fetch(three.task)!!, "calendar", 0, 0)
assertEquals(0L, getOrder("3"))
assertEquals(1L, getOrder("1"))
assertEquals(2L, getOrder("2"))
}
@Test
fun moveToBottom() = runBlocking {
insert(newGoogleTask(with(REMOTE_ID, "1")))
insert(newGoogleTask(with(REMOTE_ID, "2")))
insert(newGoogleTask(with(REMOTE_ID, "3")))
insert(newCaldavTask(with(REMOTE_ID, "1")))
insert(newCaldavTask(with(REMOTE_ID, "2")))
insert(newCaldavTask(with(REMOTE_ID, "3")))
val one = getByRemoteId("1")
googleTaskDao.move(one, 0, 2)
assertEquals(0, googleTaskDao.getByRemoteId("2")!!.order)
assertEquals(1, googleTaskDao.getByRemoteId("3")!!.order)
assertEquals(2, googleTaskDao.getByRemoteId("1")!!.order)
}
@Test
fun findChildrenInList() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L)))
assertEquals(listOf(2L), googleTaskDao.getChildren(listOf(1L, 2L)))
googleTaskDao.move(taskDao.fetch(one.task)!!, "calendar", 0, 2)
assertEquals(0L, getOrder("2"))
assertEquals(1L, getOrder("3"))
assertEquals(2L, getOrder("1"))
}
@Test
fun dontAllowEmptyParent() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "1234")))
googleTaskDao.updatePosition("1234", "", "0")
@ -169,7 +166,7 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Test
fun updatePositionWithNullParent() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "1234")))
googleTaskDao.updatePosition("1234", null, "0")
@ -178,7 +175,7 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Test
fun updatePosition() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "1234")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "1234")))
googleTaskDao.updatePosition("1234", "abcd", "0")
@ -187,86 +184,84 @@ class GoogleTaskDaoTests : InjectingTestCase() {
@Test
fun updateParents() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_PARENT, "123")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_PARENT, "123")))
googleTaskDao.updateParents()
caldavDao.updateParents()
assertEquals(1, googleTaskDao.getByTaskId(2)!!.parent)
assertEquals(1, taskDao.fetch(2)!!.parent)
}
@Test
fun updateParentsByList() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_PARENT, "123")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_PARENT, "123")))
googleTaskDao.updateParents("1")
caldavDao.updateParents("calendar")
assertEquals(1, googleTaskDao.getByTaskId(2)!!.parent)
assertEquals(1, taskDao.fetch(2)!!.parent)
}
@Test
fun updateParentsMustMatchList() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "2"), with(REMOTE_PARENT, "123")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "2"), with(REMOTE_PARENT, "123")))
googleTaskDao.updateParents()
caldavDao.updateParents()
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
assertEquals(0, taskDao.fetch(2)!!.parent)
}
@Test
fun updateParentsByListMustMatchList() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "123")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "2"), with(REMOTE_PARENT, "123")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "123")))
insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "2"), with(REMOTE_PARENT, "123")))
googleTaskDao.updateParents("2")
caldavDao.updateParents("2")
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
assertEquals(0, taskDao.fetch(2)!!.parent)
}
@Test
fun ignoreEmptyStringWhenUpdatingParents() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
googleTaskDao.updateParents()
caldavDao.updateParents()
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
assertEquals(0, taskDao.fetch(2)!!.parent)
}
@Test
fun ignoreEmptyStringWhenUpdatingParentsForList() = runBlocking {
insert(newGoogleTask(with(TASK, 1), with(LIST, "1"), with(REMOTE_ID, "")))
insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
insert(newCaldavTask(with(TASK, 1), with(REMOTE_ID, "")))
insert(newCaldavTask(with(TASK, 2), with(REMOTE_ID, ""), with(REMOTE_PARENT, "")))
googleTaskDao.updateParents("1")
caldavDao.updateParents("1")
assertEquals(0, taskDao.fetch(2)!!.parent)
}
assertEquals(0, googleTaskDao.getByTaskId(2)!!.parent)
private suspend fun getOrder(remoteId: String): Long? {
return taskDao.fetch(googleTaskDao.getByRemoteId(remoteId)!!.task)?.order
}
private suspend fun insertTop(googleTask: GoogleTask) {
private suspend fun insertTop(googleTask: CaldavTask) {
insert(googleTask, true)
}
private suspend fun insertBottom(googleTask: GoogleTask) {
private suspend fun insertBottom(googleTask: CaldavTask) {
insert(googleTask, false)
}
private suspend fun insert(googleTask: GoogleTask, top: Boolean = false) {
private suspend fun insert(googleTask: CaldavTask, top: Boolean = false) {
val task = newTask()
taskDao.createNew(task)
googleTask.task = task.id
googleTaskDao.insertAndShift(googleTask, top)
googleTaskDao.insertAndShift(task, googleTask, top)
}
private suspend fun getByRemoteId(remoteId: String): SubsetGoogleTask {
val googleTask = googleTaskDao.getByRemoteId(remoteId)!!
val result = SubsetGoogleTask()
result.gt_id = googleTask.id
result.gt_list_id = googleTask.listId
result.gt_order = googleTask.order
result.gt_parent = googleTask.parent
return result
private suspend fun getByRemoteId(remoteId: String): CaldavTask {
return googleTaskDao.getByRemoteId(remoteId)!!
}
}

@ -1,6 +1,5 @@
package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
@ -8,46 +7,22 @@ import org.junit.Assert.*
import org.junit.Test
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskListMaker.ACCOUNT
import org.tasks.makers.GoogleTaskListMaker.REMOTE_ID
import org.tasks.makers.GoogleTaskListMaker.newGoogleTaskList
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class GoogleTaskListDaoTest : InjectingTestCase() {
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var caldavDao: CaldavDao
@Test
fun noResultsForEmptyAccount() = runBlocking {
val account = GoogleTaskAccount()
account.account = "user@gmail.com"
googleTaskListDao.insert(account)
val account = CaldavAccount().apply {
uuid = "user@gmail.com"
username = "user@gmail.com"
}
caldavDao.insert(account)
assertTrue(googleTaskListDao.getGoogleTaskFilters(account.account!!).isEmpty())
}
@Test
fun findListWithNullAccount() = runBlocking {
val list = newGoogleTaskList(with(REMOTE_ID, "1234"), with(ACCOUNT, null as String?))
list.id = googleTaskListDao.insert(list)
assertEquals(list, googleTaskListDao.findExistingList("1234"))
}
@Test
fun findListWithEmptyAccount() = runBlocking {
val list = newGoogleTaskList(with(REMOTE_ID, "1234"), with(ACCOUNT, ""))
list.id = googleTaskListDao.insert(list)
assertEquals(list, googleTaskListDao.findExistingList("1234"))
}
@Test
fun ignoreListWithAccount() = runBlocking {
val list = newGoogleTaskList(with(REMOTE_ID, "1234"), with(ACCOUNT, "user@gmail.com"))
googleTaskListDao.insert(list)
assertNull(googleTaskListDao.findExistingList("1234"))
assertTrue(googleTaskListDao.getGoogleTaskFilters(account.username!!).isEmpty())
}
}

@ -14,32 +14,39 @@ import org.junit.Test
import org.tasks.R
import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskListMaker.REMOTE_ID
import org.tasks.makers.GoogleTaskListMaker.newGoogleTaskList
import org.tasks.makers.GoogleTaskMaker.LIST
import org.tasks.makers.GoogleTaskMaker.ORDER
import org.tasks.makers.GoogleTaskMaker.PARENT
import org.tasks.makers.GoogleTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.CaldavCalendarMaker.UUID
import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.ID
import org.tasks.makers.TaskMaker.UUID
import org.tasks.makers.TaskMaker.ORDER
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.preferences.Preferences
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@HiltAndroidTest
class ManualGoogleTaskQueryTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences
private val filter: GtasksFilter = GtasksFilter(newGoogleTaskList(with(REMOTE_ID, "1234")))
private lateinit var filter: GtasksFilter
@Before
override fun setUp() {
super.setUp()
preferences.clear()
preferences.setBoolean(R.string.p_manual_sort, true)
val calendar = newCaldavCalendar(with(UUID, "1234"))
runBlocking {
caldavDao.insert(newCaldavAccount())
caldavDao.insert(calendar)
}
filter = GtasksFilter(calendar)
}
@Test
@ -100,8 +107,13 @@ class ManualGoogleTaskQueryTest : InjectingTestCase() {
}
private suspend fun newTask(id: Long, order: Long, parent: Long = 0) {
taskDao.insert(TaskMaker.newTask(with(ID, id), with(UUID, UUIDHelper.newUUID())))
googleTaskDao.insert(newGoogleTask(with(LIST, filter.list.remoteId), with(TASK, id), with(PARENT, parent), with(ORDER, order)))
taskDao.insert(TaskMaker.newTask(
with(ID, id),
with(TaskMaker.UUID, UUIDHelper.newUUID()),
with(ORDER, order),
with(PARENT, parent),
))
googleTaskDao.insert(newCaldavTask(with(CALENDAR, filter.list.uuid), with(TASK, id)))
}
private suspend fun query(): List<TaskContainer> = taskDao.fetchTasks {

@ -22,7 +22,6 @@ import org.tasks.injection.ProductionModule
import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_ORDER
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TagDataMaker.NAME
import org.tasks.makers.TagDataMaker.newTagData
@ -31,6 +30,7 @@ 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.ORDER
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import java.util.*
@ -129,20 +129,18 @@ class OpenTasksPropertiesTests : OpenTasksTest() {
synchronizer.sync()
assertEquals(
633734058L,
caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")?.order
)
val task = caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")!!.task
assertEquals(633734058L, taskDao.fetch(task)?.order)
}
@Test
fun pushOrder() = runBlocking {
val (listId, list) = openTaskDao.insertList()
val task = newTask().apply { taskDao.createNew(this) }
val task = newTask(with(ORDER, 5678L))
taskDao.createNew(task)
caldavDao.insert(newCaldavTask(
with(CALENDAR, list.uuid),
with(REMOTE_ID, "1234"),
with(REMOTE_ORDER, 5678L),
with(CaldavTaskMaker.TASK, task.id)
))

@ -980,7 +980,6 @@ class TaskListFragment : Fragment(), OnRefreshListener, Toolbar.OnMenuItemClickL
companion object {
const val TAGS_METADATA_JOIN = "for_tags" // $NON-NLS-1$
const val GTASK_METADATA_JOIN = "googletask" // $NON-NLS-1$
const val CALDAV_METADATA_JOIN = "for_caldav" // $NON-NLS-1$
const val ACTION_RELOAD = "action_reload"
const val ACTION_DELETED = "action_deleted"

@ -48,14 +48,14 @@ class CaldavManualSortTaskAdapter internal constructor(
private suspend fun changeParent(task: TaskContainer, newParent: Long) {
val caldavTask = task.getCaldavTask()
if (newParent == 0L) {
caldavTask.cd_remote_parent = ""
caldavTask.remoteParent = ""
task.parent = 0
} else {
val parentTask = caldavDao.getTask(newParent) ?: return
caldavTask.cd_remote_parent = parentTask.remoteId
caldavTask.remoteParent = parentTask.remoteId
task.parent = newParent
}
caldavDao.update(caldavTask.cd_id, caldavTask.cd_remote_parent)
caldavDao.update(caldavTask.id, caldavTask.remoteParent)
taskDao.save(task.getTask(), null)
}
}

@ -104,7 +104,7 @@ class FilterViewHolder internal constructor(
}
return when (filter) {
is TagFilter -> R.drawable.ic_outline_label_24px
is GtasksFilter -> R.drawable.ic_list_24px
is GtasksFilter,
is CaldavFilter -> R.drawable.ic_list_24px
is CustomFilter -> R.drawable.ic_outline_filter_list_24px
is PlaceFilter -> R.drawable.ic_outline_place_24px

@ -15,29 +15,83 @@ class GoogleTaskManualSortAdapter internal constructor(
override suspend fun moved(from: Int, to: Int, indent: Int) {
val task = getTask(from)
val googleTask = task.googleTask
val googleTask = task.caldavTask
val previous = if (to > 0) getTask(to - 1) else null
val list = googleTask.calendar!!
if (previous == null) {
googleTaskDao.move(googleTask, 0, 0)
googleTaskDao.move(
task = task.task,
list = list,
newParent = 0,
newPosition = 0,
)
} else if (to == count || to <= from) {
when {
indent == 0 -> googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + if (to == count) 0 else 1)
previous.hasParent() && previous.parent == googleTask.parent -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + if (to == count) 0 else 1)
previous.hasParent() -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + 1)
else -> googleTaskDao.move(googleTask, previous.id, 0)
indent == 0 ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = 0,
newPosition = previous.getPrimarySort() + if (to == count) 0 else 1,
)
previous.hasParent() && previous.parent == task.parent ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort() + if (to == count) 0 else 1,
)
previous.hasParent() ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort() + 1,
)
else ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.id,
newPosition = 0,
)
}
} else {
when {
indent == 0 -> googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + if (task.hasParent()) 1 else 0)
previous.hasParent() && previous.parent == googleTask.parent -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort())
previous.hasParent() -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + 1)
else -> googleTaskDao.move(googleTask, previous.id, 0)
indent == 0 ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = 0,
newPosition = previous.getPrimarySort() + if (task.hasParent()) 1 else 0,
)
previous.hasParent() && previous.parent == task.parent ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort(),
)
previous.hasParent() ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.parent,
newPosition = previous.getSecondarySort() + 1,
)
else ->
googleTaskDao.move(
task = task.task,
list = list,
newParent = previous.id,
newPosition = 0,
)
}
}
taskDao.touch(task.id)
localBroadcastManager.broadcastRefresh()
if (BuildConfig.DEBUG) {
googleTaskDao.validateSorting(task.googleTaskList!!)
googleTaskDao.validateSorting(task.caldav!!)
}
}
}

@ -7,7 +7,6 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.dialogs.NewFilterDialog
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.*
@ -19,7 +18,6 @@ import javax.inject.Inject
class SubheaderClickHandler @Inject constructor(
private val activity: Activity,
private val preferences: Preferences,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
private val localBroadcastManager: LocalBroadcastManager,
): SubheaderViewHolder.ClickHandler {
@ -28,8 +26,10 @@ class SubheaderClickHandler @Inject constructor(
val collapsed = !subheader.isCollapsed
when (subheader.subheaderType) {
PREFERENCE -> preferences.setBoolean(subheader.id.toInt(), collapsed)
GOOGLE_TASKS -> googleTaskDao.setCollapsed(subheader.id, collapsed)
CALDAV, TASKS, ETESYNC -> caldavDao.setCollapsed(subheader.id, collapsed)
GOOGLE_TASKS,
CALDAV,
TASKS,
ETESYNC -> caldavDao.setCollapsed(subheader.id, collapsed)
}
localBroadcastManager.broadcastRefreshList()
}

@ -13,10 +13,7 @@ import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao
import org.tasks.data.SubsetCaldav
import org.tasks.data.SubsetGoogleTask
import org.tasks.data.TaskContainer
import org.tasks.date.DateTimeUtils.toAppleEpoch
import org.tasks.date.DateTimeUtils.toDateTime
@ -135,21 +132,15 @@ open class TaskAdapter(
open suspend fun moved(from: Int, to: Int, indent: Int) {
val task = getTask(from)
val newParent = findParent(indent, to)
if (newParent?.id ?: 0 == task.parent) {
if ((newParent?.id ?: 0) == task.parent) {
if (indent == 0) {
changeSortGroup(task, if (from < to) to - 1 else to)
}
return
} else if (newParent != null) {
when {
task.isGoogleTask -> if (task.googleTaskList != newParent.googleTaskList) {
googleTaskDao.markDeleted(task.id)
task.googletask = null
}
task.isCaldavTask -> if (task.caldav != newParent.caldav) {
caldavDao.markDeleted(listOf(task.id))
task.caldavTask = null
}
if (task.caldav != newParent.caldav) {
caldavDao.markDeleted(listOf(task.id))
task.caldavTask = null
}
}
when {
@ -250,22 +241,18 @@ open class TaskAdapter(
}
private suspend fun changeGoogleTaskParent(task: TaskContainer, newParent: TaskContainer?) {
val list = newParent?.googleTaskList ?: task.googleTaskList!!
if (newParent == null || task.googleTaskList == newParent.googleTaskList) {
val list = newParent?.caldav ?: task.caldav!!
if (newParent == null || task.caldav == newParent.caldav) {
googleTaskDao.move(
task.googleTask,
newParent?.id ?: 0,
if (newTasksOnTop) 0 else googleTaskDao.getBottom(list, newParent?.id ?: 0))
task.task,
list,
newParent?.id ?: 0,
if (newTasksOnTop) 0 else googleTaskDao.getBottom(list, newParent?.id ?: 0)
)
} else {
val googleTask = GoogleTask(task.id, list)
googleTask.parent = newParent.id
googleTaskDao.insertAndShift(googleTask, newTasksOnTop)
task.googletask = SubsetGoogleTask().apply {
gt_id = googleTask.id
gt_list_id = googleTask.listId
gt_order = googleTask.order
gt_parent = googleTask.parent
}
task.parent = newParent.id
task.caldavTask = CaldavTask(task.id, list, remoteId = null)
googleTaskDao.insertAndShift(task.task, task.caldavTask, newTasksOnTop)
}
taskDao.touch(task.id)
if (BuildConfig.DEBUG) {
@ -275,16 +262,19 @@ open class TaskAdapter(
private suspend fun changeCaldavParent(task: TaskContainer, newParent: TaskContainer?) {
val list = newParent?.caldav ?: task.caldav!!
val caldavTask = task.getCaldavTask() ?: SubsetCaldav()
val caldavTask = task.getCaldavTask() ?: CaldavTask(
task.id,
list,
)
val newParentId = newParent?.id ?: 0
if (newParentId == 0L) {
caldavTask.cd_remote_parent = ""
caldavTask.remoteParent = ""
} else {
val parentTask = caldavDao.getTask(newParentId) ?: return
caldavTask.cd_calendar = list
caldavTask.cd_remote_parent = parentTask.remoteId
caldavTask.calendar = list
caldavTask.remoteParent = parentTask.remoteId
}
caldavTask.cd_order = if (newTasksOnTop) {
task.task.order = if (newTasksOnTop) {
caldavDao.findFirstTask(list, newParentId)
?.takeIf { task.creationDate.toAppleEpoch() >= it}
?.minus(1)
@ -293,15 +283,15 @@ open class TaskAdapter(
?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1)
}
if (caldavTask.cd_id == 0L) {
if (caldavTask.id == 0L) {
val newTask = CaldavTask(task.id, list)
newTask.order = caldavTask.cd_order
newTask.remoteParent = caldavTask.cd_remote_parent
caldavTask.cd_id = caldavDao.insert(newTask)
newTask.remoteParent = caldavTask.remoteParent
caldavTask.id = caldavDao.insert(newTask)
task.caldavTask = caldavTask
} else {
caldavDao.update(caldavTask)
}
taskDao.setOrder(task.id, task.task.order)
taskDao.setParent(newParentId, listOf(task.id))
taskDao.touch(task.id)
localBroadcastManager.broadcastRefresh()

@ -2,19 +2,24 @@ package com.todoroo.astrid.api;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Join;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.astrid.data.Task;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.tasks.R;
import org.tasks.data.CaldavCalendar;
import org.tasks.data.CaldavTask;
import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskList;
import org.tasks.data.TaskDao;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class GtasksFilter extends Filter {
/** Parcelable Creator Object */
@ -36,14 +41,14 @@ public class GtasksFilter extends Filter {
}
};
private GoogleTaskList list;
private CaldavCalendar list;
private GtasksFilter() {
super();
}
public GtasksFilter(GoogleTaskList list) {
super(list.getTitle(), getQueryTemplate(list), getValuesForNewTasks(list));
public GtasksFilter(CaldavCalendar list) {
super(list.getName(), getQueryTemplate(list), getValuesForNewTasks(list));
this.list = list;
id = list.getId();
tint = list.getColor();
@ -51,31 +56,27 @@ public class GtasksFilter extends Filter {
order = list.getOrder();
}
private static QueryTemplate getQueryTemplate(GoogleTaskList list) {
private static QueryTemplate getQueryTemplate(CaldavCalendar list) {
return new QueryTemplate()
.join(Join.left(GoogleTask.TABLE, Task.ID.eq(GoogleTask.TASK)))
.join(Join.left(CaldavTask.TABLE, Task.ID.eq(CaldavTask.TASK)))
.where(
Criterion.and(
TaskDao.TaskCriteria.activeAndVisible(),
GoogleTask.DELETED.eq(0),
GoogleTask.LIST.eq(list.getRemoteId())));
CaldavTask.DELETED.eq(0),
CaldavTask.CALENDAR.eq(list.getUuid())));
}
private static Map<String, Object> getValuesForNewTasks(GoogleTaskList list) {
private static Map<String, Object> getValuesForNewTasks(CaldavCalendar list) {
Map<String, Object> values = new HashMap<>();
values.put(GoogleTask.KEY, list.getRemoteId());
values.put(GoogleTask.KEY, list.getUuid());
return values;
}
public long getStoreId() {
return list.getId();
}
public String getAccount() {
return list.getAccount();
}
public GoogleTaskList getList() {
public CaldavCalendar getList() {
return list;
}
@ -98,7 +99,7 @@ public class GtasksFilter extends Filter {
}
public String getRemoteId() {
return list.getRemoteId();
return list.getUuid();
}
@Override

@ -61,11 +61,6 @@ class SearchFilter : Filter {
.from(CaldavTask.TABLE)
.join(Join.inner(CaldavCalendar.TABLE, CaldavCalendar.UUID.eq(CaldavTask.CALENDAR)))
.where(CaldavCalendar.NAME.like(matcher))),
Task.ID.`in`(
Query.select(GoogleTask.TASK)
.from(GoogleTask.TABLE)
.join(Join.inner(GoogleTaskList.TABLE, GoogleTaskList.REMOTE_ID.eq(GoogleTask.LIST)))
.where(GoogleTaskList.NAME.like(matcher)))
)))
}
}

@ -101,9 +101,8 @@ class BuiltInFilterExposer @Inject constructor(
Filter(
"No list",
QueryTemplate()
.join(Join.left(GoogleTask.TABLE, GoogleTask.TASK.eq(Task.ID)))
.join(Join.left(CaldavTask.TABLE, CaldavTask.TASK.eq(Task.ID)))
.where(and(GoogleTask.ID.eq(null), CaldavTask.ID.eq(null)))
.where(CaldavTask.ID.eq(null))
).apply {
icon = R.drawable.ic_outline_cloud_off_24px
}
@ -116,13 +115,9 @@ class BuiltInFilterExposer @Inject constructor(
Filter(
"Missing list",
QueryTemplate()
.join(Join.left(GoogleTask.TABLE, GoogleTask.TASK.eq(Task.ID)))
.join(Join.left(CaldavTask.TABLE, CaldavTask.TASK.eq(Task.ID)))
.join(Join.left(GoogleTaskList.TABLE, GoogleTaskList.REMOTE_ID.eq(GoogleTask.LIST)))
.join(Join.left(CaldavCalendar.TABLE, CaldavCalendar.UUID.eq(CaldavTask.CALENDAR)))
.where(or(
and(GoogleTask.ID.gt(0), GoogleTaskList.REMOTE_ID.eq(null)),
and(CaldavTask.ID.gt(0), CaldavCalendar.UUID.eq(null))))
.where(and(CaldavTask.ID.gt(0), CaldavCalendar.UUID.eq(null)))
).apply {
icon = R.drawable.ic_outline_cloud_off_24px
}
@ -131,15 +126,10 @@ class BuiltInFilterExposer @Inject constructor(
Filter(
"Missing account",
QueryTemplate()
.join(Join.left(GoogleTask.TABLE, and(GoogleTask.TASK.eq(Task.ID))))
.join(Join.left(CaldavTask.TABLE, and(CaldavTask.TASK.eq(Task.ID))))
.join(Join.left(GoogleTaskList.TABLE, GoogleTaskList.REMOTE_ID.eq(GoogleTask.LIST)))
.join(Join.left(CaldavCalendar.TABLE, CaldavCalendar.UUID.eq(CaldavTask.CALENDAR)))
.join(Join.left(GoogleTaskAccount.TABLE, GoogleTaskAccount.ACCOUNT.eq(GoogleTaskList.ACCOUNT)))
.join(Join.left(CaldavAccount.TABLE, CaldavAccount.UUID.eq(CaldavCalendar.ACCOUNT)))
.where(or(
and(GoogleTask.ID.gt(0), GoogleTaskAccount.ACCOUNT.eq(null)),
and(CaldavTask.ID.gt(0), CaldavAccount.UUID.eq(null))))
.where(and(CaldavTask.ID.gt(0), CaldavAccount.UUID.eq(null)))
).apply {
icon = R.drawable.ic_outline_cloud_off_24px
}

@ -39,7 +39,7 @@ public class SortHelper {
public static final long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT
@SuppressLint("DefaultLocale")
public static final String CALDAV_ORDER_COLUMN =
String.format("IFNULL(caldav_tasks.cd_order, (tasks.created - %d) / 1000)", APPLE_EPOCH);
String.format("IFNULL(tasks.`order`, (tasks.created - %d) / 1000)", APPLE_EPOCH);
private static final String ADJUSTED_DUE_DATE =
"(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)";
@ -176,7 +176,7 @@ public class SortHelper {
select = "tasks.created AS sort_created";
break;
case SORT_GTASKS:
select = "google_tasks.gt_order AS sort_manual";
select = "tasks.`order` AS sort_manual";
break;
case SORT_CALDAV:
select = CALDAV_ORDER_COLUMN + " AS sort_manual";

@ -22,13 +22,10 @@ import org.tasks.notifications.NotificationDao
Place::class,
Geofence::class,
Tag::class,
GoogleTask::class,
Filter::class,
GoogleTaskList::class,
CaldavCalendar::class,
CaldavTask::class,
CaldavAccount::class,
GoogleTaskAccount::class,
Principal::class,
PrincipalAccess::class,
Attachment::class,
@ -36,7 +33,7 @@ import org.tasks.notifications.NotificationDao
autoMigrations = [
AutoMigration(from = 83, to = 84, spec = Migrations.AutoMigrate83to84::class),
],
version = 87
version = 88
)
abstract class Database : RoomDatabase() {
abstract fun notificationDao(): NotificationDao

@ -69,6 +69,8 @@ class TaskDao @Inject constructor(
syncAdapters.sync()
}
suspend fun setOrder(taskId: Long, order: Long?) = taskDao.setOrder(taskId, order)
suspend fun setParent(parent: Long, tasks: List<Long>) = taskDao.setParent(parent, tasks)
suspend fun getChildren(ids: List<Long>) = taskDao.getChildren(ids)

@ -103,6 +103,9 @@ class Task : Parcelable {
@Transient
var parent = 0L
@ColumnInfo(name = "order")
var order: Long? = null
@ColumnInfo(name = "read_only", defaultValue = "0")
var readOnly: Boolean = false
@ -136,6 +139,7 @@ class Task : Parcelable {
isCollapsed = ParcelCompat.readBoolean(parcel)
parent = parcel.readLong()
readOnly = ParcelCompat.readBoolean(parcel)
order = parcel.readLong()
}
var uuid: String
@ -270,6 +274,7 @@ class Task : Parcelable {
ParcelCompat.writeBoolean(dest, isCollapsed)
dest.writeLong(parent)
ParcelCompat.writeBoolean(dest, readOnly)
dest.writeLong(order ?: 0)
}
fun insignificantChange(task: Task?): Boolean {
@ -295,6 +300,7 @@ class Task : Parcelable {
&& calendarURI == task.calendarURI
&& parent == task.parent
&& remoteId == task.remoteId
&& order == task.order
}
fun googleTaskUpToDate(original: Task?): Boolean {
@ -309,6 +315,7 @@ class Task : Parcelable {
&& deletionDate == original.deletionDate
&& parent == original.parent
&& notes == original.notes
&& order == original.order
}
fun caldavUpToDate(original: Task?): Boolean {
@ -327,6 +334,7 @@ class Task : Parcelable {
&& recurrence == original.recurrence
&& parent == original.parent
&& isCollapsed == original.isCollapsed
&& order == original.order
}
val isSaved: Boolean
@ -414,6 +422,7 @@ class Task : Parcelable {
if (parent != other.parent) return false
if (transitoryData != other.transitoryData) return false
if (readOnly != other.readOnly) return false
if (order != other.order) return false
return true
}
@ -441,11 +450,12 @@ class Task : Parcelable {
result = 31 * result + parent.hashCode()
result = 31 * result + (transitoryData?.hashCode() ?: 0)
result = 31 * result + readOnly.hashCode()
result = 31 * result + order.hashCode()
return result
}
override fun toString(): String {
return "Task(id=$id, title=$title, priority=$priority, dueDate=$dueDate, hideUntil=$hideUntil, creationDate=$creationDate, modificationDate=$modificationDate, completionDate=$completionDate, deletionDate=$deletionDate, notes=$notes, estimatedSeconds=$estimatedSeconds, elapsedSeconds=$elapsedSeconds, timerStart=$timerStart, ringFlags=$ringFlags, reminderLast=$reminderLast, recurrence=$recurrence, calendarURI=$calendarURI, remoteId='$remoteId', isCollapsed=$isCollapsed, parent=$parent, transitoryData=$transitoryData, readOnly=$readOnly)"
return "Task(id=$id, title=$title, priority=$priority, dueDate=$dueDate, hideUntil=$hideUntil, creationDate=$creationDate, modificationDate=$modificationDate, completionDate=$completionDate, deletionDate=$deletionDate, notes=$notes, estimatedSeconds=$estimatedSeconds, elapsedSeconds=$elapsedSeconds, timerStart=$timerStart, ringFlags=$ringFlags, reminderLast=$reminderLast, recurrence=$recurrence, calendarURI=$calendarURI, remoteId='$remoteId', isCollapsed=$isCollapsed, parent=$parent, transitoryData=$transitoryData, readOnly=$readOnly, order=$order)"
}
@Retention(AnnotationRetention.SOURCE)

@ -8,25 +8,24 @@ package com.todoroo.astrid.gtasks
import com.google.api.services.tasks.model.TaskList
import com.todoroo.astrid.service.TaskDeleter
import org.tasks.LocalBroadcastManager
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskList
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.GoogleTaskListDao
import timber.log.Timber
import java.util.*
import javax.inject.Inject
class GtasksListService @Inject constructor(
private val googleTaskListDao: GoogleTaskListDao,
private val taskDeleter: TaskDeleter,
private val localBroadcastManager: LocalBroadcastManager) {
private val localBroadcastManager: LocalBroadcastManager,
) {
/**
* Reads in remote list information and updates local list objects.
*
* @param remoteLists remote information about your lists
*/
suspend fun updateLists(account: GoogleTaskAccount, remoteLists: List<TaskList>) {
val lists = googleTaskListDao.getLists(account.account!!)
suspend fun updateLists(account: CaldavAccount, remoteLists: List<TaskList>) {
val lists = googleTaskListDao.getLists(account.uuid!!)
val previousLists: MutableSet<Long> = HashSet()
for (list in lists) {
previousLists.add(list.id)
@ -34,27 +33,20 @@ class GtasksListService @Inject constructor(
for (i in remoteLists.indices) {
val remote = remoteLists[i]
val id = remote.id
var local: GoogleTaskList? = null
var local: CaldavCalendar? = null
for (list in lists) {
if (list.remoteId == id) {
if (list.uuid == id) {
local = list
break
}
}
val title = remote.title
if (local == null) {
val byRemoteId = googleTaskListDao.findExistingList(id)
if (byRemoteId != null) {
byRemoteId.account = account.account
local = byRemoteId
} else {
Timber.d("Adding new gtask list %s", title)
local = GoogleTaskList()
local.account = account.account
local.remoteId = id
}
local = CaldavCalendar(
account = account.uuid,
uuid = id,
)
}
local.title = title
local.name = remote.title
googleTaskListDao.insertOrReplace(local)
previousLists.remove(local.id)
}

@ -20,7 +20,9 @@ import org.tasks.PermissionUtil.verifyPermissions
import org.tasks.R
import org.tasks.analytics.Constants
import org.tasks.analytics.Firebase
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.dialogs.DialogBuilder
import org.tasks.gtasks.GoogleAccountManager
@ -39,6 +41,7 @@ import javax.inject.Inject
class GtasksLoginActivity : InjectingAppCompatActivity() {
@Inject lateinit var dialogBuilder: DialogBuilder
@Inject lateinit var googleAccountManager: GoogleAccountManager
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var permissionRequestor: ActivityPermissionRequestor
@Inject lateinit var firebase: Firebase
@ -71,18 +74,21 @@ class GtasksLoginActivity : InjectingAppCompatActivity() {
startActivity(intent)
} else {
withContext(NonCancellable) {
var account = googleTaskListDao.getAccount(accountName)
var account = caldavDao.getAccount(TYPE_GOOGLE_TASKS, accountName)
if (account == null) {
account = GoogleTaskAccount()
account.account = accountName
googleTaskListDao.insert(account)
account = CaldavAccount()
account.accountType = TYPE_GOOGLE_TASKS
account.uuid = accountName
account.name = accountName
account.username = accountName
caldavDao.insert(account)
firebase.logEvent(
R.string.event_sync_add_account,
R.string.param_type to Constants.SYNC_TYPE_GOOGLE_TASKS
)
} else {
account.error = ""
googleTaskListDao.update(account)
caldavDao.update(account)
googleTaskListDao.resetLastSync(accountName)
}
}

@ -35,11 +35,9 @@ class TaskCompleter @Inject internal constructor(
ArrayList<Task?>()
.apply {
if (includeChildren) {
addAll(googleTaskDao.getChildTasks(item.id))
addAll(taskDao.getChildren(item.id).let { taskDao.fetch(it) })
}
if (!completed) {
add(googleTaskDao.getParentTask(item.id))
addAll(taskDao.getParents(item.id).let { taskDao.fetch(it) })
}
add(item)

@ -55,7 +55,10 @@ class TaskCreator @Inject constructor(
val addToTop = preferences.addTasksToTop()
if (task.hasTransitory(GoogleTask.KEY)) {
googleTaskDao.insertAndShift(
GoogleTask(task.id, task.getTransitory<String>(GoogleTask.KEY)!!), addToTop)
task,
CaldavTask(task.id, task.getTransitory<String>(GoogleTask.KEY)!!, remoteId = null),
addToTop
)
} else if (task.hasTransitory(CaldavTask.KEY)) {
caldavDao.insert(
task, CaldavTask(task.id, task.getTransitory<String>(CaldavTask.KEY)), addToTop)
@ -63,7 +66,10 @@ class TaskCreator @Inject constructor(
val remoteList = defaultFilterProvider.getDefaultList()
if (remoteList is GtasksFilter) {
googleTaskDao.insertAndShift(
GoogleTask(task.id, remoteList.remoteId), addToTop)
task,
CaldavTask(task.id, remoteList.remoteId, remoteId = null),
addToTop
)
} else if (remoteList is CaldavFilter) {
caldavDao.insert(
task, CaldavTask(task.id, remoteList.uuid), addToTop)

@ -2,7 +2,6 @@ package com.todoroo.astrid.service
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.data.Task
import kotlinx.coroutines.runBlocking
import org.tasks.LocalBroadcastManager
import org.tasks.caldav.VtodoCache
import org.tasks.data.*
@ -29,7 +28,6 @@ class TaskDeleter @Inject constructor(
suspend fun markDeleted(taskIds: List<Long>): List<Task> {
val ids = taskIds
.toSet()
.plus(taskIds.chunkedMap(googleTaskDao::getChildren))
.plus(taskIds.chunkedMap(taskDao::getChildren))
.let { taskDao.fetch(it.toList()) }
.filterNot { it.readOnly }
@ -51,7 +49,6 @@ class TaskDeleter @Inject constructor(
.map(TaskContainer::getId)
.toMutableList()
completed.removeAll(deletionDao.hasRecurringAncestors(completed))
completed.removeAll(googleTaskDao.hasRecurringParent(completed))
markDeleted(completed)
return completed.size
}
@ -66,18 +63,6 @@ class TaskDeleter @Inject constructor(
localBroadcastManager.broadcastRefresh()
}
fun delete(list: GoogleTaskList) = runBlocking {
val tasks = deletionDao.delete(list)
delete(tasks)
localBroadcastManager.broadcastRefreshList()
}
suspend fun delete(list: GoogleTaskAccount) {
val tasks = deletionDao.delete(list)
delete(tasks)
localBroadcastManager.broadcastRefreshList()
}
suspend fun delete(list: CaldavCalendar) {
vtodoCache.delete(list)
val tasks = deletionDao.delete(list)

@ -28,8 +28,7 @@ class TaskDuplicator @Inject constructor(
return taskIds
.dbchunk()
.flatMap {
it.minus(googleTaskDao.getChildren(it).toSet())
.minus(taskDao.getChildren(it).toSet())
it.minus(taskDao.getChildren(it).toSet())
}
.let { taskDao.fetch(it) }
.filterNot { it.readOnly }
@ -58,7 +57,11 @@ class TaskDuplicator @Inject constructor(
val googleTask = googleTaskDao.getByTaskId(originalId)
val addToTop = preferences.addTasksToTop()
if (googleTask != null) {
googleTaskDao.insertAndShift(GoogleTask(clone.id, googleTask.listId!!), addToTop)
googleTaskDao.insertAndShift(
clone,
CaldavTask(clone.id, googleTask.calendar!!, remoteId = null),
addToTop
)
}
val caldavTask = caldavDao.getTask(originalId)
if (caldavTask != null) {

@ -46,11 +46,8 @@ class TaskMover @Inject constructor(
suspend fun move(ids: List<Long>, selectedList: Filter) {
val tasks = ids
.dbchunk()
.flatMap {
it.minus(googleTaskDao.getChildren(it).toSet())
.minus(taskDao.getChildren(it).toSet())
}
.let { taskDao.fetch(it) }
.flatMap { taskDao.getChildren(it) }
.let { taskDao.fetch(ids.minus(it.toSet())) }
.filterNot { it.readOnly }
val taskIds = tasks.map { it.id }
taskDao.setParent(0, ids.intersect(taskIds.toSet()).toList())
@ -82,32 +79,30 @@ class TaskMover @Inject constructor(
moveLocalTask(task, selectedList)
}
private suspend fun moveGoogleTask(task: Task, googleTask: GoogleTask, selected: Filter) {
if (selected is GtasksFilter && googleTask.listId == selected.remoteId) {
private suspend fun moveGoogleTask(task: Task, googleTask: CaldavTask, selected: Filter) {
if (selected is GtasksFilter && googleTask.calendar == selected.remoteId) {
return
}
val id = googleTask.task
val children = googleTaskDao.getChildren(id)
val childIds = children.map(GoogleTask::task)
googleTaskDao.markDeleted(id, DateUtilities.now())
val id = task.id
val children = taskDao.getChildren(id)
caldavDao.markDeleted(children + id, DateUtilities.now())
when(selected) {
is GtasksFilter -> {
val listId = selected.remoteId
googleTaskDao.insertAndShift(GoogleTask(id, listId), preferences.addTasksToTop())
googleTaskDao.insertAndShift(
task = task,
caldavTask = CaldavTask(id, listId, remoteId = null),
top = preferences.addTasksToTop()
)
children.takeIf { it.isNotEmpty() }
?.map {
val newChild = GoogleTask(it.task, listId)
newChild.order = it.order
newChild.parent = id
newChild
}
?.map { CaldavTask(task = it, calendar = listId, remoteId = null) }
?.let { googleTaskDao.insert(it) }
}
is CaldavFilter -> {
val listId = selected.uuid
val newParent = CaldavTask(id, listId)
caldavDao.insert(task, newParent, preferences.addTasksToTop())
childIds.map {
children.map {
val newChild = CaldavTask(it, listId)
newChild.remoteParent = newParent.remoteId
newChild
@ -176,16 +171,16 @@ class TaskMover @Inject constructor(
}
private suspend fun moveToGoogleTasks(id: Long, children: List<Long>, filter: GtasksFilter) {
taskDao.setParent(0, children)
val task = taskDao.fetch(id) ?: return
taskDao.setParent(id, children)
val listId = filter.remoteId
googleTaskDao.insertAndShift(GoogleTask(id, listId), preferences.addTasksToTop())
googleTaskDao.insertAndShift(
task,
CaldavTask(id, listId, remoteId = null),
preferences.addTasksToTop()
)
children.takeIf { it.isNotEmpty() }
?.mapIndexed { index, task ->
val newChild = GoogleTask(task, listId)
newChild.order = index.toLong()
newChild.parent = id
newChild
}
?.map { CaldavTask(it, listId, remoteId = null) }
?.let { googleTaskDao.insert(it) }
}
}

@ -35,7 +35,6 @@ class Upgrader @Inject constructor(
private val filterDao: FilterDao,
private val defaultFilterProvider: DefaultFilterProvider,
private val googleTaskListDao: GoogleTaskListDao,
private val googleTaskDao: GoogleTaskDao,
private val userActivityDao: UserActivityDao,
private val taskAttachmentDao: TaskAttachmentDao,
private val caldavDao: CaldavDao,
@ -56,7 +55,6 @@ class Upgrader @Inject constructor(
run(from, V4_9_5) { removeDuplicateTags() }
run(from, V5_3_0) { migrateFilters() }
run(from, V6_0_beta_1) { migrateDefaultSyncList() }
run(from, V6_0_beta_2) { migrateGoogleTaskAccount() }
run(from, V6_4) { migrateUris() }
run(from, V6_7) { this.migrateGoogleTaskFilters() }
run(from, V6_8_1) { this.migrateCaldavFilters() }
@ -71,8 +69,8 @@ class Upgrader @Inject constructor(
preferences.setBoolean(R.string.p_astrid_sort_enabled, true)
taskMover.migrateLocalTasks()
}
run(from, V9_7) { googleTaskListDao.resetOrders() }
run(from, V9_7_3) { googleTaskDao.updateParents() }
run(from, V9_7) { caldavDao.resetOrders() }
run(from, V9_7_3) { caldavDao.updateParents() }
run(from, V10_0_2) {
filterDao.getFilters()
.filter { it.getSql().trim() == "WHERE" }
@ -96,6 +94,9 @@ class Upgrader @Inject constructor(
run(from, V12_6) {
setInstallDetails(from)
}
run(from, V13_2) {
caldavDao.updateParents()
}
preferences.setBoolean(R.string.p_just_updated, true)
} else {
setInstallDetails(to)
@ -133,10 +134,6 @@ class Upgrader @Inject constructor(
calendar.copy(color = getAndroidColor(calendar.color))
)
}
for (list in googleTaskListDao.getAllLists()) {
list.setColor(getAndroidColor(list.getColor()!!))
googleTaskListDao.update(list)
}
for (tagData in tagDataDao.getAll()) {
tagData.setColor(getAndroidColor(tagData.getColor()!!))
tagDataDao.update(tagData)
@ -156,8 +153,7 @@ class Upgrader @Inject constructor(
val remoteTask = vtodoCache.getVtodo(task)?.let { fromVtodo(it) } ?: continue
val order: Long? = remoteTask.order
if (order != null) {
task.order = order
caldavDao.update(task)
taskDao.setOrder(task.task, order)
}
}
}
@ -247,19 +243,6 @@ class Upgrader @Inject constructor(
}
}
private suspend fun migrateGoogleTaskAccount() {
val account = preferences.getStringValue("gtasks_user")
if (!isNullOrEmpty(account)) {
val googleTaskAccount = GoogleTaskAccount()
googleTaskAccount.account = account
googleTaskListDao.insert(googleTaskAccount)
for (list in googleTaskListDao.getAllLists()) {
list.account = account
googleTaskListDao.insertOrReplace(list)
}
}
}
private suspend fun migrateUris() {
migrateUriPreference(R.string.p_backup_dir)
migrateUriPreference(R.string.p_attachment_dir)
@ -339,7 +322,6 @@ class Upgrader @Inject constructor(
private const val V4_9_5 = 434
private const val V5_3_0 = 491
private const val V6_0_beta_1 = 522
private const val V6_0_beta_2 = 523
const val V6_4 = 546
private const val V6_7 = 585
private const val V6_8_1 = 607
@ -358,6 +340,7 @@ class Upgrader @Inject constructor(
const val V12_4 = 120400
const val V12_6 = 120601
const val V12_8 = 120800
const val V13_2 = 130200
@JvmStatic
fun getAndroidColor(context: Context, index: Int): Int {

@ -1,7 +1,7 @@
package org.tasks.activities
import dagger.hilt.android.lifecycle.HiltViewModel
import org.tasks.data.GoogleTaskList
import org.tasks.data.CaldavCalendar
import org.tasks.googleapis.InvokerFactory
import org.tasks.ui.ActionViewModel
import javax.inject.Inject
@ -9,7 +9,7 @@ import javax.inject.Inject
@HiltViewModel
class DeleteListViewModel @Inject constructor(
private val invoker: InvokerFactory) : ActionViewModel() {
suspend fun deleteList(list: GoogleTaskList) {
run { invoker.getGtasksInvoker(list.account!!).deleteGtaskList(list.remoteId) }
suspend fun deleteList(list: CaldavCalendar) {
run { invoker.getGtasksInvoker(list.account!!).deleteGtaskList(list.uuid) }
}
}

@ -8,6 +8,7 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.ProgressBar
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.textfield.TextInputEditText
import com.google.api.services.tasks.model.TaskList
import com.todoroo.astrid.activity.MainActivity
@ -15,10 +16,13 @@ import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.service.TaskDeleter
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.tasks.R
import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskList
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.GoogleTaskListDao
import org.tasks.databinding.ActivityGoogleTaskListSettingsBinding
import org.tasks.extensions.Context.toast
@ -34,20 +38,21 @@ class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
private lateinit var progressView: ProgressBar
private var isNewList = false
private lateinit var gtasksList: GoogleTaskList
private lateinit var gtasksList: CaldavCalendar
private val createListViewModel: CreateListViewModel by viewModels()
private val renameListViewModel: RenameListViewModel by viewModels()
private val deleteListViewModel: DeleteListViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
gtasksList = intent.getParcelableExtra(EXTRA_STORE_DATA)
?: GoogleTaskList().apply {
?: CaldavCalendar(
account = intent.getParcelableExtra<CaldavAccount>(EXTRA_ACCOUNT)!!.username
).apply {
isNewList = true
account = intent.getParcelableExtra<GoogleTaskAccount>(EXTRA_ACCOUNT)!!.account
}
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
selectedColor = gtasksList.getColor()!!
selectedColor = gtasksList.color
selectedIcon = gtasksList.getIcon()!!
}
if (isNewList) {
@ -55,7 +60,7 @@ class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(name, InputMethodManager.SHOW_IMPLICIT)
} else {
name.setText(gtasksList.title)
name.setText(gtasksList.name)
}
if (createListViewModel.inProgress
|| renameListViewModel.inProgress
@ -72,7 +77,7 @@ class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
get() = isNewList
override val toolbarTitle: String?
get() = if (isNew) getString(R.string.new_list) else gtasksList.title!!
get() = if (isNew) getString(R.string.new_list) else gtasksList.name!!
private fun showProgressIndicator() {
progressView.visibility = View.VISIBLE
@ -104,7 +109,7 @@ class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
}
else -> {
if (colorChanged() || iconChanged()) {
gtasksList.setColor(selectedColor)
gtasksList.color = selectedColor
gtasksList.setIcon(selectedIcon)
googleTaskListDao.insertOrReplace(gtasksList)
setResult(
@ -154,17 +159,17 @@ class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
selectedColor >= 0 || !isNullOrEmpty(newName)
} else colorChanged() || nameChanged() || iconChanged()
private fun colorChanged() = selectedColor != gtasksList.getColor()
private fun colorChanged() = selectedColor != gtasksList.color
private fun iconChanged() = selectedIcon != gtasksList.getIcon()
private fun nameChanged() = newName != gtasksList.title
private fun nameChanged() = newName != gtasksList.name
private suspend fun onListCreated(taskList: TaskList) {
with(gtasksList) {
remoteId = taskList.id
title = taskList.title
setColor(selectedColor)
uuid = taskList.id
name = taskList.title
color = selectedColor
setIcon(selectedIcon)
id = googleTaskListDao.insertOrReplace(this)
}
@ -176,16 +181,20 @@ class GoogleTaskListSettingsActivity : BaseListSettingsActivity() {
private fun onListDeleted(deleted: Boolean) {
if (deleted) {
taskDeleter.delete(gtasksList)
setResult(Activity.RESULT_OK, Intent(TaskListFragment.ACTION_DELETED))
finish()
lifecycleScope.launch {
withContext(NonCancellable) {
taskDeleter.delete(gtasksList)
}
setResult(Activity.RESULT_OK, Intent(TaskListFragment.ACTION_DELETED))
finish()
}
}
}
private suspend fun onListRenamed(taskList: TaskList) {
with(gtasksList) {
title = taskList.title
setColor(selectedColor)
name = taskList.title
color = selectedColor
setIcon(selectedIcon)
googleTaskListDao.insertOrReplace(this)
}

@ -126,7 +126,6 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
lifecycleScope.launch {
filterDao.resetOrders()
caldavDao.resetOrders()
googleTaskListDao.resetOrders()
tagDataDao.resetOrders()
locationDao.resetOrders()
updateFilters()
@ -226,7 +225,7 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
private suspend fun setOrder(order: Int, filter: FilterListItem) {
when (filter) {
is GtasksFilter -> googleTaskListDao.setOrder(filter.list.id, order)
is GtasksFilter -> caldavDao.setOrder(filter.list.id, order)
is CaldavFilter -> caldavDao.setOrder(filter.calendar.id, order)
is TagFilter -> tagDataDao.setOrder(filter.tagData.id!!, order)
is CustomFilter -> filterDao.setOrder(filter.id, order)

@ -2,7 +2,7 @@ package org.tasks.activities
import com.google.api.services.tasks.model.TaskList
import dagger.hilt.android.lifecycle.HiltViewModel
import org.tasks.data.GoogleTaskList
import org.tasks.data.CaldavCalendar
import org.tasks.googleapis.InvokerFactory
import org.tasks.ui.CompletableViewModel
import javax.inject.Inject
@ -10,7 +10,7 @@ import javax.inject.Inject
@HiltViewModel
class RenameListViewModel @Inject constructor(
private val invoker: InvokerFactory) : CompletableViewModel<TaskList>() {
suspend fun renameList(list: GoogleTaskList, name: String) {
run { invoker.getGtasksInvoker(list.account!!).renameGtaskList(list.remoteId, name)!! }
suspend fun renameList(list: CaldavCalendar, name: String) {
run { invoker.getGtasksInvoker(list.account!!).renameGtaskList(list.uuid, name)!! }
}
}

@ -9,8 +9,6 @@ class BackupContainer(
val places: List<Place>?,
val tags: List<TagData>?,
val filters: List<Filter>?,
val googleTaskAccounts: List<GoogleTaskAccount>?,
val googleTaskLists: List<GoogleTaskList>?,
val caldavAccounts: List<CaldavAccount>?,
val caldavCalendars: List<CaldavCalendar>?,
val taskListMetadata: List<TaskListMetadata>?,
@ -20,17 +18,19 @@ class BackupContainer(
val stringPrefs: Map<String, String>?,
val boolPrefs: Map<String, java.lang.Boolean>?,
val setPrefs: Map<String, java.util.Set<*>>?,
val googleTaskAccounts: List<GoogleTaskAccount>? = emptyList(),
val googleTaskLists: List<GoogleTaskList>? = emptyList(),
) {
class TaskBackup(
val task: Task,
val alarms: List<Alarm>,
val geofences: List<Geofence>?,
val tags: List<Tag>,
val google: List<GoogleTask>,
val comments: List<UserActivity>,
val attachments: List<Attachment>?,
val caldavTasks: List<CaldavTask>?,
val vtodo: String?,
val google: List<GoogleTask> = emptyList(),
) {
val locations: List<LegacyLocation> = emptyList()
}

@ -37,9 +37,7 @@ class TasksJsonExporter @Inject constructor(
private val alarmDao: AlarmDao,
private val locationDao: LocationDao,
private val tagDao: TagDao,
private val googleTaskDao: GoogleTaskDao,
private val filterDao: FilterDao,
private val googleTaskListDao: GoogleTaskListDao,
private val taskAttachmentDao: TaskAttachmentDao,
private val caldavDao: CaldavDao,
private val workManager: WorkManager,
@ -117,7 +115,6 @@ class TasksJsonExporter @Inject constructor(
alarmDao.getAlarms(taskId),
locationDao.getGeofencesForTask(taskId),
tagDao.getTagsForTask(taskId),
googleTaskDao.getAllByTaskId(taskId),
userActivityDao.getComments(taskId),
taskAttachmentDao.getAttachmentsForTask(taskId),
caldavTasks,
@ -132,8 +129,6 @@ class TasksJsonExporter @Inject constructor(
locationDao.getPlaces(),
tagDataDao.getAll(),
filterDao.getFilters(),
googleTaskListDao.getAccounts(),
googleTaskListDao.getAllLists(),
caldavDao.getAccounts(),
caldavDao.getCalendars(),
taskListMetadataDao.getAll(),

@ -19,6 +19,7 @@ import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.caldav.VtodoCache
import org.tasks.data.*
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.Place.Companion.newPlace
import org.tasks.db.Migrations.repeatFrom
import org.tasks.db.Migrations.withoutFrom
@ -38,8 +39,6 @@ class TasksJsonImporter @Inject constructor(
private val localBroadcastManager: LocalBroadcastManager,
private val alarmDao: AlarmDao,
private val tagDao: TagDao,
private val googleTaskDao: GoogleTaskDao,
private val googleTaskListDao: GoogleTaskListDao,
private val filterDao: FilterDao,
private val taskAttachmentDao: TaskAttachmentDao,
private val caldavDao: CaldavDao,
@ -81,8 +80,15 @@ class TasksJsonImporter @Inject constructor(
tagDataDao.createNew(tagData)
}
backupContainer.googleTaskAccounts?.forEach { googleTaskAccount ->
if (googleTaskListDao.getAccount(googleTaskAccount.account!!) == null) {
googleTaskListDao.insert(googleTaskAccount)
if (caldavDao.getAccount(TYPE_GOOGLE_TASKS, googleTaskAccount.account!!) == null) {
caldavDao.insert(
CaldavAccount().apply {
accountType = TYPE_GOOGLE_TASKS
uuid = googleTaskAccount.account
name = googleTaskAccount.account
username = googleTaskAccount.account
}
)
}
}
backupContainer.places?.forEach { place ->
@ -91,9 +97,15 @@ class TasksJsonImporter @Inject constructor(
}
}
backupContainer.googleTaskLists?.forEach { googleTaskList ->
googleTaskList.setColor(themeToColor(context, version, googleTaskList.getColor()!!))
if (googleTaskListDao.getByRemoteId(googleTaskList.remoteId!!) == null) {
googleTaskListDao.insert(googleTaskList)
if (caldavDao.getCalendar(googleTaskList.remoteId!!) == null) {
caldavDao.insert(
CaldavCalendar(
account = googleTaskList.account,
uuid = googleTaskList.remoteId,
color = themeToColor(context, version, googleTaskList.color ?: 0),
)
)
}
}
backupContainer.filters?.forEach { filter ->
@ -174,8 +186,17 @@ class TasksJsonImporter @Inject constructor(
userActivityDao.createNew(comment)
}
for (googleTask in backup.google) {
googleTask.task = taskId
googleTaskDao.insert(googleTask)
caldavDao.insert(
CaldavTask(
task = taskId,
calendar = googleTask.listId,
remoteId = googleTask.remoteId,
).apply {
remoteOrder = googleTask.remoteOrder
remoteParent = googleTask.remoteParent
lastSync = googleTask.lastSync
}
)
}
for (location in backup.locations) {
val place = newPlace()
@ -226,7 +247,6 @@ class TasksJsonImporter @Inject constructor(
}
result.importCount++
}
googleTaskDao.updateParents()
caldavDao.updateParents()
val ignoreKeys = ignorePrefs.map { context.getString(it) }
backupContainer

@ -438,7 +438,7 @@ class iCalendar @Inject constructor(
else -> if (priority > 5) min(9, priority) else 9
}
parent = if (task.parent == 0L) null else caldavTask.remoteParent
order = caldavTask.order
order = task.order
collapsed = task.isCollapsed
}

@ -30,12 +30,12 @@ fun com.todoroo.astrid.data.Task.applyRemote(
applyDue(remote, local)
applyStart(remote, local)
applyCollapsed(remote, local)
applyOrder(remote, local)
return this
}
fun CaldavTask.applyRemote(remote: Task, local: Task?): CaldavTask {
applyParent(remote, local)
applyOrder(remote, local)
return this
}
@ -108,7 +108,7 @@ private fun com.todoroo.astrid.data.Task.applyCollapsed(remote: Task, local: Tas
}
}
private fun CaldavTask.applyOrder(remote: Task, local: Task?) {
private fun com.todoroo.astrid.data.Task.applyOrder(remote: Task, local: Task?) {
if (local == null || local.order == order) {
order = remote.order
}

@ -28,13 +28,13 @@ import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.data.Task
import org.tasks.compose.*
import org.tasks.data.GoogleTask
import org.tasks.data.TaskContainer
@Composable
fun SubtaskRow(
originalFilter: Filter?,
filter: Filter?,
googleTask: GoogleTask?,
hasParent: Boolean,
desaturate: Boolean,
existingSubtasks: List<TaskContainer>,
newSubtasks: List<Task>,
@ -61,9 +61,11 @@ fun SubtaskRow(
},
content = {
Column {
val isGoogleTaskChild =
filter is GtasksFilter && googleTask != null && googleTask.parent > 0 && googleTask.listId == filter.remoteId
hasParent &&
filter is GtasksFilter &&
originalFilter is GtasksFilter &&
originalFilter.remoteId == filter.remoteId
if (isGoogleTaskChild) {
DisabledText(
text = stringResource(id = org.tasks.R.string.subtasks_multilevel_google_task),
@ -207,8 +209,9 @@ fun ExistingSubtaskRow(
fun NoSubtasks() {
MdcTheme {
SubtaskRow(
originalFilter = null,
filter = null,
googleTask = null,
hasParent = false,
desaturate = true,
existingSubtasks = emptyList(),
newSubtasks = emptyList(),
@ -228,8 +231,9 @@ fun NoSubtasks() {
fun SubtasksPreview() {
MdcTheme {
SubtaskRow(
originalFilter = null,
filter = null,
googleTask = null,
hasParent = false,
desaturate = true,
existingSubtasks = listOf(
TaskContainer().apply {

@ -103,6 +103,9 @@ class CaldavAccount : Parcelable {
val isMicrosoft: Boolean
get() = accountType == TYPE_MICROSOFT
val isGoogleTasks: Boolean
get() = accountType == TYPE_GOOGLE_TASKS
fun listSettingsClass(): Class<out Activity> = when(accountType) {
TYPE_LOCAL -> LocalListSettingsActivity::class.java
TYPE_ETESYNC, TYPE_OPENTASKS -> OpenTasksListSettingsActivity::class.java
@ -208,6 +211,7 @@ class CaldavAccount : Parcelable {
uuid.isDavx5() -> R.string.davx5
uuid.isDecSync() -> R.string.decsync
isMicrosoft -> R.string.microsoft
isGoogleTasks -> R.string.gtasks_GPr_header
else -> 0
}
@ -219,12 +223,14 @@ class CaldavAccount : Parcelable {
uuid.isDavx5() -> R.drawable.ic_davx5_icon_green_bg
uuid.isDecSync() -> R.drawable.ic_decsync
isMicrosoft -> R.drawable.ic_microsoft_tasks
isGoogleTasks -> R.drawable.ic_google
else -> 0
}
companion object {
val TABLE = Table("caldav_accounts")
val UUID = TABLE.column("cda_uuid")
val ACCOUNT_TYPE = TABLE.column("cda_account_type")
const val TYPE_CALDAV = 0
@Deprecated("use etebase") const val TYPE_ETESYNC = 1
@ -233,6 +239,7 @@ class CaldavAccount : Parcelable {
const val TYPE_TASKS = 4
const val TYPE_ETEBASE = 5
const val TYPE_MICROSOFT = 6
const val TYPE_GOOGLE_TASKS = 7
const val SERVER_UNKNOWN = -1
const val SERVER_TASKS = 0

@ -25,6 +25,7 @@ data class CaldavCalendar(
@ColumnInfo(name = "cdl_icon") private var icon: Int? = -1,
@ColumnInfo(name = "cdl_order") val order: Int = NO_ORDER,
@ColumnInfo(name = "cdl_access") var access: Int = ACCESS_OWNER,
@ColumnInfo(name = "cdl_last_sync") val lastSync: Long = 0,
) : Parcelable {
@Ignore
constructor(source: Parcel): this(
@ -38,6 +39,7 @@ data class CaldavCalendar(
icon = source.readInt(),
order = source.readInt(),
access = source.readInt(),
lastSync = source.readLong(),
)
@Suppress("RedundantNullableReturnType")
@ -63,6 +65,7 @@ data class CaldavCalendar(
writeInt(getIcon()!!)
writeInt(order)
writeInt(access)
writeLong(lastSync)
}
}

@ -12,6 +12,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.tasks.R
import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.CaldavAccount.Companion.TYPE_LOCAL
import org.tasks.data.CaldavAccount.Companion.TYPE_OPENTASKS
import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
@ -92,25 +93,43 @@ ORDER BY CASE cda_account_type
@Transaction
open suspend fun insert(task: Task, caldavTask: CaldavTask, addToTop: Boolean): Long {
if (caldavTask.order != null) {
if (task.order != null) {
return insert(caldavTask)
}
if (addToTop) {
caldavTask.order = findFirstTask(caldavTask.calendar!!, task.parent)
task.order = findFirstTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() >= it }
?.minus(1)
} else {
caldavTask.order = findLastTask(caldavTask.calendar!!, task.parent)
task.order = findLastTask(caldavTask.calendar!!, task.parent)
?.takeIf { task.creationDate.toAppleEpoch() <= it }
?.plus(1)
}
return insert(caldavTask)
val id = insert(caldavTask)
update(task)
return id
}
@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")
@Query("""
SELECT MIN(IFNULL(`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 suspend 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")
@Query("""
SELECT MAX(IFNULL(`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 suspend fun findLastTask(calendar: String, parent: Long): Long?
@Insert
@ -122,15 +141,11 @@ ORDER BY CASE cda_account_type
@Update
abstract suspend fun update(caldavTask: CaldavTask)
suspend fun update(caldavTask: SubsetCaldav) {
update(caldavTask.cd_id, caldavTask.cd_order, caldavTask.cd_remote_parent)
}
@Query("UPDATE caldav_tasks SET cd_order = :position, cd_remote_parent = :parent WHERE cd_id = :id")
internal abstract suspend fun update(id: Long, position: Long?, parent: String?)
@Update
abstract suspend fun update(task: Task)
@Query("UPDATE caldav_tasks SET cd_order = :position WHERE cd_id = :id")
internal abstract suspend fun update(id: Long, position: Long?)
@Update
abstract suspend fun updateTasks(tasks: Iterable<Task>)
@Query("UPDATE caldav_tasks SET cd_remote_parent = :remoteParent WHERE cd_id = :id")
internal abstract suspend fun update(id: Long, remoteParent: String?)
@ -188,7 +203,16 @@ SELECT EXISTS(SELECT 1
+ "AND cd_deleted = 0")
abstract suspend fun getCaldavTasksToPush(calendar: String): List<CaldavTaskContainer>
@Query("SELECT * FROM caldav_lists ORDER BY cdl_name COLLATE NOCASE")
@Query("SELECT * FROM caldav_lists " +
"INNER JOIN caldav_accounts ON caldav_lists.cdl_account = caldav_accounts.cda_uuid " +
"WHERE caldav_accounts.cda_account_type = $TYPE_GOOGLE_TASKS " +
"ORDER BY cdl_name COLLATE NOCASE")
abstract suspend fun getGoogleTaskLists(): List<CaldavCalendar>
@Query("SELECT * FROM caldav_lists " +
"INNER JOIN caldav_accounts ON caldav_lists.cdl_account = caldav_accounts.cda_uuid " +
"WHERE caldav_accounts.cda_account_type != $TYPE_GOOGLE_TASKS " +
"ORDER BY cdl_name COLLATE NOCASE")
abstract suspend fun getCalendars(): List<CaldavCalendar>
@Query("""
@ -240,12 +264,16 @@ SELECT EXISTS(SELECT 1
@Query("""
SELECT caldav_lists.*, COUNT(DISTINCT(tasks._id)) AS count, COUNT(DISTINCT(principal_access.id)) AS principals
FROM caldav_lists
LEFT JOIN caldav_tasks
ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid
LEFT JOIN tasks ON caldav_tasks.cd_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND
tasks.hideUntil < :now AND cd_deleted = 0
LEFT JOIN caldav_tasks ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid
LEFT JOIN tasks ON caldav_tasks.cd_task = tasks._id AND
tasks.deleted = 0 AND
tasks.completed = 0 AND
tasks.hideUntil < :now AND
cd_deleted = 0
LEFT JOIN principal_access ON caldav_lists.cdl_id = principal_access.list
LEFT JOIN caldav_accounts ON caldav_accounts.cda_uuid = caldav_lists.cdl_account
WHERE caldav_lists.cdl_account = :uuid
AND caldav_accounts.cda_account_type != $TYPE_GOOGLE_TASKS
GROUP BY caldav_lists.cdl_uuid
""")
abstract suspend fun getCaldavFilters(uuid: String, now: Long = currentTimeMillis()): List<CaldavFilters>
@ -255,7 +283,10 @@ GROUP BY caldav_lists.cdl_uuid
+ " 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)"
+ " AND p.cd_deleted = 0"
+ " AND caldav_tasks.cd_remote_parent IS NOT NULL"
+ " AND caldav_tasks.cd_remote_parent != ''"
+ "), 0)"
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0)")
abstract suspend fun updateParents()
@ -266,14 +297,16 @@ GROUP BY caldav_lists.cdl_uuid
+ " 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)"
+ " AND p.cd_deleted = 0"
+ " AND caldav_tasks.cd_remote_parent IS NOT NULL"
+ " AND caldav_tasks.cd_remote_parent != ''"
+ "), 0)"
+ "WHERE _id IN (SELECT _id FROM tasks INNER JOIN caldav_tasks ON _id = cd_task WHERE cd_deleted = 0 AND cd_calendar = :calendar)")
abstract suspend fun updateParents(calendar: String)
@Transaction
open suspend fun move(task: TaskContainer, newParent: Long, newPosition: Long?) {
val previousParent = task.parent
val caldavTask = task.caldavTask
val previousPosition = task.caldavSortOrder
if (newPosition != null) {
if (newParent == previousParent && newPosition < previousPosition) {
@ -282,28 +315,28 @@ GROUP BY caldav_lists.cdl_uuid
shiftDown(task.caldav!!, newParent, newPosition)
}
}
caldavTask.cd_order = newPosition
update(caldavTask.cd_id, caldavTask.cd_order)
task.task.order = newPosition
setTaskOrder(task.id, newPosition)
}
@Transaction
open suspend fun shiftDown(calendar: String, parent: Long, from: Long, to: Long? = null) {
val updated = ArrayList<CaldavTask>()
val updated = ArrayList<Task>()
val tasks = getTasksToShift(calendar, parent, from, to)
for (i in tasks.indices) {
val task = tasks[i]
val current = from + i
if (task.sortOrder == current) {
val caldavTask = task.caldavTask
caldavTask.order = current + 1
updated.add(caldavTask)
val task = task.task
task.order = current + 1
updated.add(task)
} else if (task.sortOrder > current) {
break
}
}
update(updated)
updateTasks(updated)
updated
.map(CaldavTask::task)
.map(Task::id)
.dbchunk()
.forEach { touchInternal(it) }
}
@ -311,7 +344,18 @@ GROUP BY caldav_lists.cdl_uuid
@Query("UPDATE tasks SET modified = :modificationTime WHERE _id in (:ids)")
internal abstract suspend fun touchInternal(ids: List<Long>, modificationTime: Long = now())
@Query("SELECT task.*, caldav_task.*, IFNULL(cd_order, (created - $APPLE_EPOCH) / 1000) AS primary_sort FROM caldav_tasks AS caldav_task INNER JOIN tasks AS task ON _id = cd_task WHERE cd_calendar = :calendar AND parent = :parent AND cd_deleted = 0 AND deleted = 0 AND primary_sort >= :from AND primary_sort < IFNULL(:to, ${Long.MAX_VALUE}) ORDER BY primary_sort")
@Query("""
SELECT task.*, caldav_task.*, IFNULL(`order`, (created - $APPLE_EPOCH) / 1000) AS primary_sort
FROM caldav_tasks AS caldav_task
INNER JOIN tasks AS task ON _id = cd_task
WHERE cd_calendar = :calendar
AND parent = :parent
AND cd_deleted = 0
AND deleted = 0
AND primary_sort >= :from
AND primary_sort < IFNULL(:to, ${Long.MAX_VALUE})
ORDER BY primary_sort
""")
internal abstract suspend fun getTasksToShift(calendar: String, parent: Long, from: Long, to: Long?): List<CaldavTaskContainer>
@Query("UPDATE caldav_lists SET cdl_order = $NO_ORDER")
@ -320,6 +364,9 @@ GROUP BY caldav_lists.cdl_uuid
@Query("UPDATE caldav_lists SET cdl_order = :order WHERE cdl_id = :id")
abstract suspend fun setOrder(id: Long, order: Int)
@Query("UPDATE tasks SET `order` = :order WHERE _id = :id")
abstract suspend fun setTaskOrder(id: Long, order: Long?)
suspend fun setupLocalAccount(context: Context): CaldavAccount = mutex.withLock {
val account = getLocalAccount()
getLocalList(context, account)

@ -1,10 +1,6 @@
package org.tasks.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.PrimaryKey
import androidx.room.*
import com.todoroo.andlib.data.Table
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.helper.UUIDHelper
@ -52,9 +48,11 @@ class CaldavTask {
@ColumnInfo(name = "cd_remote_parent")
var remoteParent: String? = null
@ColumnInfo(name = "cd_order")
@Transient
var order: Long? = null
@ColumnInfo(name = "gt_moved")
var isMoved: Boolean = false
@ColumnInfo(name = "gt_remote_order")
var remoteOrder: Long = 0
constructor()
@ -67,7 +65,7 @@ class CaldavTask {
}
@Ignore
constructor(task: Long, calendar: String?, remoteId: String?, `object`: String?) {
constructor(task: Long, calendar: String?, remoteId: String?, `object`: String? = null) {
this.task = task
this.calendar = calendar
this.remoteId = remoteId
@ -77,7 +75,7 @@ class CaldavTask {
fun isDeleted() = deleted > 0
override fun toString(): String =
"CaldavTask(id=$id, task=$task, calendar=$calendar, `object`=$`object`, remoteId=$remoteId, etag=$etag, lastSync=$lastSync, deleted=$deleted, remoteParent=$remoteParent, order=$order)"
"CaldavTask(id=$id, task=$task, calendar=$calendar, `object`=$`object`, remoteId=$remoteId, etag=$etag, lastSync=$lastSync, deleted=$deleted, remoteParent=$remoteParent, isMoved=$isMoved, remoteOrder=$remoteOrder)"
companion object {
const val KEY = "caldav"
@ -87,4 +85,4 @@ class CaldavTask {
@JvmField val DELETED = TABLE.column("cd_deleted")
@JvmField val CALENDAR = TABLE.column("cd_calendar")
}
}
}

@ -18,7 +18,7 @@ class CaldavTaskContainer {
get() = task.isDeleted
val sortOrder: Long
get() = caldavTask.order ?: DateTime(task.creationDate).toAppleEpoch()
get() = task.order ?: DateTime(task.creationDate).toAppleEpoch()
val startDate: Long
get() = task.hideUntil

@ -39,9 +39,6 @@ interface ContentProviderDao {
INNER JOIN caldav_accounts ON cdl_account = cda_uuid""")
fun getLists(): Cursor
@Query("SELECT * FROM google_task_lists")
fun getGoogleTaskLists(): Cursor
@RawQuery
fun rawQuery(query: SupportSQLiteQuery): Cursor
}

@ -24,7 +24,5 @@ class ContentProviderDaoBlocking @Inject constructor(private val dao: ContentPro
fun getLists(): Cursor = dao.getLists()
fun getGoogleTaskLists(): Cursor = dao.getGoogleTaskLists()
fun rawQuery(query: SupportSQLiteQuery): Cursor = dao.rawQuery(query)
}

@ -55,36 +55,6 @@ WHERE recurring = 1
ids.eachChunk(this::markDeletedInternal)
}
@Query("SELECT gt_task FROM google_tasks WHERE gt_deleted = 0 AND gt_list_id = :listId")
internal abstract suspend fun getActiveGoogleTasks(listId: String): List<Long>
@Delete
internal abstract suspend fun deleteGoogleTaskList(googleTaskList: GoogleTaskList)
@Transaction
open suspend fun delete(googleTaskList: GoogleTaskList): List<Long> {
val tasks = getActiveGoogleTasks(googleTaskList.remoteId!!)
delete(tasks)
deleteGoogleTaskList(googleTaskList)
return tasks
}
@Delete
internal abstract suspend fun deleteGoogleTaskAccount(googleTaskAccount: GoogleTaskAccount)
@Query("SELECT * FROM google_task_lists WHERE gtl_account = :account ORDER BY gtl_title ASC")
abstract suspend fun getLists(account: String): List<GoogleTaskList>
@Transaction
open suspend fun delete(googleTaskAccount: GoogleTaskAccount): List<Long> {
val deleted = ArrayList<Long>()
for (list in getLists(googleTaskAccount.account!!)) {
deleted.addAll(delete(list))
}
deleteGoogleTaskAccount(googleTaskAccount)
return deleted
}
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0")
internal abstract suspend fun getActiveCaldavTasks(calendar: String): List<Long>

@ -1,126 +1,15 @@
package org.tasks.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey
import com.todoroo.andlib.data.Table
import com.todoroo.astrid.data.Task
@Entity(
tableName = "google_tasks",
indices = [
Index(name = "gt_list_parent", value = ["gt_list_id", "gt_parent"])
],
foreignKeys = [
ForeignKey(
entity = Task::class,
parentColumns = ["_id"],
childColumns = ["gt_task"],
onDelete = ForeignKey.CASCADE,
),
]
)
class GoogleTask {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "gt_id")
@Transient
var id: Long = 0
@ColumnInfo(name = "gt_task", index = true)
@Transient
var task: Long = 0
@ColumnInfo(name = "gt_remote_id")
var remoteId: String? = ""
@ColumnInfo(name = "gt_list_id")
var listId: String? = ""
@ColumnInfo(name = "gt_parent")
@Transient
var parent: Long = 0
@ColumnInfo(name = "gt_remote_parent")
var remoteParent: String? = null
set(value) {
field = if (value?.isNotBlank() == true) value else null
}
@ColumnInfo(name = "gt_moved")
@Transient
var isMoved = false
@ColumnInfo(name = "gt_order")
@Transient
var order: Long = 0
@ColumnInfo(name = "gt_remote_order")
var remoteOrder: Long = 0
@ColumnInfo(name = "gt_last_sync")
var lastSync: Long = 0
@ColumnInfo(name = "gt_deleted")
var deleted: Long = 0
constructor()
@Ignore
constructor(task: Long, listId: String) {
this.task = task
this.listId = listId
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GoogleTask) return false
if (id != other.id) return false
if (task != other.task) return false
if (remoteId != other.remoteId) return false
if (listId != other.listId) return false
if (parent != other.parent) return false
if (remoteParent != other.remoteParent) return false
if (isMoved != other.isMoved) return false
if (order != other.order) return false
if (remoteOrder != other.remoteOrder) return false
if (lastSync != other.lastSync) return false
if (deleted != other.deleted) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + task.hashCode()
result = 31 * result + remoteId.hashCode()
result = 31 * result + listId.hashCode()
result = 31 * result + parent.hashCode()
result = 31 * result + (remoteParent?.hashCode() ?: 0)
result = 31 * result + isMoved.hashCode()
result = 31 * result + order.hashCode()
result = 31 * result + remoteOrder.hashCode()
result = 31 * result + lastSync.hashCode()
result = 31 * result + deleted.hashCode()
return result
}
override fun toString(): String =
"GoogleTask(id=$id, task=$task, remoteId='$remoteId', listId='$listId', parent=$parent, remoteParent=$remoteParent, isMoved=$isMoved, order=$order, remoteOrder=$remoteOrder, lastSync=$lastSync, deleted=$deleted)"
val isNew: Boolean
get() = id == 0L
@Deprecated("For backup use only")
data class GoogleTask(
var remoteId: String? = "",
var listId: String? = "",
var remoteParent: String? = null,
var remoteOrder: Long = 0,
var lastSync: Long = 0,
var deleted: Long = 0,
) {
companion object {
const val KEY = "gtasks"
@JvmField val TABLE = Table("google_tasks")
val ID = TABLE.column("gt_id")
@JvmField val PARENT = TABLE.column("gt_parent")
@JvmField val TASK = TABLE.column("gt_task")
@JvmField val DELETED = TABLE.column("gt_deleted")
@JvmField val LIST = TABLE.column("gt_list_id")
}
}

@ -1,98 +1,8 @@
package org.tasks.data
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ParcelCompat
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.todoroo.andlib.data.Table
@Entity(tableName = "google_task_accounts")
class GoogleTaskAccount : Parcelable {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "gta_id")
@Transient
var id: Long = 0
@ColumnInfo(name = "gta_account")
var account: String? = null
@ColumnInfo(name = "gta_error")
@Transient
var error: String? = ""
@ColumnInfo(name = "gta_etag")
var etag: String? = null
@ColumnInfo(name = "gta_collapsed")
var isCollapsed = false
constructor()
@Ignore
constructor(source: Parcel) {
id = source.readLong()
account = source.readString()
error = source.readString()
etag = source.readString()
isCollapsed = ParcelCompat.readBoolean(source)
}
@Ignore
constructor(account: String?) {
this.account = account
}
override fun describeContents() = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
with(dest) {
writeLong(id)
writeString(account)
writeString(error)
writeString(etag)
ParcelCompat.writeBoolean(this, isCollapsed)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GoogleTaskAccount) return false
if (id != other.id) return false
if (account != other.account) return false
if (error != other.error) return false
if (etag != other.etag) return false
if (isCollapsed != other.isCollapsed) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + (account?.hashCode() ?: 0)
result = 31 * result + (error?.hashCode() ?: 0)
result = 31 * result + (etag?.hashCode() ?: 0)
result = 31 * result + isCollapsed.hashCode()
return result
}
override fun toString(): String =
"GoogleTaskAccount(id=$id, account=$account, error=$error, etag=$etag, isCollapsed=$isCollapsed)"
val hasError: Boolean
get() = !error.isNullOrBlank()
companion object {
val TABLE = Table("google_task_accounts")
val ACCOUNT = TABLE.column("gta_account")
@JvmField val CREATOR: Parcelable.Creator<GoogleTaskAccount> = object : Parcelable.Creator<GoogleTaskAccount> {
override fun createFromParcel(source: Parcel): GoogleTaskAccount = GoogleTaskAccount(source)
override fun newArray(size: Int): Array<GoogleTaskAccount?> = arrayOfNulls(size)
}
}
}
@Deprecated("Only used for backup migration")
data class GoogleTaskAccount(
var account: String? = null,
var etag: String? = null,
var isCollapsed: Boolean = false,
)

@ -2,195 +2,179 @@ package org.tasks.data
import androidx.room.*
import com.todoroo.astrid.data.Task
import kotlinx.coroutines.flow.Flow
import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
@Dao
abstract class GoogleTaskDao {
@Insert
abstract suspend fun insert(task: GoogleTask): Long
abstract suspend fun insert(task: CaldavTask): Long
@Insert
abstract suspend fun insert(tasks: Iterable<GoogleTask>)
abstract suspend fun insert(tasks: Iterable<CaldavTask>)
@Transaction
open suspend fun insertAndShift(task: GoogleTask, top: Boolean) {
open suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) {
if (top) {
task.order = 0
shiftDown(task.listId!!, task.parent, 0)
shiftDown(caldavTask.calendar!!, task.parent, 0)
} else {
task.order = getBottom(task.listId!!, task.parent)
task.order = getBottom(caldavTask.calendar!!, task.parent)
}
task.id = insert(task)
caldavTask.id = insert(caldavTask)
update(task)
}
@Query("UPDATE google_tasks SET gt_order = gt_order + 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order >= :position")
@Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftDown(listId: String, parent: Long, position: Long)
@Query("UPDATE google_tasks SET gt_order = gt_order - 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order > :from AND gt_order <= :to")
@Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` > :from AND `order` <= :to AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftUp(listId: String, parent: Long, from: Long, to: Long)
@Query("UPDATE google_tasks SET gt_order = gt_order + 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order < :from AND gt_order >= :to")
@Query("UPDATE tasks SET `order` = `order` + 1 WHERE parent = :parent AND `order` < :from AND `order` >= :to AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftDown(listId: String, parent: Long, from: Long, to: Long)
@Query("UPDATE google_tasks SET gt_order = gt_order - 1 WHERE gt_list_id = :listId AND gt_parent = :parent AND gt_order >= :position")
@Query("UPDATE tasks SET `order` = `order` - 1 WHERE parent = :parent AND `order` >= :position AND _id IN (SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :listId)")
internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long)
@Transaction
open suspend fun move(task: SubsetGoogleTask, newParent: Long, newPosition: Long) {
open suspend fun move(task: Task, list: String, newParent: Long, newPosition: Long) {
val previousParent = task.parent
val previousPosition = task.order
val previousPosition = task.order!!
if (newParent == previousParent) {
if (previousPosition < newPosition) {
shiftUp(task.listId, newParent, previousPosition, newPosition)
shiftUp(list, newParent, previousPosition, newPosition)
} else {
shiftDown(task.listId, newParent, previousPosition, newPosition)
shiftDown(list, newParent, previousPosition, newPosition)
}
} else {
shiftUp(task.listId, previousParent, previousPosition)
shiftDown(task.listId, newParent, newPosition)
shiftUp(list, previousParent, previousPosition)
shiftDown(list, newParent, newPosition)
}
task.parent = newParent
task.order = newPosition
update(task)
setMoved(task.id, list)
}
@Query("UPDATE google_task_accounts SET gta_collapsed = :collapsed WHERE gta_id = :id")
abstract suspend fun setCollapsed(id: Long, collapsed: Boolean)
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted = 0 LIMIT 1")
abstract suspend fun getByTaskId(taskId: Long): GoogleTask?
@Query("UPDATE caldav_tasks SET gt_moved = 1 WHERE cd_task = :task and cd_calendar = :list")
internal abstract suspend fun setMoved(task: Long, list: String)
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted = 0 LIMIT 1")
abstract fun watchGoogleTask(taskId: Long): Flow<GoogleTask?>
@Query("SELECT caldav_tasks.* FROM caldav_tasks INNER JOIN caldav_lists ON cdl_uuid = cd_calendar INNER JOIN caldav_accounts ON cda_uuid = cdl_account WHERE cd_task = :taskId AND cd_deleted = 0 AND cda_account_type = $TYPE_GOOGLE_TASKS LIMIT 1")
abstract suspend fun getByTaskId(taskId: Long): CaldavTask?
@Update
abstract suspend fun update(googleTask: GoogleTask)
private suspend fun update(googleTask: SubsetGoogleTask) {
update(googleTask.id, googleTask.parent, googleTask.order)
}
@Query("UPDATE google_tasks SET gt_order = :order, gt_parent = :parent, gt_moved = 1 WHERE gt_id = :id")
abstract suspend fun update(id: Long, parent: Long, order: Long)
abstract suspend fun update(googleTask: CaldavTask)
@Query("UPDATE google_tasks SET gt_deleted = :now WHERE gt_task = :task OR gt_parent = :task")
abstract suspend fun markDeleted(task: Long, now: Long = currentTimeMillis())
@Update
abstract suspend fun update(task: Task)
@Delete
abstract suspend fun delete(deleted: GoogleTask)
abstract suspend fun delete(deleted: CaldavTask)
@Query("SELECT * FROM google_tasks WHERE gt_remote_id = :remoteId LIMIT 1")
abstract suspend fun getByRemoteId(remoteId: String): GoogleTask?
@Query("SELECT * FROM caldav_tasks WHERE cd_remote_id = :remoteId LIMIT 1")
abstract suspend fun getByRemoteId(remoteId: String): CaldavTask?
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted > 0")
abstract suspend fun getDeletedByTaskId(taskId: Long): List<GoogleTask>
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted > 0")
abstract suspend fun getDeletedByTaskId(taskId: Long): List<CaldavTask>
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId")
abstract suspend fun getAllByTaskId(taskId: Long): List<GoogleTask>
@Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId")
abstract suspend fun getAllByTaskId(taskId: Long): List<CaldavTask>
@Query("SELECT DISTINCT gt_list_id FROM google_tasks WHERE gt_deleted = 0 AND gt_task IN (:tasks)")
@Query("SELECT DISTINCT cd_calendar FROM caldav_tasks WHERE cd_deleted = 0 AND cd_task IN (:tasks)")
abstract suspend fun getLists(tasks: List<Long>): List<String>
@Query("SELECT gt_task FROM google_tasks WHERE gt_parent IN (:ids) AND gt_deleted = 0")
abstract suspend fun getChildren(ids: List<Long>): List<Long>
suspend fun hasRecurringParent(ids: List<Long>): List<Long> =
ids.chunkedMap { internalHasRecurringParent(it) }
@Query("""
SELECT gt_task
FROM google_tasks
INNER JOIN tasks ON gt_parent = _id
WHERE gt_task IN (:ids)
AND gt_deleted = 0
AND tasks.recurrence IS NOT NULL
AND tasks.recurrence != ''
AND tasks.completed = 0
""")
abstract suspend fun internalHasRecurringParent(ids: List<Long>): List<Long>
@Query("SELECT tasks.* FROM tasks JOIN google_tasks ON tasks._id = gt_task WHERE gt_parent = :taskId")
abstract suspend fun getChildTasks(taskId: Long): List<Task>
@Query("SELECT tasks.* FROM tasks JOIN google_tasks ON tasks._id = gt_parent WHERE gt_task = :taskId")
abstract suspend fun getParentTask(taskId: Long): Task?
@Query("SELECT * FROM google_tasks WHERE gt_parent = :id AND gt_deleted = 0")
abstract suspend fun getChildren(id: Long): List<GoogleTask>
@Query("SELECT IFNULL(MAX(gt_order), -1) + 1 FROM google_tasks WHERE gt_list_id = :listId AND gt_parent = :parent")
@Query("SELECT IFNULL(MAX(`order`), -1) + 1 FROM tasks INNER JOIN caldav_tasks ON cd_task = tasks._id WHERE cd_calendar = :listId AND parent = :parent")
abstract suspend fun getBottom(listId: String, parent: Long): Long
@Query("SELECT gt_remote_id FROM google_tasks JOIN tasks ON tasks._id = gt_task WHERE deleted = 0 AND gt_list_id = :listId AND gt_parent = :parent AND gt_order < :order AND gt_remote_id IS NOT NULL AND gt_remote_id != '' ORDER BY gt_order DESC")
@Query(
"""
SELECT cd_remote_id
FROM caldav_tasks
INNER JOIN tasks ON tasks._id = cd_task
WHERE deleted = 0
AND cd_calendar = :listId
AND parent = :parent
AND `order` < :order
AND cd_remote_id IS NOT NULL
AND cd_remote_id != ''
ORDER BY `order` DESC
"""
)
abstract suspend fun getPrevious(listId: String, parent: Long, order: Long): String?
@Query("SELECT gt_remote_id FROM google_tasks WHERE gt_task = :task")
@Query("SELECT cd_remote_id FROM caldav_tasks WHERE cd_task = :task")
abstract suspend fun getRemoteId(task: Long): String?
@Query("SELECT gt_task FROM google_tasks WHERE gt_remote_id = :remoteId")
@Query("SELECT cd_task FROM caldav_tasks WHERE cd_remote_id = :remoteId")
abstract suspend fun getTask(remoteId: String): Long?
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
@Query("SELECT google_tasks.*, gt_order AS primary_sort, NULL AS secondary_sort FROM google_tasks JOIN tasks ON tasks._id = gt_task WHERE gt_parent = 0 AND gt_list_id = :listId AND tasks.deleted = 0 UNION SELECT c.*, p.gt_order AS primary_sort, c.gt_order AS secondary_sort FROM google_tasks AS c LEFT JOIN google_tasks AS p ON c.gt_parent = p.gt_task JOIN tasks ON tasks._id = c.gt_task WHERE c.gt_parent > 0 AND c.gt_list_id = :listId AND tasks.deleted = 0 ORDER BY primary_sort ASC, secondary_sort ASC")
abstract suspend fun getByLocalOrder(listId: String): List<GoogleTask>
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH)
@Query("SELECT google_tasks.*, gt_remote_order AS primary_sort, NULL AS secondary_sort FROM google_tasks JOIN tasks ON tasks._id = gt_task WHERE gt_parent = 0 AND gt_list_id = :listId AND tasks.deleted = 0 UNION SELECT c.*, p.gt_remote_order AS primary_sort, c.gt_remote_order AS secondary_sort FROM google_tasks AS c LEFT JOIN google_tasks AS p ON c.gt_parent = p.gt_task JOIN tasks ON tasks._id = c.gt_task WHERE c.gt_parent > 0 AND c.gt_list_id = :listId AND tasks.deleted = 0 ORDER BY primary_sort ASC, secondary_sort ASC")
internal abstract suspend fun getByRemoteOrder(listId: String): List<GoogleTask>
@Query(
"""
SELECT tasks.*, `order` AS primary_sort, NULL AS secondary_sort
FROM tasks
INNER JOIN caldav_tasks ON tasks._id = cd_task
WHERE parent = 0
AND cd_calendar = :listId
AND tasks.deleted = 0
UNION
SELECT c.*, p.`order` AS primary_sort, c.`order` AS secondary_sort
FROM tasks AS c
INNER JOIN tasks AS p ON c.parent = p._id
INNER JOIN caldav_tasks ON c._id = cd_task
WHERE c.parent > 0
AND cd_calendar = :listId
AND c.deleted = 0
ORDER BY primary_sort ASC, secondary_sort ASC
"""
)
internal abstract suspend fun getByLocalOrder(listId: String): List<Task>
@Query(
"""
SELECT tasks.*, gt_remote_order AS primary_sort, NULL AS secondary_sort
FROM tasks
JOIN caldav_tasks ON tasks._id = cd_task
WHERE parent = 0
AND cd_calendar = :listId
AND tasks.deleted = 0
UNION
SELECT c.*, parent.gt_remote_order AS primary_sort, child.gt_remote_order AS secondary_sort
FROM tasks AS c
INNER JOIN tasks AS p ON c.parent = p._id
INNER JOIN caldav_tasks AS child ON c._id = child.cd_task
INNER JOIN caldav_tasks AS parent ON p._id = parent.cd_task
WHERE c.parent > 0
AND child.cd_calendar = :listId
AND c.deleted = 0
ORDER BY primary_sort ASC, secondary_sort ASC
"""
)
internal abstract suspend fun getByRemoteOrder(listId: String): List<Task>
@Query("""
UPDATE google_tasks
SET gt_parent = IFNULL((SELECT gt_task
FROM google_tasks AS p
WHERE google_tasks.gt_remote_parent IS NOT NULL
AND google_tasks.gt_remote_parent != ''
AND p.gt_remote_id = google_tasks.gt_remote_parent
AND p.gt_list_id = google_tasks.gt_list_id
AND p.gt_deleted = 0), 0)
WHERE gt_moved = 0
""")
abstract suspend fun updateParents()
@Query("""
UPDATE google_tasks
SET gt_parent = IFNULL((SELECT gt_task
FROM google_tasks AS p
WHERE google_tasks.gt_remote_parent IS NOT NULL
AND google_tasks.gt_remote_parent != ''
AND p.gt_remote_id = google_tasks.gt_remote_parent
AND p.gt_list_id = google_tasks.gt_list_id
AND p.gt_deleted = 0), 0)
WHERE gt_list_id = :listId
AND gt_moved = 0
""")
abstract suspend fun updateParents(listId: String)
@Query("""
UPDATE google_tasks
SET gt_remote_parent = CASE WHEN :parent == '' THEN NULL ELSE :parent END,
UPDATE caldav_tasks
SET cd_remote_parent = CASE WHEN :parent == '' THEN NULL ELSE :parent END,
gt_remote_order = :position
WHERE gt_remote_id = :id
WHERE cd_remote_id = :id
""")
abstract suspend fun updatePosition(id: String, parent: String?, position: String)
@Transaction
open suspend fun reposition(listId: String) {
updateParents(listId)
open suspend fun reposition(caldavDao: CaldavDao, listId: String) {
caldavDao.updateParents(listId)
val orderedTasks = getByRemoteOrder(listId)
var subtasks = 0L
var parent = 0L
for (task in orderedTasks) {
if (task.parent > 0) {
if (task.order != subtasks && !task.isMoved) {
if (task.order != subtasks) {
task.order = subtasks
update(task)
}
subtasks++
} else {
subtasks = 0
if (task.order != parent && !task.isMoved) {
if (task.order != parent) {
task.order = parent
update(task)
}

@ -1,126 +1,14 @@
package org.tasks.data
import android.os.Parcel
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import androidx.room.PrimaryKey
import com.todoroo.andlib.data.Table
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
import org.tasks.themes.CustomIcons.LIST
@Entity(tableName = "google_task_lists")
class GoogleTaskList : Parcelable {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "gtl_id")
@Transient
var id: Long = 0
@ColumnInfo(name = "gtl_account")
var account: String? = null
@ColumnInfo(name = "gtl_remote_id")
var remoteId: String? = null
@ColumnInfo(name = "gtl_title")
var title: String? = null
@ColumnInfo(name = "gtl_remote_order")
var order = NO_ORDER
@ColumnInfo(name = "gtl_last_sync")
var lastSync: Long = 0
@ColumnInfo(name = "gtl_color")
private var color: Int? = null
@ColumnInfo(name = "gtl_icon")
private var icon: Int? = -1
constructor()
@Ignore
constructor(parcel: Parcel) {
id = parcel.readLong()
account = parcel.readString()
remoteId = parcel.readString()
title = parcel.readString()
order = parcel.readInt()
lastSync = parcel.readLong()
color = parcel.readInt()
icon = parcel.readInt()
}
@Suppress("RedundantNullableReturnType")
fun getColor(): Int? = color ?: 0
fun setColor(color: Int?) {
this.color = color
}
@Suppress("RedundantNullableReturnType")
fun getIcon(): Int? = icon ?: LIST
fun setIcon(icon: Int?) {
this.icon = icon
}
override fun describeContents() = 0
override fun writeToParcel(parcel: Parcel, i: Int) {
with(parcel) {
writeLong(id)
writeString(account)
writeString(remoteId)
writeString(title)
writeInt(order)
writeLong(lastSync)
writeInt(getColor()!!)
writeInt(getIcon()!!)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is GoogleTaskList) return false
if (id != other.id) return false
if (account != other.account) return false
if (remoteId != other.remoteId) return false
if (title != other.title) return false
if (order != other.order) return false
if (lastSync != other.lastSync) return false
if (color != other.color) return false
if (icon != other.icon) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + (account?.hashCode() ?: 0)
result = 31 * result + (remoteId?.hashCode() ?: 0)
result = 31 * result + (title?.hashCode() ?: 0)
result = 31 * result + order
result = 31 * result + lastSync.hashCode()
result = 31 * result + (color ?: 0)
result = 31 * result + (icon ?: 0)
return result
}
override fun toString(): String =
"GoogleTaskList(id=$id, account=$account, remoteId=$remoteId, title=$title, remoteOrder=$order, lastSync=$lastSync, color=$color, icon=$icon)"
companion object {
@JvmField val TABLE = Table("google_task_lists")
val ACCOUNT = TABLE.column("gtl_account")
@JvmField val REMOTE_ID = TABLE.column("gtl_remote_id")
@JvmField val NAME = TABLE.column("gtl_title")
@JvmField val CREATOR: Parcelable.Creator<GoogleTaskList> = object : Parcelable.Creator<GoogleTaskList> {
override fun createFromParcel(parcel: Parcel): GoogleTaskList = GoogleTaskList(parcel)
override fun newArray(size: Int): Array<GoogleTaskList?> = arrayOfNulls(size)
}
}
}
@Deprecated("Only used for backup migration")
data class GoogleTaskList(
var account: String? = null,
var remoteId: String? = null,
var title: String? = null,
var order: Int = NO_ORDER,
var lastSync: Long = 0,
var color: Int? = null,
var icon: Int? = -1,
)

@ -1,78 +1,38 @@
package org.tasks.data
import androidx.lifecycle.LiveData
import androidx.room.*
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.filters.GoogleTaskFilters
import org.tasks.time.DateTimeUtils.currentTimeMillis
@Dao
interface GoogleTaskListDao {
@Query("SELECT * FROM google_task_accounts WHERE gta_id = :id")
fun watchAccount(id: Long): LiveData<GoogleTaskAccount>
@Query("SELECT * FROM caldav_accounts WHERE cda_account_type = $TYPE_GOOGLE_TASKS")
suspend fun getAccounts(): List<CaldavAccount>
@Query("SELECT COUNT(*) FROM google_task_accounts")
suspend fun accountCount(): Int
@Query("SELECT * FROM caldav_lists WHERE cdl_id = :id")
suspend fun getById(id: Long): CaldavCalendar?
@Query("SELECT * FROM google_task_accounts")
suspend fun getAccounts(): List<GoogleTaskAccount>
@Query("SELECT * FROM caldav_lists WHERE cdl_account = :account ORDER BY cdl_name ASC")
suspend fun getLists(account: String): List<CaldavCalendar>
@Query("SELECT * FROM google_task_accounts")
fun watchAccounts(): LiveData<List<GoogleTaskAccount>>
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :remoteId LIMIT 1")
suspend fun getByRemoteId(remoteId: String): CaldavCalendar?
@Query("SELECT * FROM google_task_accounts WHERE gta_account = :account COLLATE NOCASE LIMIT 1")
suspend fun getAccount(account: String): GoogleTaskAccount?
@Query("SELECT * FROM caldav_lists WHERE cdl_uuid IN (:remoteIds)")
suspend fun getByRemoteId(remoteIds: List<String>): List<CaldavCalendar>
@Query("SELECT * FROM google_task_lists WHERE gtl_id = :id")
suspend fun getById(id: Long): GoogleTaskList?
@Query("SELECT * FROM google_task_lists WHERE gtl_account = :account ORDER BY gtl_title ASC")
suspend fun getLists(account: String): List<GoogleTaskList>
@Query("SELECT * FROM google_task_lists WHERE gtl_remote_id = :remoteId LIMIT 1")
suspend fun getByRemoteId(remoteId: String): GoogleTaskList?
@Query("SELECT * FROM google_task_lists WHERE gtl_remote_id IN (:remoteIds)")
suspend fun getByRemoteId(remoteIds: List<String>): List<GoogleTaskList>
@Query("SELECT * FROM google_task_lists")
fun subscribeToLists(): LiveData<List<GoogleTaskList>>
@Query("SELECT * FROM google_task_lists WHERE gtl_remote_id = :remoteId AND IFNULL(gtl_account, '') = ''")
suspend fun findExistingList(remoteId: String): GoogleTaskList?
@Query("SELECT * FROM google_task_lists")
suspend fun getAllLists(): List<GoogleTaskList>
@Query("UPDATE google_task_lists SET gtl_last_sync = 0 WHERE gtl_account = :account")
@Query("UPDATE caldav_lists SET cdl_last_sync = 0 WHERE cdl_account = :account")
suspend fun resetLastSync(account: String)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrReplace(googleTaskList: GoogleTaskList): Long
@Insert
suspend fun insert(googleTaskList: GoogleTaskList): Long
@Insert
suspend fun insert(googleTaskAccount: GoogleTaskAccount)
@Update
suspend fun update(account: GoogleTaskAccount)
@Update
suspend fun update(list: GoogleTaskList)
@Query("SELECT google_task_lists.*, COUNT(tasks._id) AS count"
+ " FROM google_task_lists "
+ " LEFT JOIN google_tasks ON google_tasks.gt_list_id = google_task_lists.gtl_remote_id"
+ " LEFT JOIN tasks ON google_tasks.gt_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now AND gt_deleted = 0"
+ " WHERE google_task_lists.gtl_account = :account"
+ " GROUP BY google_task_lists.gtl_remote_id")
suspend fun insertOrReplace(googleTaskList: CaldavCalendar): Long
@Query("SELECT caldav_lists.*, COUNT(tasks._id) AS count"
+ " FROM caldav_lists "
+ " LEFT JOIN caldav_tasks ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid"
+ " LEFT JOIN tasks ON caldav_tasks.cd_task = tasks._id AND tasks.deleted = 0 AND tasks.completed = 0 AND tasks.hideUntil < :now AND cd_deleted = 0"
+ " WHERE caldav_lists.cdl_account = :account"
+ " GROUP BY caldav_lists.cdl_uuid")
suspend fun getGoogleTaskFilters(account: String, now: Long = currentTimeMillis()): List<GoogleTaskFilters>
@Query("UPDATE google_task_lists SET gtl_remote_order = $NO_ORDER")
suspend fun resetOrders()
@Query("UPDATE google_task_lists SET gtl_remote_order = :order WHERE gtl_id = :id")
suspend fun setOrder(id: Long, order: Int)
}

@ -1,31 +0,0 @@
package org.tasks.data
class SubsetCaldav {
var cd_id: Long = 0
var cd_calendar: String? = null
var cd_remote_parent: String? = null
var cd_order: Long? = null
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is SubsetCaldav) return false
if (cd_id != other.cd_id) return false
if (cd_calendar != other.cd_calendar) return false
if (cd_remote_parent != other.cd_remote_parent) return false
if (cd_order != other.cd_order) return false
return true
}
override fun hashCode(): Int {
var result = cd_id.hashCode()
result = 31 * result + (cd_calendar?.hashCode() ?: 0)
result = 31 * result + (cd_remote_parent?.hashCode() ?: 0)
result = 31 * result + cd_order.hashCode()
return result
}
override fun toString(): String =
"SubsetCaldav(cd_id=$cd_id, cd_calendar=$cd_calendar, cd_remote_parent=$cd_remote_parent, cd_order=$cd_order)"
}

@ -1,70 +0,0 @@
package org.tasks.data;
import java.util.Objects;
public class SubsetGoogleTask {
public long gt_id;
public long gt_parent;
public String gt_list_id;
public long gt_order;
public long getId() {
return gt_id;
}
public String getListId() {
return gt_list_id;
}
public long getParent() {
return gt_parent;
}
public void setParent(long parent) {
gt_parent = parent;
}
public long getOrder() {
return gt_order;
}
public void setOrder(long order) {
gt_order = order;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SubsetGoogleTask)) {
return false;
}
SubsetGoogleTask that = (SubsetGoogleTask) o;
return gt_id == that.gt_id
&& gt_parent == that.gt_parent
&& gt_order == that.gt_order
&& Objects.equals(gt_list_id, that.gt_list_id);
}
@Override
public int hashCode() {
return Objects.hash(gt_id, gt_parent, gt_list_id, gt_order);
}
@Override
public String toString() {
return "SubsetGoogleTask{"
+ "gt_id="
+ gt_id
+ ", gt_parent="
+ gt_parent
+ ", gt_list_id='"
+ gt_list_id
+ '\''
+ ", gt_order="
+ gt_order
+ '}';
}
}

@ -2,9 +2,7 @@ package org.tasks.data;
public class SubtaskInfo {
public boolean hasSubtasks;
public boolean hasGoogleSubtasks;
public boolean usesSubtasks() {
return hasSubtasks || hasGoogleSubtasks;
return hasSubtasks;
}
}

@ -7,9 +7,9 @@ import java.util.Objects;
public class TaskContainer {
@Embedded public Task task;
@Embedded public SubsetGoogleTask googletask;
@Embedded public SubsetCaldav caldavTask;
@Embedded public CaldavTask caldavTask;
@Embedded public Location location;
public boolean isGoogleTask;
public boolean parentComplete;
public String tags;
public int children;
@ -23,16 +23,11 @@ public class TaskContainer {
return tags;
}
public @Nullable String getGoogleTaskList() {
return isGoogleTask() ? googletask.getListId() : null;
}
public boolean isGoogleTask() {
return googletask != null;
}
public @Nullable String getCaldav() {
return isCaldavTask() ? caldavTask.getCd_calendar() : null;
if (caldavTask != null) {
return caldavTask.getCalendar();
}
return null;
}
public boolean isCaldavTask() {
@ -127,7 +122,6 @@ public class TaskContainer {
&& indent == that.indent
&& targetIndent == that.targetIndent
&& Objects.equals(task, that.task)
&& Objects.equals(googletask, that.googletask)
&& Objects.equals(caldavTask, that.caldavTask)
&& Objects.equals(location, that.location)
&& Objects.equals(tags, that.tags)
@ -138,7 +132,6 @@ public class TaskContainer {
public int hashCode() {
return Objects.hash(
task,
googletask,
caldavTask,
location,
tags,
@ -155,8 +148,6 @@ public class TaskContainer {
return "TaskContainer{"
+ "task="
+ task
+ ", googletask="
+ googletask
+ ", caldavTask="
+ caldavTask
+ ", location="
@ -184,20 +175,11 @@ public class TaskContainer {
}
public long getParent() {
if (googletask != null) {
return googletask.getParent();
} else {
return task.getParent();
}
return task.getParent();
}
public void setParent(long parent) {
if (googletask != null) {
task.setParent(0);
googletask.setParent(parent);
} else {
task.setParent(parent);
}
task.setParent(parent);
}
public boolean hasParent() {
@ -208,11 +190,7 @@ public class TaskContainer {
return children > 0;
}
public SubsetGoogleTask getGoogleTask() {
return googletask;
}
public SubsetCaldav getCaldavTask() {
public CaldavTask getCaldavTask() {
return caldavTask;
}

@ -1,12 +1,7 @@
package org.tasks.data
import androidx.paging.DataSource
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
import androidx.room.withTransaction
import androidx.room.*
import androidx.sqlite.db.SimpleSQLiteQuery
import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Field
@ -66,10 +61,11 @@ abstract class TaskDao(private val database: Database) {
abstract suspend fun setCompletionDate(remoteIds: List<String>, completionDate: Long, updateTime: Long = now())
@Query("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 = '' OR google_tasks.gt_deleted > 0) "
+ "ORDER BY CASE WHEN gt_parent = 0 THEN 0 ELSE 1 END, gt_order ASC")
+ "LEFT JOIN caldav_tasks ON tasks._id = caldav_tasks.cd_task "
+ "LEFT JOIN caldav_lists ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid "
+ "WHERE cdl_account = :account "
+ "AND (tasks.modified > caldav_tasks.cd_last_sync OR caldav_tasks.cd_remote_id = '' OR caldav_tasks.cd_remote_id IS NULL OR caldav_tasks.cd_deleted > 0) "
+ "ORDER BY CASE WHEN parent = 0 THEN 0 ELSE 1 END, `order` ASC")
abstract suspend fun getGoogleTasksToPush(account: String): List<Task>
@Query("""
@ -131,15 +127,7 @@ abstract class TaskDao(private val database: Database) {
@RawQuery
abstract suspend fun count(query: SimpleSQLiteQuery): Int
@Query("""
SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks,
EXISTS(SELECT 1
FROM google_tasks
INNER JOIN tasks ON gt_task = _id
WHERE deleted = 0
AND gt_parent > 0
AND gt_deleted = 0) AS hasGoogleSubtasks
""")
@Query("SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks")
abstract suspend fun getSubtaskInfo(): SubtaskInfo
@RawQuery(observedEntities = [Place::class])
@ -151,6 +139,9 @@ SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtas
@Query("UPDATE tasks SET modified = :now WHERE _id in (:ids)")
internal abstract suspend fun internalTouch(ids: List<Long>, now: Long = currentTimeMillis())
@Query("UPDATE tasks SET `order` = :order WHERE _id = :id")
internal abstract suspend fun setOrder(id: Long, order: Long?)
suspend fun setParent(parent: Long, tasks: List<Long>) =
tasks.eachChunk { setParentInternal(parent, it) }
@ -250,10 +241,8 @@ FROM recursive_tasks
@Query("""
SELECT _id
FROM tasks
LEFT JOIN google_tasks ON _id = gt_task AND gt_deleted = 0
LEFT JOIN caldav_tasks ON _id = cd_task AND cd_deleted = 0
WHERE gt_id IS NULL
AND cd_id IS NULL
WHERE cd_id IS NULL
AND parent = 0
""")
abstract suspend fun getLocalTasks(): List<Long>

@ -6,27 +6,26 @@ import com.todoroo.andlib.sql.Join
import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.data.Task
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.TaskListQueryNonRecursive.getNonRecursiveQuery
import org.tasks.data.TaskListQueryRecursive.getRecursiveQuery
import org.tasks.preferences.QueryPreferences
object TaskListQuery {
private val JOIN_GTASK = Criterion.and(
Task.ID.eq(field("${TaskListFragment.GTASK_METADATA_JOIN}.gt_task")),
field("${TaskListFragment.GTASK_METADATA_JOIN}.gt_deleted").eq(0))
private val JOIN_CALDAV = Criterion.and(
Task.ID.eq(field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_task")),
field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_deleted").eq(0))
val JOINS = """
${Join.left(GoogleTask.TABLE.`as`(TaskListFragment.GTASK_METADATA_JOIN), JOIN_GTASK)}
${Join.left(CaldavTask.TABLE.`as`(TaskListFragment.CALDAV_METADATA_JOIN), JOIN_CALDAV)}
${Join.left(CaldavCalendar.TABLE, field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_calendar").eq(CaldavCalendar.UUID))}
${Join.left(CaldavAccount.TABLE, CaldavCalendar.ACCOUNT.eq(CaldavAccount.UUID))}
${Join.left(Geofence.TABLE, Geofence.TASK.eq(Task.ID))}
${Join.left(Place.TABLE, Place.UID.eq(Geofence.PLACE))}
""".trimIndent()
val FIELDS = listOf(
field("tasks.*"),
field("${TaskListFragment.GTASK_METADATA_JOIN}.*"),
field("${TaskListFragment.CALDAV_METADATA_JOIN}.*"),
field("CASE ${CaldavAccount.ACCOUNT_TYPE} WHEN $TYPE_GOOGLE_TASKS THEN 1 ELSE 0 END").`as`("isGoogleTask"),
field("geofences.*"),
field("places.*"))
@ -37,11 +36,11 @@ object TaskListQuery {
subtasks: SubtaskInfo
): MutableList<String> = when {
filter.supportsManualSort() && preferences.isManualSort ->
getRecursiveQuery(filter, preferences, subtasks)
getRecursiveQuery(filter, preferences)
filter.supportsAstridSorting() && preferences.isAstridSort ->
getNonRecursiveQuery(filter, preferences)
filter.supportsSubtasks() && subtasks.usesSubtasks() && !preferences.usePagedQueries() ->
getRecursiveQuery(filter, preferences, subtasks)
getRecursiveQuery(filter, preferences)
else -> getNonRecursiveQuery(filter, preferences)
}
}

@ -33,47 +33,27 @@ internal object TaskListQueryRecursive {
LEFT JOIN (SELECT parent, count(distinct recursive_tasks.task) AS children FROM recursive_tasks GROUP BY parent) AS recursive_children ON recursive_children.parent = tasks._id
${TaskListQuery.JOINS}
""".trimIndent()
private val GOOGLE_SUBTASKS =
private val SUBTASK_QUERY =
QueryTemplate()
.join(Join.inner(RECURSIVE, GoogleTask.PARENT.eq(RECURSIVE_TASK)))
.join(Join.inner(GoogleTask.TABLE, Criterion.and(GoogleTask.TASK.eq(Task.ID), GoogleTask.DELETED.eq(0))))
.where(activeAndVisible())
private val ALL_SUBTASKS =
QueryTemplate()
.join(Join.inner(RECURSIVE, Criterion.or(GoogleTask.PARENT.eq(RECURSIVE_TASK), Task.PARENT.eq(RECURSIVE_TASK))))
.join(Join.left(GoogleTask.TABLE, Criterion.and(GoogleTask.TASK.eq(Task.ID), GoogleTask.DELETED.eq(0))))
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.where(activeAndVisible())
fun getRecursiveQuery(
filter: Filter,
preferences: QueryPreferences,
subtasks: SubtaskInfo
): MutableList<String> {
var joinedQuery = JOINS
var where = " WHERE recursive_tasks.hidden = 0"
val parentQuery: String
val subtaskQuery: QueryTemplate
when (filter) {
is CaldavFilter -> {
parentQuery = newCaldavQuery(filter)
subtaskQuery = QueryTemplate()
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.join(Join.inner(CaldavTask.TABLE, Criterion.and(CaldavTask.TASK.eq(Task.ID), CaldavTask.DELETED.eq(0))))
.where(activeAndVisible())
parentQuery = newCaldavQuery(filter.uuid)
}
is GtasksFilter -> {
parentQuery = newGoogleTaskQuery(filter)
subtaskQuery = GOOGLE_SUBTASKS
parentQuery = newCaldavQuery(filter.list.uuid!!)
}
else -> {
parentQuery = PermaSql.replacePlaceholdersForQuery(filter.getSqlQuery())
subtaskQuery = when {
subtasks.hasGoogleSubtasks && subtasks.hasSubtasks -> ALL_SUBTASKS
subtasks.hasGoogleSubtasks -> GOOGLE_SUBTASKS
else -> QueryTemplate()
.join(Join.inner(RECURSIVE, Task.PARENT.eq(RECURSIVE_TASK)))
.where(activeAndVisible())
}
joinedQuery += " LEFT JOIN (SELECT task, max(indent) AS max_indent FROM recursive_tasks GROUP BY task) AS recursive_indents ON recursive_indents.task = tasks._id "
where += " AND indent = max_indent "
}
@ -85,7 +65,7 @@ internal object TaskListQueryRecursive {
when {
manualSort && filter is GtasksFilter -> {
sortMode = SortHelper.SORT_GTASKS
sortField = "google_tasks.gt_order"
sortField = "tasks.`order`"
}
manualSort && filter is CaldavFilter -> {
sortMode = SortHelper.SORT_CALDAV
@ -111,7 +91,7 @@ internal object TaskListQueryRecursive {
SELECT tasks._id, $parentCompleted as parent_complete, 0 as subtask_complete, $completionSort as completion_sort, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, $sortSelect, $sortField as primary_sort, NULL as secondarySort, ${SortHelper.getSortGroup(sortMode)} FROM tasks
$parentQuery
UNION ALL SELECT tasks._id, recursive_tasks.parent_complete, $parentCompleted as subtask_complete, $completionSort as completion_sort, recursive_tasks.task as parent, tasks.collapsed as collapsed, CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END as hidden, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, $sortSelect, recursive_tasks.primary_sort as primary_sort, $sortField as secondary_sort, recursive_tasks.sort_group FROM tasks
$subtaskQuery
$SUBTASK_QUERY
ORDER BY parent_complete ASC, sort_indent DESC, subtask_complete ASC, completion_sort DESC, ${SortHelper.orderForSortTypeRecursive(sortMode, reverseSort)}
) SELECT * FROM recursive_tasks
""".trimIndent()
@ -127,26 +107,14 @@ internal object TaskListQueryRecursive {
.toString())
}
private fun newCaldavQuery(filter: CaldavFilter) =
private fun newCaldavQuery(list: String) =
QueryTemplate()
.join(Join.inner(
CaldavTask.TABLE,
Criterion.and(
CaldavTask.CALENDAR.eq(filter.uuid),
CaldavTask.CALENDAR.eq(list),
CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0))))
.where(Criterion.and(activeAndVisible(), Task.PARENT.eq(0)))
.toString()
private fun newGoogleTaskQuery(filter: GtasksFilter) =
QueryTemplate()
.join(Join.inner(
GoogleTask.TABLE,
Criterion.and(
GoogleTask.LIST.eq(filter.remoteId),
GoogleTask.PARENT.eq(0),
GoogleTask.TASK.eq(Task.ID),
GoogleTask.DELETED.eq(0))))
.where(activeAndVisible())
.toString()
}

@ -16,6 +16,8 @@ import org.tasks.data.Alarm.Companion.TYPE_REL_END
import org.tasks.data.Alarm.Companion.TYPE_REL_START
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.CaldavCalendar.Companion.ACCESS_OWNER
import org.tasks.data.CaldavCalendar.Companion.ACCESS_READ_ONLY
import org.tasks.data.OpenTaskDao.Companion.getLong
import org.tasks.extensions.getLongOrNull
@ -588,6 +590,27 @@ object Migrations {
}
}
private val MIGRATION_87_88 = object : Migration(87, 88) {
override fun migrate(database: SupportSQLiteDatabase) {
// migrate google task accounts and lists to caldav table
database.execSQL("ALTER TABLE `caldav_lists` ADD COLUMN `cdl_last_sync` INTEGER NOT NULL DEFAULT 0")
database.execSQL("INSERT INTO `caldav_accounts` (`cda_account_type`, `cda_server_type`, `cda_uuid`, `cda_name`, `cda_username`, `cda_collapsed`) SELECT $TYPE_GOOGLE_TASKS, $SERVER_UNKNOWN, `gta_account`, `gta_account`, `gta_account`, `gta_collapsed` FROM `google_task_accounts`")
database.execSQL("INSERT INTO `caldav_lists` (`cdl_account`, `cdl_uuid`, `cdl_name`, `cdl_color`, `cdl_icon`, `cdl_order`, `cdl_access`, `cdl_last_sync`) SELECT `gtl_account`, `gtl_remote_id`, `gtl_title`, `gtl_color`, `gtl_icon`, `gtl_remote_order`, $ACCESS_OWNER, `gtl_last_sync` FROM `google_task_lists`")
database.execSQL("DROP TABLE `google_task_accounts`")
database.execSQL("DROP TABLE `google_task_lists`")
// move cd_order to task table
database.execSQL("ALTER TABLE `tasks` ADD COLUMN `order` INTEGER")
database.execSQL("ALTER TABLE `caldav_tasks` RENAME TO `caldav-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_remote_parent` TEXT, `gt_moved` INTEGER NOT NULL, `gt_remote_order` INTEGER NOT NULL, FOREIGN KEY(`cd_task`) REFERENCES `tasks`(`_id`) ON UPDATE NO ACTION ON DELETE CASCADE)")
database.execSQL("DROP INDEX `index_caldav_tasks_cd_task`")
database.execSQL("CREATE INDEX IF NOT EXISTS `index_caldav_tasks_cd_task` ON `caldav_tasks` (`cd_task`)")
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_remote_parent`, `gt_moved`, `gt_remote_order`) SELECT `cd_id`, `cd_task`, `cd_calendar`, `cd_object`, `cd_remote_id`, `cd_etag`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, 0, 0 FROM `caldav-temp`")
database.execSQL("DROP TABLE `caldav-temp`")
database.execSQL("INSERT INTO `caldav_tasks` (`cd_task`, `cd_calendar`, `cd_remote_id`, `cd_last_sync`, `cd_deleted`, `cd_remote_parent`, `gt_moved`, `gt_remote_order`) SELECT `gt_task`, `gt_list_id`, `gt_remote_id`, `gt_last_sync`, `gt_deleted`, `gt_remote_parent`, 0, 0 FROM google_tasks")
database.execSQL("DROP TABLE `google_tasks`")
}
}
fun migrations(fileStorage: FileStorage) = arrayOf(
MIGRATION_35_36,
MIGRATION_36_37,
@ -631,6 +654,7 @@ object Migrations {
MIGRATION_84_85,
MIGRATION_85_86,
MIGRATION_86_87,
MIGRATION_87_88,
)
private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) {

@ -19,7 +19,6 @@ import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.dialogs.FilterPicker.Companion.EXTRA_LISTS_ONLY
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader
@ -37,7 +36,6 @@ class FilterPickerViewModel @Inject constructor(
private val inventory: Inventory,
private val colorProvider: ColorProvider,
private val preferences: Preferences,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao,
) : ViewModel() {
private val listsOnly = savedStateHandle[EXTRA_LISTS_ONLY] ?: false
@ -70,8 +68,7 @@ class FilterPickerViewModel @Inject constructor(
when (subheader.subheaderType) {
NavigationDrawerSubheader.SubheaderType.PREFERENCE ->
preferences.setBoolean(subheader.id.toInt(), collapsed)
NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS ->
googleTaskDao.setCollapsed(subheader.id, collapsed)
NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS,
NavigationDrawerSubheader.SubheaderType.CALDAV,
NavigationDrawerSubheader.SubheaderType.TASKS,
NavigationDrawerSubheader.SubheaderType.ETESYNC ->

@ -6,25 +6,13 @@ import com.todoroo.andlib.sql.Criterion.Companion.exists
import com.todoroo.andlib.sql.Criterion.Companion.or
import com.todoroo.andlib.sql.Field.Companion.field
import com.todoroo.andlib.sql.Join.Companion.inner
import com.todoroo.andlib.sql.Join.Companion.left
import com.todoroo.andlib.sql.Query.Companion.select
import com.todoroo.andlib.sql.UnaryCriterion.Companion.isNotNull
import com.todoroo.astrid.api.BooleanCriterion
import com.todoroo.astrid.api.CustomFilterCriterion
import com.todoroo.astrid.api.MultipleSelectCriterion
import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.api.TextInputCriterion
import com.todoroo.astrid.api.*
import com.todoroo.astrid.data.Task
import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R
import org.tasks.data.Alarm
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.Tag
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.data.*
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import javax.inject.Inject
@ -147,12 +135,8 @@ class FilterCriteriaProvider @Inject constructor(
context.getString(R.string.custom_filter_has_subtask),
select(Task.ID)
.from(Task.TABLE)
.join(left(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent"))))
.join(left(GoogleTask.TABLE, GoogleTask.PARENT.eq(Task.ID)))
.where(or(
isNotNull(field("children._id")),
isNotNull(GoogleTask.ID)
))
.join(inner(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent"))))
.where(isNotNull(field("children._id")))
.toString()
)
@ -162,11 +146,7 @@ class FilterCriteriaProvider @Inject constructor(
context.getString(R.string.custom_filter_is_subtask),
select(Task.ID)
.from(Task.TABLE)
.join(left(GoogleTask.TABLE, GoogleTask.TASK.eq(Task.ID)))
.where(or(
field("${Task.PARENT}>0").eq(1),
field("${GoogleTask.PARENT}>0").eq(1)
))
.where(field("${Task.PARENT}>0").eq(1))
.toString()
)
@ -297,26 +277,26 @@ class FilterCriteriaProvider @Inject constructor(
r.getString(R.string.CFC_title_contains_name))
private suspend fun gtasksFilterCriteria(): CustomFilterCriterion {
val lists = googleTaskListDao.getAllLists()
val lists = caldavDao.getGoogleTaskLists()
val listNames = arrayOfNulls<String>(lists.size)
val listIds = arrayOfNulls<String>(lists.size)
for (i in lists.indices) {
listNames[i] = lists[i].title
listIds[i] = lists[i].remoteId
listNames[i] = lists[i].name
listIds[i] = lists[i].uuid
}
val values: MutableMap<String, Any> = HashMap()
values[GoogleTask.KEY] = "?"
return MultipleSelectCriterion(
IDENTIFIER_GTASKS,
context.getString(R.string.CFC_gtasks_list_text),
select(GoogleTask.TASK)
.from(GoogleTask.TABLE)
.join(inner(Task.TABLE, GoogleTask.TASK.eq(Task.ID)))
select(CaldavTask.TASK)
.from(CaldavTask.TABLE)
.join(inner(Task.TABLE, CaldavTask.TASK.eq(Task.ID)))
.where(
and(
activeAndVisible(),
GoogleTask.DELETED.eq(0),
GoogleTask.LIST.eq("?")))
CaldavTask.DELETED.eq(0),
CaldavTask.CALENDAR.eq("?")))
.toString(),
values,
listNames,

@ -39,7 +39,7 @@ class FilterProvider @Inject constructor(
private val locationDao: LocationDao) {
suspend fun listPickerItems(): List<FilterListItem> =
googleTaskFilters(false).plus(caldavFilters(false))
caldavFilters(false)
suspend fun navDrawerItems(): List<FilterListItem> =
getAllFilters(hideUnused = true).plus(navDrawerFooter)
@ -166,7 +166,6 @@ class FilterProvider @Inject constructor(
.plus(addFilters(showCreate, showBuiltIn))
.plus(addTags(showCreate, hideUnused))
.plus(addPlaces(showCreate, hideUnused))
.plus(googleTaskFilters(showCreate))
.plus(caldavFilters(showCreate))
.toList()
.plusAllIf(BuildConfig.DEBUG) { getDebugFilters() }
@ -201,13 +200,10 @@ class FilterProvider @Inject constructor(
Intent(context, HelpAndFeedback::class.java),
0))
private suspend fun googleTaskFilters(showCreate: Boolean = true): List<FilterListItem> =
googleTaskListDao.getAccounts().flatMap { googleTaskFilter(it, showCreate) }
private suspend fun googleTaskFilter(account: GoogleTaskAccount, showCreate: Boolean): List<FilterListItem> =
private suspend fun googleTaskFilter(account: CaldavAccount, showCreate: Boolean): List<FilterListItem> =
listOf(
NavigationDrawerSubheader(
account.account,
account.username,
account.error?.isNotBlank() ?: false,
account.isCollapsed,
SubheaderType.GOOGLE_TASKS,
@ -221,7 +217,7 @@ class FilterProvider @Inject constructor(
}))
.apply { if (account.isCollapsed) return this }
.plus(googleTaskListDao
.getGoogleTaskFilters(account.account!!)
.getGoogleTaskFilters(account.username!!)
.map(GoogleTaskFilters::toGtasksFilter)
.sort())
@ -229,7 +225,16 @@ class FilterProvider @Inject constructor(
caldavDao.getAccounts()
.ifEmpty { listOf(caldavDao.setupLocalAccount(context)) }
.filter { it.accountType != TYPE_LOCAL || preferences.getBoolean(R.string.p_lists_enabled, true) }
.flatMap { caldavFilter(it, showCreate && it.accountType != TYPE_OPENTASKS && it.accountType != TYPE_ETESYNC) }
.flatMap {
if (it.isGoogleTasks) {
googleTaskFilter(it, showCreate)
} else {
caldavFilter(
it,
showCreate && it.accountType != TYPE_OPENTASKS && it.accountType != TYPE_ETESYNC
)
}
}
private suspend fun caldavFilter(account: CaldavAccount, showCreate: Boolean): List<FilterListItem> =
listOf(

@ -1,12 +1,15 @@
package org.tasks.filters;
import androidx.room.Embedded;
import com.todoroo.astrid.api.GtasksFilter;
import org.tasks.data.CaldavCalendar;
import java.util.Objects;
import org.tasks.data.GoogleTaskList;
public class GoogleTaskFilters {
@Embedded public GoogleTaskList googleTaskList;
@Embedded public CaldavCalendar googleTaskList;
public int count;
GtasksFilter toGtasksFilter() {

@ -43,6 +43,7 @@ import kotlin.math.max
class GoogleTaskSynchronizer @Inject constructor(
@param:ApplicationContext private val context: Context,
private val googleTaskListDao: GoogleTaskListDao,
private val caldavDao: CaldavDao,
private val gtasksListService: GtasksListService,
private val preferences: Preferences,
private val taskDao: TaskDao,
@ -58,7 +59,7 @@ class GoogleTaskSynchronizer @Inject constructor(
private val invokers: InvokerFactory,
private val alarmDao: AlarmDao,
) {
suspend fun sync(account: GoogleTaskAccount, i: Int) {
suspend fun sync(account: CaldavAccount, i: Int) {
Timber.d("%s: start sync", account)
try {
if (i == 0 || inventory.hasPro) {
@ -94,20 +95,20 @@ class GoogleTaskSynchronizer @Inject constructor(
account.error = e.message
firebase.reportException(e)
} finally {
googleTaskListDao.update(account)
caldavDao.update(account)
localBroadcastManager.broadcastRefreshList()
Timber.d("%s: end sync", account)
}
}
@Throws(IOException::class)
private suspend fun synchronize(account: GoogleTaskAccount) {
private suspend fun synchronize(account: CaldavAccount) {
if (!permissionChecker.canAccessAccounts()
|| googleAccountManager.getAccount(account.account) == null) {
|| googleAccountManager.getAccount(account.username) == null) {
account.error = context.getString(R.string.cannot_access_account)
return
}
val gtasksInvoker = invokers.getGtasksInvoker(account.account!!)
val gtasksInvoker = invokers.getGtasksInvoker(account.username!!)
pushLocalChanges(account, gtasksInvoker)
val gtaskLists: MutableList<TaskList> = ArrayList()
var nextPageToken: String? = null
@ -130,13 +131,13 @@ class GoogleTaskSynchronizer @Inject constructor(
}
}
for (list in googleTaskListDao.getByRemoteId(gtaskLists.map { it.id })) {
if (isNullOrEmpty(list.remoteId)) {
if (isNullOrEmpty(list.uuid)) {
firebase.reportException(RuntimeException("Empty remote id"))
continue
}
fetchAndApplyRemoteChanges(gtasksInvoker, list)
if (!preferences.isPositionHackEnabled) {
googleTaskDao.reposition(list.remoteId!!)
googleTaskDao.reposition(caldavDao, list.uuid!!)
}
}
if (preferences.isPositionHackEnabled) {
@ -145,10 +146,10 @@ class GoogleTaskSynchronizer @Inject constructor(
for (task in tasks) {
googleTaskDao.updatePosition(task.id, task.parent, task.position)
}
googleTaskDao.reposition(list.id)
googleTaskDao.reposition(caldavDao, list.id)
}
}
account.etag = eTag
// account.etag = eTag
account.error = ""
}
@ -168,8 +169,8 @@ class GoogleTaskSynchronizer @Inject constructor(
}
@Throws(IOException::class)
private suspend fun pushLocalChanges(account: GoogleTaskAccount, gtasksInvoker: GtasksInvoker) {
val tasks = taskDao.getGoogleTasksToPush(account.account!!)
private suspend fun pushLocalChanges(account: CaldavAccount, gtasksInvoker: GtasksInvoker) {
val tasks = taskDao.getGoogleTasksToPush(account.uuid!!)
for (task in tasks) {
pushTask(task, gtasksInvoker)
}
@ -178,7 +179,7 @@ class GoogleTaskSynchronizer @Inject constructor(
@Throws(IOException::class)
private suspend fun pushTask(task: com.todoroo.astrid.data.Task, gtasksInvoker: GtasksInvoker) {
for (deleted in googleTaskDao.getDeletedByTaskId(task.id)) {
gtasksInvoker.deleteGtask(deleted.listId, deleted.remoteId)
gtasksInvoker.deleteGtask(deleted.calendar, deleted.remoteId)
googleTaskDao.delete(deleted)
}
val gtasksMetadata = googleTaskDao.getByTaskId(task.id) ?: return
@ -188,14 +189,14 @@ class GoogleTaskSynchronizer @Inject constructor(
val defaultRemoteList = defaultFilterProvider.defaultList
var listId = if (defaultRemoteList is GtasksFilter) defaultRemoteList.remoteId else DEFAULT_LIST
if (isNullOrEmpty(gtasksMetadata.remoteId)) { // Create case
val selectedList = gtasksMetadata.listId
val selectedList = gtasksMetadata.calendar
if (!isNullOrEmpty(selectedList)) {
listId = selectedList
}
newlyCreated = true
} else { // update case
remoteId = gtasksMetadata.remoteId
listId = gtasksMetadata.listId
listId = gtasksMetadata.calendar
remoteModel.id = remoteId
}
@ -223,12 +224,11 @@ class GoogleTaskSynchronizer @Inject constructor(
remoteModel.status = "needsAction" // $NON-NLS-1$
}
if (newlyCreated) {
val parent = gtasksMetadata.parent
val parent = task.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious(
listId!!, if (isNullOrEmpty(localParent)) 0 else parent, gtasksMetadata.order)
val created: Task?
created = try {
listId!!, if (isNullOrEmpty(localParent)) 0 else parent, task.order ?: 0)
val created: Task? = try {
gtasksInvoker.createGtask(listId, remoteModel, localParent, previous)
} catch (e: HttpNotFoundException) {
gtasksInvoker.createGtask(listId, remoteModel, null, null)
@ -236,8 +236,8 @@ class GoogleTaskSynchronizer @Inject constructor(
if (created != null) {
// Update the metadata for the newly created task
gtasksMetadata.remoteId = created.id
gtasksMetadata.listId = listId
setOrderAndParent(gtasksMetadata, created)
gtasksMetadata.calendar = listId
setOrderAndParent(gtasksMetadata, created, task)
} else {
return
}
@ -245,15 +245,15 @@ class GoogleTaskSynchronizer @Inject constructor(
try {
if (!task.isDeleted && gtasksMetadata.isMoved) {
try {
val parent = gtasksMetadata.parent
val parent = task.parent
val localParent = if (parent > 0) googleTaskDao.getRemoteId(parent) else null
val previous = googleTaskDao.getPrevious(
listId!!,
if (isNullOrEmpty(localParent)) 0 else parent,
gtasksMetadata.order)
if (localParent.isNullOrBlank()) 0 else parent,
task.order ?: 0)
gtasksInvoker
.moveGtask(listId, remoteModel.id, localParent, previous)
?.let { setOrderAndParent(gtasksMetadata, it) }
?.let { setOrderAndParent(gtasksMetadata, it, task) }
} catch (e: GoogleJsonResponseException) {
if (e.statusCode == 400) {
Timber.e(e)
@ -275,8 +275,10 @@ class GoogleTaskSynchronizer @Inject constructor(
@Throws(IOException::class)
private suspend fun fetchAndApplyRemoteChanges(
gtasksInvoker: GtasksInvoker, list: GoogleTaskList) {
val listId = list.remoteId
gtasksInvoker: GtasksInvoker,
list: CaldavCalendar
) {
val listId = list.uuid
var lastSyncDate = list.lastSync
val tasks: MutableList<Task> = ArrayList()
var nextPageToken: String? = null
@ -300,7 +302,7 @@ class GoogleTaskSynchronizer @Inject constructor(
var googleTask = googleTaskDao.getByRemoteId(remoteId)
var task: com.todoroo.astrid.data.Task? = null
if (googleTask == null) {
googleTask = GoogleTask(0, "")
googleTask = CaldavTask(0, "", remoteId = null)
} else if (googleTask.task > 0) {
task = taskDao.fetch(googleTask.task)
}
@ -325,33 +327,36 @@ class GoogleTaskSynchronizer @Inject constructor(
continue
}
} else {
setOrderAndParent(googleTask, gtask)
if (task == null) {
task = taskCreator.createWithValues("")
}
setOrderAndParent(googleTask, gtask, task)
googleTask.remoteId = gtask.id
}
if (task == null) {
task = taskCreator.createWithValues("")
}
task.title = getTruncatedValue(task.title, gtask.title, MAX_TITLE_LENGTH)
task.completionDate = GtasksApiUtilities.gtasksCompletedTimeToUnixTime(gtask.completed?.let(::DateTime))
val dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(gtask.due?.let(::DateTime))
mergeDates(createDueDate(com.todoroo.astrid.data.Task.URGENCY_SPECIFIC_DAY, dueDate), task)
task.notes = getTruncatedValue(task.notes, gtask.notes, MAX_DESCRIPTION_LENGTH)
googleTask.listId = listId
googleTask.calendar = listId
if (task.title?.isNotBlank() == true || task.notes?.isNotBlank() == true) {
write(task, googleTask)
}
}
list.lastSync = lastSyncDate
googleTaskListDao.insertOrReplace(list)
googleTaskListDao.insertOrReplace(
list.copy(
lastSync = lastSyncDate
)
)
}
private suspend fun setOrderAndParent(googleTask: GoogleTask, task: Task) {
private suspend fun setOrderAndParent(googleTask: CaldavTask, task: Task, local: com.todoroo.astrid.data.Task) {
task.position?.toLongOrNull()?.let { googleTask.remoteOrder = it }
googleTask.remoteParent = task.parent?.takeIf { it.isNotBlank() }
googleTask.parent = googleTask.remoteParent?.let { googleTaskDao.getTask(it) } ?: 0L
local.parent = googleTask.remoteParent?.let { googleTaskDao.getTask(it) } ?: 0L
}
private suspend fun write(task: com.todoroo.astrid.data.Task, googleTask: GoogleTask) {
private suspend fun write(task: com.todoroo.astrid.data.Task, googleTask: CaldavTask) {
task.suppressSync()
task.suppressRefresh()
if (task.isNew) {
@ -361,7 +366,7 @@ class GoogleTaskSynchronizer @Inject constructor(
taskDao.save(task)
googleTask.lastSync = task.modificationDate
googleTask.task = task.id
if (googleTask.isNew) {
if (googleTask.id == 0L) {
googleTaskDao.insert(googleTask)
} else {
googleTaskDao.update(googleTask)

@ -19,6 +19,7 @@ import org.tasks.R
import org.tasks.data.*
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.data.CaldavAccount.Companion.TYPE_ETEBASE
import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.data.CaldavAccount.Companion.TYPE_TASKS
import org.tasks.date.DateTimeUtils.midnight
import org.tasks.date.DateTimeUtils.newDateTime
@ -122,8 +123,8 @@ class WorkManagerImpl constructor(
override fun updateBackgroundSync() {
throttle.run {
val enabled = googleTaskListDao.accountCount() > 0 ||
caldavDao.getAccounts(TYPE_CALDAV, TYPE_TASKS, TYPE_ETEBASE).isNotEmpty() ||
val enabled =
caldavDao.getAccounts(TYPE_GOOGLE_TASKS, TYPE_CALDAV, TYPE_TASKS, TYPE_ETEBASE).isNotEmpty() ||
openTaskDao.shouldSync()
if (enabled) {
Timber.d("Enabling background sync")

@ -78,7 +78,7 @@ class DefaultFilterProvider @Inject constructor(
getFilterFromPreference(prefString, getMyTasksFilter(context.resources))!!
private suspend fun getAnyList(): Filter {
val filter = googleTaskListDao.getAllLists().getOrNull(0)?.let(::GtasksFilter)
val filter = caldavDao.getGoogleTaskLists().getOrNull(0)?.let(::GtasksFilter)
?: caldavDao.getCalendars().filterNot { it.access == ACCESS_READ_ONLY }.getOrElse(0) { caldavDao.getLocalList(context) }.let(::CaldavFilter)
defaultList = filter
return filter
@ -100,7 +100,8 @@ class DefaultFilterProvider @Inject constructor(
val tag = tagDataDao.getByUuid(split[1])
if (tag == null || isNullOrEmpty(tag.name)) null else TagFilter(tag)
}
TYPE_GOOGLE_TASKS -> googleTaskListDao.getById(split[1].toLong())?.let { GtasksFilter(it) }
// TODO: convert filters from old ID to uuid
TYPE_GOOGLE_TASKS -> caldavDao.getCalendarByUuid(split[1])?.let { GtasksFilter(it) }
TYPE_CALDAV -> caldavDao.getCalendarByUuid(split[1])?.let { CaldavFilter(it) }
TYPE_LOCATION -> locationDao.getPlace(split[1])?.let { PlaceFilter(it) }
else -> null
@ -114,7 +115,7 @@ class DefaultFilterProvider @Inject constructor(
TYPE_FILTER -> getFilterPreference(filterType, getBuiltInFilterId(filter))
TYPE_CUSTOM_FILTER -> getFilterPreference(filterType, (filter as CustomFilter).id)
TYPE_TAG -> getFilterPreference(filterType, (filter as TagFilter).uuid)
TYPE_GOOGLE_TASKS -> getFilterPreference(filterType, (filter as GtasksFilter).storeId)
TYPE_GOOGLE_TASKS -> getFilterPreference(filterType, (filter as GtasksFilter).remoteId)
TYPE_CALDAV -> getFilterPreference(filterType, (filter as CaldavFilter).uuid)
TYPE_LOCATION -> getFilterPreference(filterType, (filter as PlaceFilter).uid)
else -> null
@ -169,7 +170,7 @@ class DefaultFilterProvider @Inject constructor(
val googleTask = googleTaskDao.getByTaskId(task.id)
val caldavTask = caldavDao.getTask(task.id)
if (googleTask != null) {
val googleTaskList = googleTaskListDao.getByRemoteId(googleTask.listId!!)
val googleTaskList = googleTaskListDao.getByRemoteId(googleTask.calendar!!)
if (googleTaskList != null) {
originalList = GtasksFilter(googleTaskList)
}

@ -14,7 +14,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.tasks.R
import org.tasks.backup.BackupConstants
import org.tasks.data.*
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavDao
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.googleapis.InvokerFactory
import org.tasks.gtasks.GoogleAccountManager
@ -29,13 +30,11 @@ class PreferencesViewModel @Inject constructor(
invokers: InvokerFactory,
private val googleAccountManager: GoogleAccountManager,
caldavDao: CaldavDao,
googleTaskListDao: GoogleTaskListDao,
) : ViewModel() {
private val driveInvoker = invokers.getDriveInvoker()
val lastBackup = MutableLiveData<Long?>()
val lastDriveBackup = MutableLiveData<Long?>()
val lastAndroidBackup = MutableLiveData<Long>()
var googleTaskAccounts = googleTaskListDao.watchAccounts()
var caldavAccounts = caldavDao.watchAccounts()
private fun isStale(timestamp: Long?) =

@ -1,6 +1,8 @@
package org.tasks.preferences.fragments
import android.content.*
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
@ -11,9 +13,9 @@ import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.billing.Inventory
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.isPaymentRequired
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.CaldavDao
import org.tasks.preferences.IconPreference
import javax.inject.Inject
@ -23,11 +25,11 @@ class GoogleTasksAccount : BaseAccountPreference() {
@Inject lateinit var taskDeleter: TaskDeleter
@Inject lateinit var inventory: Inventory
@Inject lateinit var localBroadcastManager: LocalBroadcastManager
@Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var caldavDao: CaldavDao
private lateinit var googleTaskAccountLiveData: LiveData<GoogleTaskAccount>
private lateinit var googleTaskAccountLiveData: LiveData<CaldavAccount>
val googleTaskAccount: GoogleTaskAccount
val googleTaskAccount: CaldavAccount
get() = googleTaskAccountLiveData.value ?: requireArguments().getParcelable(EXTRA_ACCOUNT)!!
private val purchaseReceiver = object : BroadcastReceiver() {
@ -36,7 +38,7 @@ class GoogleTasksAccount : BaseAccountPreference() {
googleTaskAccount.let {
if (inventory.subscription.value != null && it.error.isPaymentRequired()) {
it.error = null
googleTaskListDao.update(it)
caldavDao.update(it)
}
refreshUi(it)
}
@ -49,8 +51,8 @@ class GoogleTasksAccount : BaseAccountPreference() {
override suspend fun setupPreferences(savedInstanceState: Bundle?) {
super.setupPreferences(savedInstanceState)
googleTaskAccountLiveData = googleTaskListDao.watchAccount(
arguments?.getParcelable<GoogleTaskAccount>(EXTRA_ACCOUNT)?.id ?: 0
googleTaskAccountLiveData = caldavDao.watchAccount(
arguments?.getParcelable<CaldavAccount>(EXTRA_ACCOUNT)?.id ?: 0
)
googleTaskAccountLiveData.observe(this) { refreshUi(it) }
@ -74,7 +76,7 @@ class GoogleTasksAccount : BaseAccountPreference() {
localBroadcastManager.unregisterReceiver(purchaseReceiver)
}
private fun refreshUi(account: GoogleTaskAccount?) {
private fun refreshUi(account: CaldavAccount?) {
if (account == null) {
return
}
@ -119,7 +121,7 @@ class GoogleTasksAccount : BaseAccountPreference() {
fun String?.isUnauthorized(): Boolean =
this?.startsWith("401 Unauthorized", ignoreCase = true) == true
fun newGoogleTasksAccountPreference(account: GoogleTaskAccount) =
fun newGoogleTasksAccountPreference(account: CaldavAccount) =
GoogleTasksAccount().apply {
arguments = Bundle().apply {
putParcelable(EXTRA_ACCOUNT, account)

@ -22,7 +22,6 @@ import org.tasks.billing.PurchaseActivity
import org.tasks.caldav.BaseCaldavAccountSettingsActivity
import org.tasks.caldav.CaldavAccountSettingsActivity
import org.tasks.data.CaldavAccount
import org.tasks.data.GoogleTaskAccount
import org.tasks.etebase.EtebaseAccountSettingsActivity
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.Context.toast
@ -87,7 +86,6 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
viewModel.lastBackup.observe(this) { updateBackupWarning() }
viewModel.lastAndroidBackup.observe(this) { updateBackupWarning() }
viewModel.lastDriveBackup.observe(this) { updateBackupWarning() }
viewModel.googleTaskAccounts.observe(this) { refreshAccounts() }
viewModel.caldavAccounts.observe(this) { refreshAccounts() }
if (BuildConfig.FLAVOR == "generic") {
remove(R.string.upgrade_to_pro)
@ -142,7 +140,6 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
private fun refreshAccounts() {
val caldavAccounts = viewModel.caldavAccounts.value ?: emptyList()
val googleTaskAccounts = viewModel.googleTaskAccounts.value ?: emptyList()
val addAccount = findPreference(R.string.add_account)
val index = preferenceScreen.indexOf(addAccount)
var current = 0
@ -153,15 +150,8 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
preferenceScreen.insertAt(current++)
})
}
googleTaskAccounts.forEach {
setup(it, if (current < index) {
preferenceScreen.getPreference(current++) as IconPreference
} else {
preferenceScreen.insertAt(current++)
})
}
preferenceScreen.removeAt(current, index - current)
if (caldavAccounts.isEmpty() && googleTaskAccounts.isEmpty()) {
if (caldavAccounts.isEmpty()) {
addAccount.setTitle(R.string.not_signed_in)
addAccount.setIcon(R.drawable.ic_outline_cloud_off_24px)
} else {
@ -216,6 +206,12 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
newMicrosoftAccountPreference(account),
getString(R.string.microsoft)
)
} else if (account.isGoogleTasks) {
(activity as MainPreferences).startPreference(
this,
newGoogleTasksAccountPreference(account),
getString(R.string.gtasks_GPr_header)
)
} else {
val intent = Intent(context, account.accountSettingsClass).apply {
putExtra(BaseCaldavAccountSettingsActivity.EXTRA_CALDAV_DATA, account)
@ -239,21 +235,6 @@ class MainSettingsFragment : InjectingPreferenceFragment() {
setupErrorIcon(pref, account.hasError, account.isEteSyncAccount)
}
private fun setup(account: GoogleTaskAccount, pref: IconPreference) {
pref.setTitle(R.string.gtasks_GPr_header)
pref.setIcon(R.drawable.ic_google)
pref.summary = account.account
setupErrorIcon(pref, account.hasError)
pref.setOnPreferenceClickListener {
(activity as MainPreferences).startPreference(
this,
newGoogleTasksAccountPreference(account),
account.account!!
)
false
}
}
private fun setupErrorIcon(
pref: IconPreference,
hasError: Boolean,

@ -45,7 +45,7 @@ class TasksContentProvider : ContentProvider() {
}
URI_TASKS -> hilt.contentProviderDao.getTasks()
URI_LISTS -> hilt.contentProviderDao.getLists()
URI_GOOGLE_TASK_LISTS -> hilt.contentProviderDao.getGoogleTaskLists()
URI_GOOGLE_TASK_LISTS -> null
else -> throw IllegalStateException("Unrecognized URI: $uri")
}
}
@ -70,8 +70,6 @@ class TasksContentProvider : ContentProvider() {
companion object {
private const val TODO_AGENDA_TABLES =
"""${Task.TABLE_NAME}
LEFT JOIN google_tasks ON gt_task = _id
LEFT JOIN google_task_lists ON gtl_remote_id = gt_list_id
LEFT JOIN caldav_tasks ON cd_task = _id
LEFT JOIN caldav_lists ON cdl_uuid = cd_calendar"""
private const val AUTHORITY = BuildConfig.APPLICATION_ID

@ -1,37 +1,26 @@
package org.tasks.ui
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter
import org.tasks.LocalBroadcastManager
import org.tasks.data.*
import java.util.*
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ChipListCache @Inject internal constructor(
googleTaskListDao: GoogleTaskListDao,
caldavDao: CaldavDao,
tagDataDao: TagDataDao,
private val localBroadcastManager: LocalBroadcastManager) {
private val googleTaskLists: MutableMap<String?, GtasksFilter> = HashMap()
private val caldavCalendars: MutableMap<String?, CaldavFilter> = HashMap()
private val caldavCalendars: MutableMap<String?, CaldavCalendar> = HashMap()
private val tagDatas: MutableMap<String?, TagFilter> = HashMap()
private fun updateGoogleTaskLists(updated: List<GoogleTaskList>) {
googleTaskLists.clear()
for (update in updated) {
googleTaskLists[update.remoteId] = GtasksFilter(update)
}
localBroadcastManager.broadcastRefresh()
}
private fun updateCaldavCalendars(updated: List<CaldavCalendar>) {
caldavCalendars.clear()
for (update in updated) {
caldavCalendars[update.uuid] = CaldavFilter(update)
caldavCalendars[update.uuid] = update
}
localBroadcastManager.broadcastRefresh()
}
@ -44,14 +33,11 @@ class ChipListCache @Inject internal constructor(
localBroadcastManager.broadcastRefresh()
}
fun getGoogleTaskList(googleTaskList: String?): Filter? = googleTaskLists[googleTaskList]
fun getCaldavList(caldav: String?): Filter? = caldavCalendars[caldav]
fun getCaldavList(caldav: String?): CaldavCalendar? = caldavCalendars[caldav]
fun getTag(tag: String?): TagFilter? = tagDatas[tag]
init {
googleTaskListDao.subscribeToLists().observeForever { updated: List<GoogleTaskList> -> updateGoogleTaskLists(updated) }
caldavDao.subscribeToCalendars().observeForever { updated: List<CaldavCalendar> -> updateCaldavCalendars(updated) }
tagDataDao.subscribeToTags().observeForever { updated: List<TagData> -> updateTags(updated) }
}

@ -99,29 +99,16 @@ class ChipProvider @Inject constructor(
)
}
}
if (!isSubtask && preferences.showListChip) {
if (!isNullOrEmpty(task.googleTaskList) && filter !is GtasksFilter) {
lists.getGoogleTaskList(task.googleTaskList)?.let { list ->
FilterChip(
filter = list,
defaultIcon = R.drawable.ic_list_24px,
onClick = onClick,
showText = showText,
showIcon = showIcon,
colorProvider = this::getColor,
)
}
} else if (!isNullOrEmpty(task.caldav) && filter !is CaldavFilter) {
lists.getCaldavList(task.caldav)?.let { list ->
FilterChip(
filter = list,
defaultIcon = R.drawable.ic_list_24px,
onClick = onClick,
showText = showText,
showIcon = showIcon,
colorProvider = this::getColor,
)
}
if (!isSubtask && preferences.showListChip && filter !is CaldavFilter) {
lists.getCaldavList(task.caldav)?.let { list ->
FilterChip(
filter = if (task.isGoogleTask) GtasksFilter(list) else CaldavFilter(list),
defaultIcon = R.drawable.ic_list_24px,
onClick = onClick,
showText = showText,
showIcon = showIcon,
colorProvider = this::getColor,
)
}
}
val tagString = task.tagsString

@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.data.TaskDao
import org.tasks.filters.FilterProvider
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@ -51,7 +52,12 @@ class NavigationDrawerViewModel @Inject constructor(
.navDrawerItems()
.onEach {
if (it is Filter && it.count == -1) {
it.count = taskDao.count(it)
it.count = try {
taskDao.count(it)
} catch (e: Exception) {
Timber.e(e)
0
}
}
}
.let { filters -> _viewState.update { it.copy(filters = filters) } }

@ -10,7 +10,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.api.Filter
@ -23,7 +22,6 @@ import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.edit.SubtaskRow
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.preferences.Preferences
@ -57,8 +55,9 @@ class SubtaskControlSet : TaskEditControlFragment() {
setContent {
MdcTheme {
SubtaskRow(
originalFilter = viewModel.originalList,
filter = viewModel.selectedList.collectAsStateLifecycleAware().value,
googleTask = googleTaskDao.watchGoogleTask(viewModel.task.id).collectAsStateLifecycleAware(initial = null).value,
hasParent = viewModel.hasParent,
desaturate = preferences.desaturateDarkMode,
existingSubtasks = listViewModel.tasks.observeAsState(initial = emptyList()).value,
newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value,
@ -109,20 +108,10 @@ class SubtaskControlSet : TaskEditControlFragment() {
companion object {
const val TAG = R.string.TEA_ctrl_subtask_pref
private fun getQueryTemplate(task: Task): QueryTemplate = QueryTemplate()
.join(
Join.left(
GoogleTask.TABLE,
Criterion.and(
GoogleTask.PARENT.eq(task.id),
GoogleTask.TASK.eq(Task.ID),
GoogleTask.DELETED.eq(0)
)
)
)
.where(
Criterion.and(
activeAndVisible(),
Criterion.or(Task.PARENT.eq(task.id), GoogleTask.TASK.gt(0))
Task.PARENT.eq(task.id)
)
)
}

@ -99,6 +99,8 @@ class TaskEditViewModel @Inject constructor(
val estimatedSeconds = MutableStateFlow(task.estimatedSeconds)
val elapsedSeconds = MutableStateFlow(task.elapsedSeconds)
var newSubtasks = MutableStateFlow(emptyList<Task>())
val hasParent: Boolean
get() = task.parent > 0
val dueDate = MutableStateFlow(task.dueDate)
@ -128,7 +130,7 @@ class TaskEditViewModel @Inject constructor(
}
var selectedCalendar = MutableStateFlow(originalCalendar)
private val originalList: Filter = savedStateHandle[TaskEditFragment.EXTRA_LIST]!!
val originalList: Filter = savedStateHandle[TaskEditFragment.EXTRA_LIST]!!
var selectedList = MutableStateFlow(originalList)
private var originalLocation: Location? = savedStateHandle[TaskEditFragment.EXTRA_LOCATION]
@ -292,10 +294,10 @@ class TaskEditViewModel @Inject constructor(
firebase?.addTask("subtasks")
when (selectedList.value) {
is GtasksFilter -> {
val googleTask = GoogleTask(subtask.id, (selectedList.value as GtasksFilter).remoteId)
googleTask.parent = task.id
val googleTask = CaldavTask(subtask.id, (selectedList.value as GtasksFilter).remoteId, remoteId = null)
subtask.parent = task.id
googleTask.isMoved = true
googleTaskDao.insertAndShift(googleTask, false)
googleTaskDao.insertAndShift(subtask, googleTask, false)
}
is CaldavFilter -> {
val caldavTask = CaldavTask(subtask.id, (selectedList.value as CaldavFilter).uuid)

@ -5,7 +5,6 @@ import android.widget.RemoteViews
import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter
import com.todoroo.astrid.data.Task
import dagger.hilt.android.qualifiers.ApplicationContext
@ -74,13 +73,9 @@ class ChipProvider @Inject constructor(
}
fun getListChip(filter: Filter?, task: TaskContainer): RemoteViews? {
task.googleTaskList
?.takeIf { filter !is GtasksFilter }
?.let { newChip(chipListCache.getGoogleTaskList(it), R.drawable.ic_list_24px) }
?.let { return it }
task.caldav
?.takeIf { filter !is CaldavFilter }
?.let { newChip(chipListCache.getCaldavList(it), R.drawable.ic_list_24px) }
?.let { newChip(CaldavFilter(chipListCache.getCaldavList(it)), R.drawable.ic_list_24px) }
?.let { return it }
return null
}

@ -12,19 +12,18 @@ import net.fortuna.ical4j.model.property.Status
import org.junit.Assert.*
import org.junit.Test
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.makers.CaldavTaskMaker.REMOTE_ORDER
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.CREATION_TIME
import org.tasks.makers.TaskMaker.newTask
import org.tasks.makers.iCalMaker
import org.tasks.makers.iCalMaker.COLLAPSED
import org.tasks.makers.iCalMaker.COMPLETED_AT
import org.tasks.makers.iCalMaker.CREATED_AT
import org.tasks.makers.iCalMaker.DESCRIPTION
import org.tasks.makers.iCalMaker.DUE_DATE
import org.tasks.makers.iCalMaker.ORDER
import org.tasks.makers.iCalMaker.PARENT
import org.tasks.makers.iCalMaker.PRIORITY
import org.tasks.makers.iCalMaker.RRULE
@ -567,9 +566,9 @@ class iCalendarMergeTest {
@Test
fun remoteSetsOrder() =
newCaldavTask()
newTask()
.applyRemote(
remote = newIcal(with(ORDER, 1234)),
remote = newIcal(with(iCalMaker.ORDER, 1234)),
local = null
)
.let {
@ -578,10 +577,10 @@ class iCalendarMergeTest {
@Test
fun remoteRemovesOrder() =
newCaldavTask(with(REMOTE_ORDER, 1234))
newTask(with(TaskMaker.ORDER, 1234))
.applyRemote(
remote = newIcal(),
local = newIcal(with(ORDER, 1234))
local = newIcal(with(iCalMaker.ORDER, 1234))
)
.let {
assertNull(it.order)
@ -589,10 +588,10 @@ class iCalendarMergeTest {
@Test
fun localRemovesOrder() =
newCaldavTask()
newTask()
.applyRemote(
remote = newIcal(with(ORDER, 1234)),
local = newIcal(with(ORDER, 1234))
remote = newIcal(with(iCalMaker.ORDER, 1234)),
local = newIcal(with(iCalMaker.ORDER, 1234))
)
.let {
assertNull(it.order)
@ -600,10 +599,10 @@ class iCalendarMergeTest {
@Test
fun localBeatsRemoteOrder() =
newCaldavTask(with(REMOTE_ORDER, 789))
newTask(with(TaskMaker.ORDER, 789L))
.applyRemote(
remote = newIcal(with(ORDER, 456)),
local = newIcal(with(ORDER, 123))
remote = newIcal(with(iCalMaker.ORDER, 456L)),
local = newIcal(with(iCalMaker.ORDER, 123))
)
.let {
assertEquals(789L, it.order)

@ -0,0 +1,29 @@
package org.tasks.makers
import com.natpryce.makeiteasy.Instantiator
import com.natpryce.makeiteasy.Property
import com.natpryce.makeiteasy.Property.newProperty
import com.natpryce.makeiteasy.PropertyValue
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.TYPE_CALDAV
import org.tasks.makers.Maker.make
object CaldavAccountMaker {
val ID: Property<CaldavAccount, Long> = newProperty()
val NAME: Property<CaldavAccount, String> = newProperty()
val UUID: Property<CaldavAccount, String> = newProperty()
val ACCOUNT_TYPE: Property<CaldavAccount, Int> = newProperty()
private val instantiator = Instantiator { lookup ->
CaldavAccount().apply {
id = lookup.valueOf(ID, 0L)
name = lookup.valueOf(NAME, null as String?)
uuid = lookup.valueOf(UUID, "account")
accountType = lookup.valueOf(ACCOUNT_TYPE, TYPE_CALDAV)
}
}
fun newCaldavAccount(vararg properties: PropertyValue<in CaldavAccount?, *>): CaldavAccount {
return make(instantiator, *properties)
}
}

@ -8,13 +8,17 @@ import org.tasks.data.CaldavCalendar
import org.tasks.makers.Maker.make
object CaldavCalendarMaker {
val ID: Property<CaldavCalendar, Long> = newProperty()
val ACCOUNT: Property<CaldavCalendar, String> = newProperty()
val NAME: Property<CaldavCalendar, String> = newProperty()
val UUID: Property<CaldavCalendar, String> = newProperty()
private val instantiator = Instantiator { lookup ->
CaldavCalendar(
id = lookup.valueOf(ID, 0L),
name = lookup.valueOf(NAME, null as String?),
account = lookup.valueOf(ACCOUNT, "account"),
uuid = lookup.valueOf(UUID, "uuid"),
uuid = lookup.valueOf(UUID, "calendar"),
)
}

@ -12,15 +12,13 @@ object CaldavTaskMaker {
val TASK: Property<CaldavTask, Long> = newProperty()
val REMOTE_ID: Property<CaldavTask, String?> = newProperty()
val REMOTE_PARENT: Property<CaldavTask, String?> = newProperty()
val REMOTE_ORDER: Property<CaldavTask, Long?> = newProperty()
val ETAG: Property<CaldavTask, String?> = newProperty()
val OBJECT: Property<CaldavTask, String?> = newProperty()
private val instantiator = Instantiator<CaldavTask> {
private val instantiator = Instantiator {
val task = CaldavTask(it.valueOf(TASK, 1L), it.valueOf(CALENDAR, "calendar"))
task.remoteId = it.valueOf(REMOTE_ID, task.remoteId)
task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?)
task.order = it.valueOf(REMOTE_ORDER, null as Long?)
task.etag = it.valueOf(ETAG, null as String?)
task.`object` = it.valueOf(OBJECT, task.remoteId?.let { id -> "$id.ics" })
task

@ -1,24 +0,0 @@
package org.tasks.makers
import com.natpryce.makeiteasy.Instantiator
import com.natpryce.makeiteasy.Property
import com.natpryce.makeiteasy.PropertyLookup
import com.natpryce.makeiteasy.PropertyValue
import org.tasks.data.GoogleTaskList
object GoogleTaskListMaker {
val REMOTE_ID: Property<GoogleTaskList, String> = Property.newProperty()
val ACCOUNT: Property<GoogleTaskList, String?> = Property.newProperty()
private val instantiator = Instantiator { lookup: PropertyLookup<GoogleTaskList> ->
val list = GoogleTaskList()
list.remoteId = lookup.valueOf(REMOTE_ID, "1234")
list.account = lookup.valueOf(ACCOUNT, null as String?)
list.setColor(0)
list
}
fun newGoogleTaskList(vararg properties: PropertyValue<in GoogleTaskList?, *>): GoogleTaskList {
return Maker.make(instantiator, *properties)
}
}

@ -1,33 +0,0 @@
package org.tasks.makers
import com.natpryce.makeiteasy.Instantiator
import com.natpryce.makeiteasy.Property
import com.natpryce.makeiteasy.Property.newProperty
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.helper.UUIDHelper
import org.tasks.data.GoogleTask
import org.tasks.makers.Maker.make
object GoogleTaskMaker {
val LIST: Property<GoogleTask, String> = newProperty()
val ORDER: Property<GoogleTask, Long> = newProperty()
val REMOTE_ID: Property<GoogleTask, String> = newProperty()
val TASK: Property<GoogleTask, Long> = newProperty()
val PARENT: Property<GoogleTask, Long> = newProperty()
val REMOTE_PARENT: Property<GoogleTask, String?> = newProperty()
private val instantiator = Instantiator<GoogleTask> {
val task = GoogleTask()
task.listId = it.valueOf(LIST, "1")
task.order = it.valueOf(ORDER, 0)
task.remoteId = it.valueOf(REMOTE_ID, UUIDHelper.newUUID())
task.task = it.valueOf(TASK, 1)
task.parent = it.valueOf(PARENT, 0L)
task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?)
task
}
fun newGoogleTask(vararg properties: PropertyValue<in GoogleTask?, *>): GoogleTask {
return make(instantiator, *properties)
}
}

@ -1,36 +0,0 @@
package org.tasks.makers
import com.natpryce.makeiteasy.Instantiator
import com.natpryce.makeiteasy.Property
import com.natpryce.makeiteasy.Property.newProperty
import com.natpryce.makeiteasy.PropertyLookup
import com.natpryce.makeiteasy.PropertyValue
import com.todoroo.astrid.api.FilterListItem.NO_ORDER
import org.tasks.data.GoogleTaskList
import org.tasks.makers.Maker.make
object GtaskListMaker {
val ID: Property<GoogleTaskList, Long> = newProperty()
val ACCOUNT: Property<GoogleTaskList, String> = newProperty()
val REMOTE_ID: Property<GoogleTaskList, String> = newProperty()
val LAST_SYNC: Property<GoogleTaskList, Long> = newProperty()
val NAME: Property<GoogleTaskList, String> = newProperty()
private val ORDER: Property<GoogleTaskList, Int> = newProperty()
private val COLOR: Property<GoogleTaskList, Int> = newProperty()
private val instantiator = Instantiator { lookup: PropertyLookup<GoogleTaskList> ->
val list = GoogleTaskList()
list.id = lookup.valueOf(ID, 0L)
list.account = lookup.valueOf(ACCOUNT, "account")
list.remoteId = lookup.valueOf(REMOTE_ID, "1")
list.title = lookup.valueOf(NAME, "Default")
list.order = lookup.valueOf(ORDER, NO_ORDER)
list.lastSync = lookup.valueOf(LAST_SYNC, 0L)
list.setColor(lookup.valueOf(COLOR, 0))
list
}
fun newGtaskList(vararg properties: PropertyValue<in GoogleTaskList?, *>): GoogleTaskList {
return make(instantiator, *properties)
}
}

@ -34,6 +34,7 @@ object TaskMaker {
val UUID: Property<Task, String> = newProperty()
val COLLAPSED: Property<Task, Boolean> = newProperty()
val DESCRIPTION: Property<Task, String?> = newProperty()
val ORDER: Property<Task, Long> = newProperty()
private val instantiator = Instantiator { lookup: PropertyLookup<Task> ->
val task = Task()
@ -95,6 +96,7 @@ object TaskMaker {
task.creationDate = creationTime.millis
task.modificationDate = lookup.valueOf(MODIFICATION_TIME, creationTime).millis
task.parent = lookup.valueOf(PARENT, 0L)
task.order = lookup.valueOf(ORDER, null as Long?)
task
}

Loading…
Cancel
Save