Merge branch 'consolidate_lists'

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

@ -54,8 +54,8 @@ android {
defaultConfig { defaultConfig {
testApplicationId = "org.tasks.test" testApplicationId = "org.tasks.test"
applicationId = "org.tasks" applicationId = "org.tasks"
versionCode = 130102 versionCode = 130200
versionName = "13.1.2" versionName = "13.2"
targetSdk = Versions.targetSdk targetSdk = Versions.targetSdk
minSdk = Versions.minSdk minSdk = Versions.minSdk
testInstrumentationRunner = "org.tasks.TestRunner" 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(dateTime: DateTime, index: Int) = checkOrder(dateTime.toAppleEpoch(), index)
private fun checkOrder(order: Long?, index: Int) = runBlocking { 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) { if (order == null) {
assertNull(sortOrder) assertNull(sortOrder)
} else { } else {

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

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

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

@ -9,28 +9,29 @@ import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test 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.CaldavCalendar
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao import org.tasks.data.GoogleTaskDao
import org.tasks.injection.InjectingTestCase import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule import org.tasks.injection.ProductionModule
import org.tasks.jobs.WorkManager 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.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.CaldavTaskMaker
import org.tasks.makers.CaldavTaskMaker.CALENDAR import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT import org.tasks.makers.CaldavTaskMaker.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.CaldavTaskMaker.newCaldavTask 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.ID
import org.tasks.makers.TaskMaker.PARENT
import org.tasks.makers.TaskMaker.newTask import org.tasks.makers.TaskMaker.newTask
import javax.inject.Inject import javax.inject.Inject
@ -44,18 +45,28 @@ class TaskMoverTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao @Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var taskMover: TaskMover @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 @Test
fun moveBetweenGoogleTaskLists() = runBlocking { fun moveBetweenGoogleTaskLists() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1) createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1) moveToGoogleTasks("2", 1)
assertEquals("2", googleTaskDao.getByTaskId(1)!!.listId) assertEquals("2", googleTaskDao.getByTaskId(1)?.calendar)
} }
@Test @Test
fun deleteGoogleTaskAfterMove() = runBlocking { fun deleteGoogleTaskAfterMove() = runBlocking {
createTasks(1) createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1) moveToGoogleTasks("2", 1)
val deleted = googleTaskDao.getDeletedByTaskId(1) val deleted = googleTaskDao.getDeletedByTaskId(1)
assertEquals(1, deleted.size.toLong()) assertEquals(1, deleted.size.toLong())
@ -65,24 +76,25 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun moveChildrenBetweenGoogleTaskLists() = runBlocking { fun moveChildrenBetweenGoogleTaskLists() = runBlocking {
createTasks(1, 2) setAccountType("account1", TYPE_GOOGLE_TASKS)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) setAccountType("account2", TYPE_GOOGLE_TASKS)
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) moveToGoogleTasks("2", 1)
val deleted = googleTaskDao.getDeletedByTaskId(2) val deleted = googleTaskDao.getDeletedByTaskId(2)
assertEquals(1, deleted.size.toLong()) assertEquals(1, deleted.size.toLong())
assertEquals(2, deleted[0].task) assertEquals(2, deleted[0].task)
assertTrue(deleted[0].deleted > 0) assertTrue(deleted[0].deleted > 0)
val task = googleTaskDao.getByTaskId(2)!! assertEquals(1L, taskDao.fetch(2)?.parent)
assertEquals(1, task.parent) assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
assertEquals("2", task.listId)
} }
@Test @Test
fun moveBetweenCaldavList() = runBlocking { fun moveBetweenCaldavList() = runBlocking {
createTasks(1) createTasks(1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1"))) caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1) moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar) assertEquals("2", caldavDao.getTask(1)!!.calendar)
} }
@ -90,8 +102,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun deleteCaldavTaskAfterMove() = runBlocking { fun deleteCaldavTaskAfterMove() = runBlocking {
createTasks(1) createTasks(1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1"))) caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("2", 1) moveToCaldavList("2", 1)
val deleted = caldavDao.getMoved("1") val deleted = caldavDao.getMoved("1")
assertEquals(1, deleted.size.toLong()) assertEquals(1, deleted.size.toLong())
@ -104,18 +115,17 @@ class TaskMoverTest : InjectingTestCase() {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
createSubtask(3, 2) createSubtask(3, 2)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")), with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 2L), with(TASK, 2L),
with(CALENDAR, "1"), with(CALENDAR, "1"),
with(REMOTE_ID, "b"), with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")), with(REMOTE_PARENT, "a")),
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 3L), with(TASK, 3L),
with(CALENDAR, "1"), with(CALENDAR, "1"),
with(REMOTE_PARENT, "b")))) with(REMOTE_PARENT, "b"))))
moveToCaldavList("2", 1) moveToCaldavList("2", 1)
@ -128,13 +138,16 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun moveGoogleTaskChildrenToCaldav() = runBlocking { fun moveGoogleTaskChildrenToCaldav() = runBlocking {
createTasks(1, 2) setAccountType("account1", TYPE_GOOGLE_TASKS)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) setAccountType("account2", TYPE_CALDAV)
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")))
moveToCaldavList("1", 1) moveToCaldavList("1", 1)
val task = caldavDao.getTask(2) val task = caldavDao.getTask(2)
assertEquals("1", task!!.calendar) assertEquals("1", task!!.calendar)
assertEquals(1, taskDao.fetch(2)!!.parent) assertEquals(1L, taskDao.fetch(2)?.parent)
} }
@Test @Test
@ -143,8 +156,7 @@ class TaskMoverTest : InjectingTestCase() {
createSubtask(2, 1) createSubtask(2, 1)
createSubtask(3, 2) createSubtask(3, 2)
moveToGoogleTasks("1", 1) moveToGoogleTasks("1", 1)
assertEquals(1, googleTaskDao.getByTaskId(3)!!.parent) assertEquals(1L, taskDao.fetch(3)?.parent)
assertEquals(0, taskDao.fetch(3)!!.parent)
} }
@Test @Test
@ -152,7 +164,7 @@ class TaskMoverTest : InjectingTestCase() {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
moveToGoogleTasks("1", 2) moveToGoogleTasks("1", 2)
assertEquals(0, taskDao.fetch(2)!!.parent) assertEquals(0L, taskDao.fetch(2)?.parent)
} }
@Test @Test
@ -171,43 +183,43 @@ class TaskMoverTest : InjectingTestCase() {
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")), with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 2L), with(TASK, 2L),
with(CALENDAR, "1"), with(CALENDAR, "1"),
with(REMOTE_ID, "b"), with(REMOTE_ID, "b"),
with(REMOTE_PARENT, "a")), with(REMOTE_PARENT, "a")),
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 3L), with(TASK, 3L),
with(CALENDAR, "1"), with(CALENDAR, "1"),
with(REMOTE_PARENT, "b")))) with(REMOTE_PARENT, "b"))))
moveToGoogleTasks("1", 1) moveToGoogleTasks("1", 1)
val task = googleTaskDao.getByTaskId(3L)!! val task = taskDao.fetch(3L)
assertEquals(1, task.parent) assertEquals(1L, task?.parent)
} }
@Test @Test
fun moveGoogleTaskChildWithoutParent() = runBlocking { fun moveGoogleTaskChildWithoutParent() = runBlocking {
createTasks(1, 2) setAccountType("account2", TYPE_GOOGLE_TASKS)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L))) createSubtask(2, 1)
googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
moveToGoogleTasks("2", 2) moveToGoogleTasks("2", 2)
val task = googleTaskDao.getByTaskId(2)!! assertEquals(0L, taskDao.fetch(2)?.parent)
assertEquals(0L, task.parent) assertEquals("2", googleTaskDao.getByTaskId(2)?.calendar)
assertEquals("2", task.listId)
} }
@Test @Test
fun moveCaldavChildWithoutParent() = runBlocking { fun moveCaldavChildWithoutParent() = runBlocking {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")), with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 2L), with(TASK, 2L),
with(CALENDAR, "1"), with(CALENDAR, "1"),
with(REMOTE_PARENT, "a")))) with(REMOTE_PARENT, "a"))))
moveToCaldavList("2", 2) moveToCaldavList("2", 2)
@ -218,17 +230,19 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun moveGoogleTaskToCaldav() = runBlocking { fun moveGoogleTaskToCaldav() = runBlocking {
createTasks(1) createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToCaldavList("2", 1) moveToCaldavList("2", 1)
assertEquals("2", caldavDao.getTask(1)!!.calendar) assertEquals("2", caldavDao.getTask(1)!!.calendar)
} }
@Test @Test
fun moveCaldavToGoogleTask() = runBlocking { fun moveCaldavToGoogleTask() = runBlocking {
setAccountType("account1", TYPE_CALDAV)
setAccountType("account2", TYPE_GOOGLE_TASKS)
createTasks(1) createTasks(1)
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"))) caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1) moveToGoogleTasks("2", 1)
assertEquals("2", googleTaskDao.getByTaskId(1L)!!.listId) assertEquals("2", googleTaskDao.getByTaskId(1L)?.calendar)
} }
@Test @Test
@ -237,14 +251,15 @@ class TaskMoverTest : InjectingTestCase() {
createSubtask(2, 1) createSubtask(2, 1)
createSubtask(3, 2) createSubtask(3, 2)
moveToCaldavList("1", 1) moveToCaldavList("1", 1)
assertEquals("1", caldavDao.getTask(3)!!.calendar) assertEquals("1", caldavDao.getTask(3)?.calendar)
assertEquals(2, taskDao.fetch(3)!!.parent) assertEquals(2L, taskDao.fetch(3)?.parent)
} }
@Test @Test
fun moveToSameGoogleTaskListIsNoop() = runBlocking { fun moveToSameGoogleTaskListIsNoop() = runBlocking {
setAccountType("account1", TYPE_GOOGLE_TASKS)
createTasks(1) createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
moveToGoogleTasks("1", 1) moveToGoogleTasks("1", 1)
assertTrue(googleTaskDao.getDeletedByTaskId(1).isEmpty()) assertTrue(googleTaskDao.getDeletedByTaskId(1).isEmpty())
assertEquals(1, googleTaskDao.getAllByTaskId(1).size.toLong()) assertEquals(1, googleTaskDao.getAllByTaskId(1).size.toLong())
@ -253,7 +268,7 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun moveToSameCaldavListIsNoop() = runBlocking { fun moveToSameCaldavListIsNoop() = runBlocking {
createTasks(1) createTasks(1)
caldavDao.insert(newCaldavTask(with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"))) caldavDao.insert(newCaldavTask(with(TASK, 1L), with(CALENDAR, "1")))
moveToCaldavList("1", 1) moveToCaldavList("1", 1)
assertTrue(caldavDao.getMoved("1").isEmpty()) assertTrue(caldavDao.getMoved("1").isEmpty())
assertEquals(1, caldavDao.getTasks(1).size.toLong()) assertEquals(1, caldavDao.getTasks(1).size.toLong())
@ -261,9 +276,10 @@ class TaskMoverTest : InjectingTestCase() {
@Test @Test
fun dontDuplicateWhenParentAndChildGoogleTaskMoved() = runBlocking { fun dontDuplicateWhenParentAndChildGoogleTaskMoved() = runBlocking {
createTasks(1, 2) createTasks(1)
googleTaskDao.insert(newGoogleTask(with(TASK, 1), with(LIST, "1"))) createSubtask(2, 1)
googleTaskDao.insert(newGoogleTask(with(TASK, 2), with(LIST, "1"), with(PARENT, 1L))) googleTaskDao.insert(newCaldavTask(with(TASK, 1), with(CALENDAR, "1")))
googleTaskDao.insert(newCaldavTask(with(TASK, 2), with(CALENDAR, "1")))
moveToGoogleTasks("2", 1, 2) moveToGoogleTasks("2", 1, 2)
assertEquals(1, googleTaskDao.getAllByTaskId(2).filter { it.deleted == 0L }.size) assertEquals(1, googleTaskDao.getAllByTaskId(2).filter { it.deleted == 0L }.size)
} }
@ -272,13 +288,12 @@ class TaskMoverTest : InjectingTestCase() {
fun dontDuplicateWhenParentAndChildCaldavMoved() = runBlocking { fun dontDuplicateWhenParentAndChildCaldavMoved() = runBlocking {
createTasks(1) createTasks(1)
createSubtask(2, 1) createSubtask(2, 1)
caldavDao.insert(newCaldavCalendar(with(UUID, "1")))
caldavDao.insert( caldavDao.insert(
listOf( listOf(
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")), with(TASK, 1L), with(CALENDAR, "1"), with(REMOTE_ID, "a")),
newCaldavTask( newCaldavTask(
with(CaldavTaskMaker.TASK, 2L), with(TASK, 2L),
with(CALENDAR, "1"), with(CALENDAR, "1"),
with(REMOTE_PARENT, "a")))) with(REMOTE_PARENT, "a"))))
moveToCaldavList("2", 1, 2) moveToCaldavList("2", 1, 2)
@ -292,14 +307,18 @@ class TaskMoverTest : InjectingTestCase() {
} }
private suspend fun createSubtask(id: Long, parent: Long) { 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) { 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) { private suspend fun moveToCaldavList(calendar: String, vararg tasks: Long) {
taskMover.move(tasks.toList(), CaldavFilter(CaldavCalendar(name = "", uuid = calendar))) 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()) caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
assertNull(caldavDao.getTasks(tasks[0].id)[0].order) assertNull(taskDao.fetch(tasks[0].id)!!.order)
} }
@Test @Test
@ -116,7 +116,7 @@ class CaldavDaoShiftTests : InjectingTestCase() {
caldavDao.shiftDown("calendar", 0, created.toAppleEpoch()) caldavDao.shiftDown("calendar", 0, created.toAppleEpoch())
assertNull(caldavDao.getTasks(tasks[0].id)[0].order) assertNull(taskDao.fetch(tasks[0].id)!!.order)
} }
@Test @Test
@ -134,10 +134,11 @@ class CaldavDaoShiftTests : InjectingTestCase() {
} }
private suspend fun checkOrder(dateTime: DateTime?, task: TaskContainer) { private suspend fun checkOrder(dateTime: DateTime?, task: TaskContainer) {
val order = taskDao.fetch(task.id)!!.order
if (dateTime == null) { if (dateTime == null) {
assertNull(caldavDao.getTask(task.id)!!.order) assertNull(order)
} else { } 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.remoteParent = caldavDao.getRemoteIdForTask(task.parent)
} }
caldavTask.id = caldavDao.insert(caldavTask) caldavTask.id = caldavDao.insert(caldavTask)
t.caldavTask = caldavTask.toSubset() t.caldavTask = caldavTask
}
private fun CaldavTask.toSubset(): SubsetCaldav {
val result = SubsetCaldav()
result.cd_id = id
result.cd_calendar = calendar
result.cd_remote_parent = remoteParent
return result
} }
} }

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

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

@ -1,6 +1,5 @@
package org.tasks.data package org.tasks.data
import com.natpryce.makeiteasy.MakeItEasy.with
import dagger.hilt.android.testing.HiltAndroidTest import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -8,46 +7,22 @@ import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import org.tasks.injection.InjectingTestCase import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule 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 import javax.inject.Inject
@UninstallModules(ProductionModule::class) @UninstallModules(ProductionModule::class)
@HiltAndroidTest @HiltAndroidTest
class GoogleTaskListDaoTest : InjectingTestCase() { class GoogleTaskListDaoTest : InjectingTestCase() {
@Inject lateinit var googleTaskListDao: GoogleTaskListDao @Inject lateinit var googleTaskListDao: GoogleTaskListDao
@Inject lateinit var caldavDao: CaldavDao
@Test @Test
fun noResultsForEmptyAccount() = runBlocking { fun noResultsForEmptyAccount() = runBlocking {
val account = GoogleTaskAccount() val account = CaldavAccount().apply {
account.account = "user@gmail.com" uuid = "user@gmail.com"
googleTaskListDao.insert(account) username = "user@gmail.com"
}
caldavDao.insert(account)
assertTrue(googleTaskListDao.getGoogleTaskFilters(account.account!!).isEmpty()) assertTrue(googleTaskListDao.getGoogleTaskFilters(account.username!!).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"))
} }
} }

@ -14,32 +14,39 @@ import org.junit.Test
import org.tasks.R import org.tasks.R
import org.tasks.injection.InjectingTestCase import org.tasks.injection.InjectingTestCase
import org.tasks.injection.ProductionModule import org.tasks.injection.ProductionModule
import org.tasks.makers.GoogleTaskListMaker.REMOTE_ID import org.tasks.makers.CaldavAccountMaker.newCaldavAccount
import org.tasks.makers.GoogleTaskListMaker.newGoogleTaskList import org.tasks.makers.CaldavCalendarMaker.UUID
import org.tasks.makers.GoogleTaskMaker.LIST import org.tasks.makers.CaldavCalendarMaker.newCaldavCalendar
import org.tasks.makers.GoogleTaskMaker.ORDER import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.GoogleTaskMaker.PARENT import org.tasks.makers.CaldavTaskMaker.TASK
import org.tasks.makers.GoogleTaskMaker.TASK import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.GoogleTaskMaker.newGoogleTask
import org.tasks.makers.TaskMaker import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.ID 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 org.tasks.preferences.Preferences
import javax.inject.Inject import javax.inject.Inject
@UninstallModules(ProductionModule::class) @UninstallModules(ProductionModule::class)
@HiltAndroidTest @HiltAndroidTest
class ManualGoogleTaskQueryTest : InjectingTestCase() { class ManualGoogleTaskQueryTest : InjectingTestCase() {
@Inject lateinit var caldavDao: CaldavDao
@Inject lateinit var googleTaskDao: GoogleTaskDao @Inject lateinit var googleTaskDao: GoogleTaskDao
@Inject lateinit var taskDao: TaskDao @Inject lateinit var taskDao: TaskDao
@Inject lateinit var preferences: Preferences @Inject lateinit var preferences: Preferences
private val filter: GtasksFilter = GtasksFilter(newGoogleTaskList(with(REMOTE_ID, "1234"))) private lateinit var filter: GtasksFilter
@Before @Before
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
preferences.clear() preferences.clear()
preferences.setBoolean(R.string.p_manual_sort, true) 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 @Test
@ -100,8 +107,13 @@ class ManualGoogleTaskQueryTest : InjectingTestCase() {
} }
private suspend fun newTask(id: Long, order: Long, parent: Long = 0) { private suspend fun newTask(id: Long, order: Long, parent: Long = 0) {
taskDao.insert(TaskMaker.newTask(with(ID, id), with(UUID, UUIDHelper.newUUID()))) taskDao.insert(TaskMaker.newTask(
googleTaskDao.insert(newGoogleTask(with(LIST, filter.list.remoteId), with(TASK, id), with(PARENT, parent), with(ORDER, order))) 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 { 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
import org.tasks.makers.CaldavTaskMaker.CALENDAR import org.tasks.makers.CaldavTaskMaker.CALENDAR
import org.tasks.makers.CaldavTaskMaker.REMOTE_ID import org.tasks.makers.CaldavTaskMaker.REMOTE_ID
import org.tasks.makers.CaldavTaskMaker.REMOTE_ORDER
import org.tasks.makers.CaldavTaskMaker.newCaldavTask import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TagDataMaker.NAME import org.tasks.makers.TagDataMaker.NAME
import org.tasks.makers.TagDataMaker.newTagData 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.TagMaker.newTag
import org.tasks.makers.TaskMaker import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.COLLAPSED import org.tasks.makers.TaskMaker.COLLAPSED
import org.tasks.makers.TaskMaker.ORDER
import org.tasks.makers.TaskMaker.newTask import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime import org.tasks.time.DateTime
import java.util.* import java.util.*
@ -129,20 +129,18 @@ class OpenTasksPropertiesTests : OpenTasksTest() {
synchronizer.sync() synchronizer.sync()
assertEquals( val task = caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")!!.task
633734058L, assertEquals(633734058L, taskDao.fetch(task)?.order)
caldavDao.getTaskByRemoteId(list.uuid!!, "3076145036806467726")?.order
)
} }
@Test @Test
fun pushOrder() = runBlocking { fun pushOrder() = runBlocking {
val (listId, list) = openTaskDao.insertList() val (listId, list) = openTaskDao.insertList()
val task = newTask().apply { taskDao.createNew(this) } val task = newTask(with(ORDER, 5678L))
taskDao.createNew(task)
caldavDao.insert(newCaldavTask( caldavDao.insert(newCaldavTask(
with(CALENDAR, list.uuid), with(CALENDAR, list.uuid),
with(REMOTE_ID, "1234"), with(REMOTE_ID, "1234"),
with(REMOTE_ORDER, 5678L),
with(CaldavTaskMaker.TASK, task.id) with(CaldavTaskMaker.TASK, task.id)
)) ))

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

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

@ -104,7 +104,7 @@ class FilterViewHolder internal constructor(
} }
return when (filter) { return when (filter) {
is TagFilter -> R.drawable.ic_outline_label_24px 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 CaldavFilter -> R.drawable.ic_list_24px
is CustomFilter -> R.drawable.ic_outline_filter_list_24px is CustomFilter -> R.drawable.ic_outline_filter_list_24px
is PlaceFilter -> R.drawable.ic_outline_place_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) { override suspend fun moved(from: Int, to: Int, indent: Int) {
val task = getTask(from) val task = getTask(from)
val googleTask = task.googleTask val googleTask = task.caldavTask
val previous = if (to > 0) getTask(to - 1) else null val previous = if (to > 0) getTask(to - 1) else null
val list = googleTask.calendar!!
if (previous == null) { 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) { } else if (to == count || to <= from) {
when { when {
indent == 0 -> googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + if (to == count) 0 else 1) indent == 0 ->
previous.hasParent() && previous.parent == googleTask.parent -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + if (to == count) 0 else 1) googleTaskDao.move(
previous.hasParent() -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + 1) task = task.task,
else -> googleTaskDao.move(googleTask, previous.id, 0) 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 { } else {
when { when {
indent == 0 -> googleTaskDao.move(googleTask, 0, previous.getPrimarySort() + if (task.hasParent()) 1 else 0) indent == 0 ->
previous.hasParent() && previous.parent == googleTask.parent -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort()) googleTaskDao.move(
previous.hasParent() -> googleTaskDao.move(googleTask, previous.parent, previous.getSecondarySort() + 1) task = task.task,
else -> googleTaskDao.move(googleTask, previous.id, 0) 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) taskDao.touch(task.id)
localBroadcastManager.broadcastRefresh() localBroadcastManager.broadcastRefresh()
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
googleTaskDao.validateSorting(task.googleTaskList!!) googleTaskDao.validateSorting(task.caldav!!)
} }
} }
} }

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

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

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

@ -61,11 +61,6 @@ class SearchFilter : Filter {
.from(CaldavTask.TABLE) .from(CaldavTask.TABLE)
.join(Join.inner(CaldavCalendar.TABLE, CaldavCalendar.UUID.eq(CaldavTask.CALENDAR))) .join(Join.inner(CaldavCalendar.TABLE, CaldavCalendar.UUID.eq(CaldavTask.CALENDAR)))
.where(CaldavCalendar.NAME.like(matcher))), .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( Filter(
"No list", "No list",
QueryTemplate() QueryTemplate()
.join(Join.left(GoogleTask.TABLE, GoogleTask.TASK.eq(Task.ID)))
.join(Join.left(CaldavTask.TABLE, CaldavTask.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 { ).apply {
icon = R.drawable.ic_outline_cloud_off_24px icon = R.drawable.ic_outline_cloud_off_24px
} }
@ -116,13 +115,9 @@ class BuiltInFilterExposer @Inject constructor(
Filter( Filter(
"Missing list", "Missing list",
QueryTemplate() QueryTemplate()
.join(Join.left(GoogleTask.TABLE, GoogleTask.TASK.eq(Task.ID)))
.join(Join.left(CaldavTask.TABLE, CaldavTask.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))) .join(Join.left(CaldavCalendar.TABLE, CaldavCalendar.UUID.eq(CaldavTask.CALENDAR)))
.where(or( .where(and(CaldavTask.ID.gt(0), CaldavCalendar.UUID.eq(null)))
and(GoogleTask.ID.gt(0), GoogleTaskList.REMOTE_ID.eq(null)),
and(CaldavTask.ID.gt(0), CaldavCalendar.UUID.eq(null))))
).apply { ).apply {
icon = R.drawable.ic_outline_cloud_off_24px icon = R.drawable.ic_outline_cloud_off_24px
} }
@ -131,15 +126,10 @@ class BuiltInFilterExposer @Inject constructor(
Filter( Filter(
"Missing account", "Missing account",
QueryTemplate() 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(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(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))) .join(Join.left(CaldavAccount.TABLE, CaldavAccount.UUID.eq(CaldavCalendar.ACCOUNT)))
.where(or( .where(and(CaldavTask.ID.gt(0), CaldavAccount.UUID.eq(null)))
and(GoogleTask.ID.gt(0), GoogleTaskAccount.ACCOUNT.eq(null)),
and(CaldavTask.ID.gt(0), CaldavAccount.UUID.eq(null))))
).apply { ).apply {
icon = R.drawable.ic_outline_cloud_off_24px 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 public static final long APPLE_EPOCH = 978307200000L; // 1/1/2001 GMT
@SuppressLint("DefaultLocale") @SuppressLint("DefaultLocale")
public static final String CALDAV_ORDER_COLUMN = 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 = private static final String ADJUSTED_DUE_DATE =
"(CASE WHEN (dueDate / 1000) % 60 > 0 THEN dueDate ELSE (dueDate + 43140000) END)"; "(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"; select = "tasks.created AS sort_created";
break; break;
case SORT_GTASKS: case SORT_GTASKS:
select = "google_tasks.gt_order AS sort_manual"; select = "tasks.`order` AS sort_manual";
break; break;
case SORT_CALDAV: case SORT_CALDAV:
select = CALDAV_ORDER_COLUMN + " AS sort_manual"; select = CALDAV_ORDER_COLUMN + " AS sort_manual";

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

@ -69,6 +69,8 @@ class TaskDao @Inject constructor(
syncAdapters.sync() 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 setParent(parent: Long, tasks: List<Long>) = taskDao.setParent(parent, tasks)
suspend fun getChildren(ids: List<Long>) = taskDao.getChildren(ids) suspend fun getChildren(ids: List<Long>) = taskDao.getChildren(ids)

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

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

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

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

@ -55,7 +55,10 @@ class TaskCreator @Inject constructor(
val addToTop = preferences.addTasksToTop() val addToTop = preferences.addTasksToTop()
if (task.hasTransitory(GoogleTask.KEY)) { if (task.hasTransitory(GoogleTask.KEY)) {
googleTaskDao.insertAndShift( 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)) { } else if (task.hasTransitory(CaldavTask.KEY)) {
caldavDao.insert( caldavDao.insert(
task, CaldavTask(task.id, task.getTransitory<String>(CaldavTask.KEY)), addToTop) task, CaldavTask(task.id, task.getTransitory<String>(CaldavTask.KEY)), addToTop)
@ -63,7 +66,10 @@ class TaskCreator @Inject constructor(
val remoteList = defaultFilterProvider.getDefaultList() val remoteList = defaultFilterProvider.getDefaultList()
if (remoteList is GtasksFilter) { if (remoteList is GtasksFilter) {
googleTaskDao.insertAndShift( googleTaskDao.insertAndShift(
GoogleTask(task.id, remoteList.remoteId), addToTop) task,
CaldavTask(task.id, remoteList.remoteId, remoteId = null),
addToTop
)
} else if (remoteList is CaldavFilter) { } else if (remoteList is CaldavFilter) {
caldavDao.insert( caldavDao.insert(
task, CaldavTask(task.id, remoteList.uuid), addToTop) 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.api.Filter
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.coroutines.runBlocking
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.caldav.VtodoCache import org.tasks.caldav.VtodoCache
import org.tasks.data.* import org.tasks.data.*
@ -29,7 +28,6 @@ class TaskDeleter @Inject constructor(
suspend fun markDeleted(taskIds: List<Long>): List<Task> { suspend fun markDeleted(taskIds: List<Long>): List<Task> {
val ids = taskIds val ids = taskIds
.toSet() .toSet()
.plus(taskIds.chunkedMap(googleTaskDao::getChildren))
.plus(taskIds.chunkedMap(taskDao::getChildren)) .plus(taskIds.chunkedMap(taskDao::getChildren))
.let { taskDao.fetch(it.toList()) } .let { taskDao.fetch(it.toList()) }
.filterNot { it.readOnly } .filterNot { it.readOnly }
@ -51,7 +49,6 @@ class TaskDeleter @Inject constructor(
.map(TaskContainer::getId) .map(TaskContainer::getId)
.toMutableList() .toMutableList()
completed.removeAll(deletionDao.hasRecurringAncestors(completed)) completed.removeAll(deletionDao.hasRecurringAncestors(completed))
completed.removeAll(googleTaskDao.hasRecurringParent(completed))
markDeleted(completed) markDeleted(completed)
return completed.size return completed.size
} }
@ -66,18 +63,6 @@ class TaskDeleter @Inject constructor(
localBroadcastManager.broadcastRefresh() 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) { suspend fun delete(list: CaldavCalendar) {
vtodoCache.delete(list) vtodoCache.delete(list)
val tasks = deletionDao.delete(list) val tasks = deletionDao.delete(list)

@ -28,8 +28,7 @@ class TaskDuplicator @Inject constructor(
return taskIds return taskIds
.dbchunk() .dbchunk()
.flatMap { .flatMap {
it.minus(googleTaskDao.getChildren(it).toSet()) it.minus(taskDao.getChildren(it).toSet())
.minus(taskDao.getChildren(it).toSet())
} }
.let { taskDao.fetch(it) } .let { taskDao.fetch(it) }
.filterNot { it.readOnly } .filterNot { it.readOnly }
@ -58,7 +57,11 @@ class TaskDuplicator @Inject constructor(
val googleTask = googleTaskDao.getByTaskId(originalId) val googleTask = googleTaskDao.getByTaskId(originalId)
val addToTop = preferences.addTasksToTop() val addToTop = preferences.addTasksToTop()
if (googleTask != null) { 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) val caldavTask = caldavDao.getTask(originalId)
if (caldavTask != null) { if (caldavTask != null) {

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

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

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

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

@ -126,7 +126,6 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
lifecycleScope.launch { lifecycleScope.launch {
filterDao.resetOrders() filterDao.resetOrders()
caldavDao.resetOrders() caldavDao.resetOrders()
googleTaskListDao.resetOrders()
tagDataDao.resetOrders() tagDataDao.resetOrders()
locationDao.resetOrders() locationDao.resetOrders()
updateFilters() updateFilters()
@ -226,7 +225,7 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
private suspend fun setOrder(order: Int, filter: FilterListItem) { private suspend fun setOrder(order: Int, filter: FilterListItem) {
when (filter) { 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 CaldavFilter -> caldavDao.setOrder(filter.calendar.id, order)
is TagFilter -> tagDataDao.setOrder(filter.tagData.id!!, order) is TagFilter -> tagDataDao.setOrder(filter.tagData.id!!, order)
is CustomFilter -> filterDao.setOrder(filter.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 com.google.api.services.tasks.model.TaskList
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import org.tasks.data.GoogleTaskList import org.tasks.data.CaldavCalendar
import org.tasks.googleapis.InvokerFactory import org.tasks.googleapis.InvokerFactory
import org.tasks.ui.CompletableViewModel import org.tasks.ui.CompletableViewModel
import javax.inject.Inject import javax.inject.Inject
@ -10,7 +10,7 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class RenameListViewModel @Inject constructor( class RenameListViewModel @Inject constructor(
private val invoker: InvokerFactory) : CompletableViewModel<TaskList>() { private val invoker: InvokerFactory) : CompletableViewModel<TaskList>() {
suspend fun renameList(list: GoogleTaskList, name: String) { suspend fun renameList(list: CaldavCalendar, name: String) {
run { invoker.getGtasksInvoker(list.account!!).renameGtaskList(list.remoteId, name)!! } run { invoker.getGtasksInvoker(list.account!!).renameGtaskList(list.uuid, name)!! }
} }
} }

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -55,36 +55,6 @@ WHERE recurring = 1
ids.eachChunk(this::markDeletedInternal) 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") @Query("SELECT cd_task FROM caldav_tasks WHERE cd_calendar = :calendar AND cd_deleted = 0")
internal abstract suspend fun getActiveCaldavTasks(calendar: String): List<Long> internal abstract suspend fun getActiveCaldavTasks(calendar: String): List<Long>

@ -1,126 +1,15 @@
package org.tasks.data package org.tasks.data
import androidx.room.ColumnInfo @Deprecated("For backup use only")
import androidx.room.Entity data class GoogleTask(
import androidx.room.ForeignKey var remoteId: String? = "",
import androidx.room.Ignore var listId: String? = "",
import androidx.room.Index var remoteParent: String? = null,
import androidx.room.PrimaryKey var remoteOrder: Long = 0,
import com.todoroo.andlib.data.Table var lastSync: Long = 0,
import com.todoroo.astrid.data.Task var deleted: Long = 0,
) {
@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
companion object { companion object {
const val KEY = "gtasks" 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 package org.tasks.data
import android.os.Parcel @Deprecated("Only used for backup migration")
import android.os.Parcelable data class GoogleTaskAccount(
import androidx.core.os.ParcelCompat var account: String? = null,
import androidx.room.ColumnInfo var etag: String? = null,
import androidx.room.Entity var isCollapsed: Boolean = false,
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)
}
}
}

@ -2,195 +2,179 @@ package org.tasks.data
import androidx.room.* import androidx.room.*
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.coroutines.flow.Flow import org.tasks.data.CaldavAccount.Companion.TYPE_GOOGLE_TASKS
import org.tasks.db.SuspendDbUtils.chunkedMap
import org.tasks.time.DateTimeUtils.currentTimeMillis
@Dao @Dao
abstract class GoogleTaskDao { abstract class GoogleTaskDao {
@Insert @Insert
abstract suspend fun insert(task: GoogleTask): Long abstract suspend fun insert(task: CaldavTask): Long
@Insert @Insert
abstract suspend fun insert(tasks: Iterable<GoogleTask>) abstract suspend fun insert(tasks: Iterable<CaldavTask>)
@Transaction @Transaction
open suspend fun insertAndShift(task: GoogleTask, top: Boolean) { open suspend fun insertAndShift(task: Task, caldavTask: CaldavTask, top: Boolean) {
if (top) { if (top) {
task.order = 0 task.order = 0
shiftDown(task.listId!!, task.parent, 0) shiftDown(caldavTask.calendar!!, task.parent, 0)
} else { } 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) 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) 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) 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) internal abstract suspend fun shiftUp(listId: String, parent: Long, position: Long)
@Transaction @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 previousParent = task.parent
val previousPosition = task.order val previousPosition = task.order!!
if (newParent == previousParent) { if (newParent == previousParent) {
if (previousPosition < newPosition) { if (previousPosition < newPosition) {
shiftUp(task.listId, newParent, previousPosition, newPosition) shiftUp(list, newParent, previousPosition, newPosition)
} else { } else {
shiftDown(task.listId, newParent, previousPosition, newPosition) shiftDown(list, newParent, previousPosition, newPosition)
} }
} else { } else {
shiftUp(task.listId, previousParent, previousPosition) shiftUp(list, previousParent, previousPosition)
shiftDown(task.listId, newParent, newPosition) shiftDown(list, newParent, newPosition)
} }
task.parent = newParent task.parent = newParent
task.order = newPosition task.order = newPosition
update(task) update(task)
setMoved(task.id, list)
} }
@Query("UPDATE google_task_accounts SET gta_collapsed = :collapsed WHERE gta_id = :id") @Query("UPDATE caldav_tasks SET gt_moved = 1 WHERE cd_task = :task and cd_calendar = :list")
abstract suspend fun setCollapsed(id: Long, collapsed: Boolean) 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 suspend fun getByTaskId(taskId: Long): GoogleTask?
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted = 0 LIMIT 1") @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 fun watchGoogleTask(taskId: Long): Flow<GoogleTask?> abstract suspend fun getByTaskId(taskId: Long): CaldavTask?
@Update @Update
abstract suspend fun update(googleTask: GoogleTask) abstract suspend fun update(googleTask: CaldavTask)
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)
@Query("UPDATE google_tasks SET gt_deleted = :now WHERE gt_task = :task OR gt_parent = :task") @Update
abstract suspend fun markDeleted(task: Long, now: Long = currentTimeMillis()) abstract suspend fun update(task: Task)
@Delete @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") @Query("SELECT * FROM caldav_tasks WHERE cd_remote_id = :remoteId LIMIT 1")
abstract suspend fun getByRemoteId(remoteId: String): GoogleTask? abstract suspend fun getByRemoteId(remoteId: String): CaldavTask?
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId AND gt_deleted > 0") @Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId AND cd_deleted > 0")
abstract suspend fun getDeletedByTaskId(taskId: Long): List<GoogleTask> abstract suspend fun getDeletedByTaskId(taskId: Long): List<CaldavTask>
@Query("SELECT * FROM google_tasks WHERE gt_task = :taskId") @Query("SELECT * FROM caldav_tasks WHERE cd_task = :taskId")
abstract suspend fun getAllByTaskId(taskId: Long): List<GoogleTask> 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> 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") @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 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")
abstract suspend fun getBottom(listId: String, parent: Long): Long 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? 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? 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? abstract suspend fun getTask(remoteId: String): Long?
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) @Query(
@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> SELECT tasks.*, `order` AS primary_sort, NULL AS secondary_sort
FROM tasks
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) INNER JOIN caldav_tasks ON tasks._id = cd_task
@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") WHERE parent = 0
internal abstract suspend fun getByRemoteOrder(listId: String): List<GoogleTask> 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(""" @Query("""
UPDATE google_tasks UPDATE caldav_tasks
SET gt_parent = IFNULL((SELECT gt_task SET cd_remote_parent = CASE WHEN :parent == '' THEN NULL ELSE :parent END,
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,
gt_remote_order = :position gt_remote_order = :position
WHERE gt_remote_id = :id WHERE cd_remote_id = :id
""") """)
abstract suspend fun updatePosition(id: String, parent: String?, position: String) abstract suspend fun updatePosition(id: String, parent: String?, position: String)
@Transaction @Transaction
open suspend fun reposition(listId: String) { open suspend fun reposition(caldavDao: CaldavDao, listId: String) {
updateParents(listId) caldavDao.updateParents(listId)
val orderedTasks = getByRemoteOrder(listId) val orderedTasks = getByRemoteOrder(listId)
var subtasks = 0L var subtasks = 0L
var parent = 0L var parent = 0L
for (task in orderedTasks) { for (task in orderedTasks) {
if (task.parent > 0) { if (task.parent > 0) {
if (task.order != subtasks && !task.isMoved) { if (task.order != subtasks) {
task.order = subtasks task.order = subtasks
update(task) update(task)
} }
subtasks++ subtasks++
} else { } else {
subtasks = 0 subtasks = 0
if (task.order != parent && !task.isMoved) { if (task.order != parent) {
task.order = parent task.order = parent
update(task) update(task)
} }

@ -1,126 +1,14 @@
package org.tasks.data 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 com.todoroo.astrid.api.FilterListItem.NO_ORDER
import org.tasks.themes.CustomIcons.LIST
@Entity(tableName = "google_task_lists") @Deprecated("Only used for backup migration")
class GoogleTaskList : Parcelable { data class GoogleTaskList(
@PrimaryKey(autoGenerate = true) var account: String? = null,
@ColumnInfo(name = "gtl_id") var remoteId: String? = null,
@Transient var title: String? = null,
var id: Long = 0 var order: Int = NO_ORDER,
var lastSync: Long = 0,
@ColumnInfo(name = "gtl_account") var color: Int? = null,
var account: String? = null var icon: Int? = -1,
)
@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)
}
}
}

@ -1,78 +1,38 @@
package org.tasks.data package org.tasks.data
import androidx.lifecycle.LiveData
import androidx.room.* 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.filters.GoogleTaskFilters
import org.tasks.time.DateTimeUtils.currentTimeMillis import org.tasks.time.DateTimeUtils.currentTimeMillis
@Dao @Dao
interface GoogleTaskListDao { interface GoogleTaskListDao {
@Query("SELECT * FROM google_task_accounts WHERE gta_id = :id") @Query("SELECT * FROM caldav_accounts WHERE cda_account_type = $TYPE_GOOGLE_TASKS")
fun watchAccount(id: Long): LiveData<GoogleTaskAccount> suspend fun getAccounts(): List<CaldavAccount>
@Query("SELECT COUNT(*) FROM google_task_accounts") @Query("SELECT * FROM caldav_lists WHERE cdl_id = :id")
suspend fun accountCount(): Int suspend fun getById(id: Long): CaldavCalendar?
@Query("SELECT * FROM google_task_accounts") @Query("SELECT * FROM caldav_lists WHERE cdl_account = :account ORDER BY cdl_name ASC")
suspend fun getAccounts(): List<GoogleTaskAccount> suspend fun getLists(account: String): List<CaldavCalendar>
@Query("SELECT * FROM google_task_accounts") @Query("SELECT * FROM caldav_lists WHERE cdl_uuid = :remoteId LIMIT 1")
fun watchAccounts(): LiveData<List<GoogleTaskAccount>> suspend fun getByRemoteId(remoteId: String): CaldavCalendar?
@Query("SELECT * FROM google_task_accounts WHERE gta_account = :account COLLATE NOCASE LIMIT 1") @Query("SELECT * FROM caldav_lists WHERE cdl_uuid IN (:remoteIds)")
suspend fun getAccount(account: String): GoogleTaskAccount? suspend fun getByRemoteId(remoteIds: List<String>): List<CaldavCalendar>
@Query("SELECT * FROM google_task_lists WHERE gtl_id = :id") @Query("UPDATE caldav_lists SET cdl_last_sync = 0 WHERE cdl_account = :account")
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")
suspend fun resetLastSync(account: String) suspend fun resetLastSync(account: String)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrReplace(googleTaskList: GoogleTaskList): Long suspend fun insertOrReplace(googleTaskList: CaldavCalendar): Long
@Insert @Query("SELECT caldav_lists.*, COUNT(tasks._id) AS count"
suspend fun insert(googleTaskList: GoogleTaskList): Long + " FROM caldav_lists "
+ " LEFT JOIN caldav_tasks ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid"
@Insert + " 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"
suspend fun insert(googleTaskAccount: GoogleTaskAccount) + " WHERE caldav_lists.cdl_account = :account"
+ " GROUP BY caldav_lists.cdl_uuid")
@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 getGoogleTaskFilters(account: String, now: Long = currentTimeMillis()): List<GoogleTaskFilters> 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 class SubtaskInfo {
public boolean hasSubtasks; public boolean hasSubtasks;
public boolean hasGoogleSubtasks;
public boolean usesSubtasks() { public boolean usesSubtasks() {
return hasSubtasks || hasGoogleSubtasks; return hasSubtasks;
} }
} }

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

@ -1,12 +1,7 @@
package org.tasks.data package org.tasks.data
import androidx.paging.DataSource import androidx.paging.DataSource
import androidx.room.Dao import androidx.room.*
import androidx.room.Insert
import androidx.room.Query
import androidx.room.RawQuery
import androidx.room.Update
import androidx.room.withTransaction
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import com.todoroo.andlib.sql.Criterion import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Field 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()) abstract suspend fun setCompletionDate(remoteIds: List<String>, completionDate: Long, updateTime: Long = now())
@Query("SELECT tasks.* FROM tasks " @Query("SELECT tasks.* FROM tasks "
+ "LEFT JOIN google_tasks ON tasks._id = google_tasks.gt_task " + "LEFT JOIN caldav_tasks ON tasks._id = caldav_tasks.cd_task "
+ "WHERE gt_list_id IN (SELECT gtl_remote_id FROM google_task_lists WHERE gtl_account = :account)" + "LEFT JOIN caldav_lists ON caldav_tasks.cd_calendar = caldav_lists.cdl_uuid "
+ "AND (tasks.modified > google_tasks.gt_last_sync OR google_tasks.gt_remote_id = '' OR google_tasks.gt_deleted > 0) " + "WHERE cdl_account = :account "
+ "ORDER BY CASE WHEN gt_parent = 0 THEN 0 ELSE 1 END, gt_order ASC") + "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> abstract suspend fun getGoogleTasksToPush(account: String): List<Task>
@Query(""" @Query("""
@ -131,15 +127,7 @@ abstract class TaskDao(private val database: Database) {
@RawQuery @RawQuery
abstract suspend fun count(query: SimpleSQLiteQuery): Int abstract suspend fun count(query: SimpleSQLiteQuery): Int
@Query(""" @Query("SELECT EXISTS(SELECT 1 FROM tasks WHERE parent > 0 AND deleted = 0) AS hasSubtasks")
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
""")
abstract suspend fun getSubtaskInfo(): SubtaskInfo abstract suspend fun getSubtaskInfo(): SubtaskInfo
@RawQuery(observedEntities = [Place::class]) @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)") @Query("UPDATE tasks SET modified = :now WHERE _id in (:ids)")
internal abstract suspend fun internalTouch(ids: List<Long>, now: Long = currentTimeMillis()) 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>) = suspend fun setParent(parent: Long, tasks: List<Long>) =
tasks.eachChunk { setParentInternal(parent, it) } tasks.eachChunk { setParentInternal(parent, it) }
@ -250,10 +241,8 @@ FROM recursive_tasks
@Query(""" @Query("""
SELECT _id SELECT _id
FROM tasks 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 LEFT JOIN caldav_tasks ON _id = cd_task AND cd_deleted = 0
WHERE gt_id IS NULL WHERE cd_id IS NULL
AND cd_id IS NULL
AND parent = 0 AND parent = 0
""") """)
abstract suspend fun getLocalTasks(): List<Long> 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.activity.TaskListFragment
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.data.Task 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.TaskListQueryNonRecursive.getNonRecursiveQuery
import org.tasks.data.TaskListQueryRecursive.getRecursiveQuery import org.tasks.data.TaskListQueryRecursive.getRecursiveQuery
import org.tasks.preferences.QueryPreferences import org.tasks.preferences.QueryPreferences
object TaskListQuery { 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( private val JOIN_CALDAV = Criterion.and(
Task.ID.eq(field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_task")), Task.ID.eq(field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_task")),
field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_deleted").eq(0)) field("${TaskListFragment.CALDAV_METADATA_JOIN}.cd_deleted").eq(0))
val JOINS = """ 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(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(Geofence.TABLE, Geofence.TASK.eq(Task.ID))}
${Join.left(Place.TABLE, Place.UID.eq(Geofence.PLACE))} ${Join.left(Place.TABLE, Place.UID.eq(Geofence.PLACE))}
""".trimIndent() """.trimIndent()
val FIELDS = listOf( val FIELDS = listOf(
field("tasks.*"), field("tasks.*"),
field("${TaskListFragment.GTASK_METADATA_JOIN}.*"),
field("${TaskListFragment.CALDAV_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("geofences.*"),
field("places.*")) field("places.*"))
@ -37,11 +36,11 @@ object TaskListQuery {
subtasks: SubtaskInfo subtasks: SubtaskInfo
): MutableList<String> = when { ): MutableList<String> = when {
filter.supportsManualSort() && preferences.isManualSort -> filter.supportsManualSort() && preferences.isManualSort ->
getRecursiveQuery(filter, preferences, subtasks) getRecursiveQuery(filter, preferences)
filter.supportsAstridSorting() && preferences.isAstridSort -> filter.supportsAstridSorting() && preferences.isAstridSort ->
getNonRecursiveQuery(filter, preferences) getNonRecursiveQuery(filter, preferences)
filter.supportsSubtasks() && subtasks.usesSubtasks() && !preferences.usePagedQueries() -> filter.supportsSubtasks() && subtasks.usesSubtasks() && !preferences.usePagedQueries() ->
getRecursiveQuery(filter, preferences, subtasks) getRecursiveQuery(filter, preferences)
else -> getNonRecursiveQuery(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 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} ${TaskListQuery.JOINS}
""".trimIndent() """.trimIndent()
private val GOOGLE_SUBTASKS = private val SUBTASK_QUERY =
QueryTemplate() QueryTemplate()
.join(Join.inner(RECURSIVE, GoogleTask.PARENT.eq(RECURSIVE_TASK))) .join(Join.inner(RECURSIVE, Task.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))))
.where(activeAndVisible()) .where(activeAndVisible())
fun getRecursiveQuery( fun getRecursiveQuery(
filter: Filter, filter: Filter,
preferences: QueryPreferences, preferences: QueryPreferences,
subtasks: SubtaskInfo
): MutableList<String> { ): MutableList<String> {
var joinedQuery = JOINS var joinedQuery = JOINS
var where = " WHERE recursive_tasks.hidden = 0" var where = " WHERE recursive_tasks.hidden = 0"
val parentQuery: String val parentQuery: String
val subtaskQuery: QueryTemplate
when (filter) { when (filter) {
is CaldavFilter -> { is CaldavFilter -> {
parentQuery = newCaldavQuery(filter) parentQuery = newCaldavQuery(filter.uuid)
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())
} }
is GtasksFilter -> { is GtasksFilter -> {
parentQuery = newGoogleTaskQuery(filter) parentQuery = newCaldavQuery(filter.list.uuid!!)
subtaskQuery = GOOGLE_SUBTASKS
} }
else -> { else -> {
parentQuery = PermaSql.replacePlaceholdersForQuery(filter.getSqlQuery()) 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 " 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 " where += " AND indent = max_indent "
} }
@ -85,7 +65,7 @@ internal object TaskListQueryRecursive {
when { when {
manualSort && filter is GtasksFilter -> { manualSort && filter is GtasksFilter -> {
sortMode = SortHelper.SORT_GTASKS sortMode = SortHelper.SORT_GTASKS
sortField = "google_tasks.gt_order" sortField = "tasks.`order`"
} }
manualSort && filter is CaldavFilter -> { manualSort && filter is CaldavFilter -> {
sortMode = SortHelper.SORT_CALDAV 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 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 $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 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)} ORDER BY parent_complete ASC, sort_indent DESC, subtask_complete ASC, completion_sort DESC, ${SortHelper.orderForSortTypeRecursive(sortMode, reverseSort)}
) SELECT * FROM recursive_tasks ) SELECT * FROM recursive_tasks
""".trimIndent() """.trimIndent()
@ -127,26 +107,14 @@ internal object TaskListQueryRecursive {
.toString()) .toString())
} }
private fun newCaldavQuery(filter: CaldavFilter) = private fun newCaldavQuery(list: String) =
QueryTemplate() QueryTemplate()
.join(Join.inner( .join(Join.inner(
CaldavTask.TABLE, CaldavTask.TABLE,
Criterion.and( Criterion.and(
CaldavTask.CALENDAR.eq(filter.uuid), CaldavTask.CALENDAR.eq(list),
CaldavTask.TASK.eq(Task.ID), CaldavTask.TASK.eq(Task.ID),
CaldavTask.DELETED.eq(0)))) CaldavTask.DELETED.eq(0))))
.where(Criterion.and(activeAndVisible(), Task.PARENT.eq(0))) .where(Criterion.and(activeAndVisible(), Task.PARENT.eq(0)))
.toString() .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_REL_START
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.CaldavAccount.Companion.SERVER_UNKNOWN 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.CaldavCalendar.Companion.ACCESS_READ_ONLY
import org.tasks.data.OpenTaskDao.Companion.getLong import org.tasks.data.OpenTaskDao.Companion.getLong
import org.tasks.extensions.getLongOrNull 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( fun migrations(fileStorage: FileStorage) = arrayOf(
MIGRATION_35_36, MIGRATION_35_36,
MIGRATION_36_37, MIGRATION_36_37,
@ -631,6 +654,7 @@ object Migrations {
MIGRATION_84_85, MIGRATION_84_85,
MIGRATION_85_86, MIGRATION_85_86,
MIGRATION_86_87, MIGRATION_86_87,
MIGRATION_87_88,
) )
private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) { 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.R
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.data.GoogleTaskDao
import org.tasks.dialogs.FilterPicker.Companion.EXTRA_LISTS_ONLY import org.tasks.dialogs.FilterPicker.Companion.EXTRA_LISTS_ONLY
import org.tasks.filters.FilterProvider import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader import org.tasks.filters.NavigationDrawerSubheader
@ -37,7 +36,6 @@ class FilterPickerViewModel @Inject constructor(
private val inventory: Inventory, private val inventory: Inventory,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val preferences: Preferences, private val preferences: Preferences,
private val googleTaskDao: GoogleTaskDao,
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
) : ViewModel() { ) : ViewModel() {
private val listsOnly = savedStateHandle[EXTRA_LISTS_ONLY] ?: false private val listsOnly = savedStateHandle[EXTRA_LISTS_ONLY] ?: false
@ -70,8 +68,7 @@ class FilterPickerViewModel @Inject constructor(
when (subheader.subheaderType) { when (subheader.subheaderType) {
NavigationDrawerSubheader.SubheaderType.PREFERENCE -> NavigationDrawerSubheader.SubheaderType.PREFERENCE ->
preferences.setBoolean(subheader.id.toInt(), collapsed) preferences.setBoolean(subheader.id.toInt(), collapsed)
NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS -> NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS,
googleTaskDao.setCollapsed(subheader.id, collapsed)
NavigationDrawerSubheader.SubheaderType.CALDAV, NavigationDrawerSubheader.SubheaderType.CALDAV,
NavigationDrawerSubheader.SubheaderType.TASKS, NavigationDrawerSubheader.SubheaderType.TASKS,
NavigationDrawerSubheader.SubheaderType.ETESYNC -> 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.Criterion.Companion.or
import com.todoroo.andlib.sql.Field.Companion.field import com.todoroo.andlib.sql.Field.Companion.field
import com.todoroo.andlib.sql.Join.Companion.inner 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.Query.Companion.select
import com.todoroo.andlib.sql.UnaryCriterion.Companion.isNotNull import com.todoroo.andlib.sql.UnaryCriterion.Companion.isNotNull
import com.todoroo.astrid.api.BooleanCriterion import com.todoroo.astrid.api.*
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.data.Task import com.todoroo.astrid.data.Task
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.R import org.tasks.R
import org.tasks.data.Alarm import org.tasks.data.*
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.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import javax.inject.Inject import javax.inject.Inject
@ -147,12 +135,8 @@ class FilterCriteriaProvider @Inject constructor(
context.getString(R.string.custom_filter_has_subtask), context.getString(R.string.custom_filter_has_subtask),
select(Task.ID) select(Task.ID)
.from(Task.TABLE) .from(Task.TABLE)
.join(left(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent")))) .join(inner(Task.TABLE.`as`("children"), Task.ID.eq(field("children.parent"))))
.join(left(GoogleTask.TABLE, GoogleTask.PARENT.eq(Task.ID))) .where(isNotNull(field("children._id")))
.where(or(
isNotNull(field("children._id")),
isNotNull(GoogleTask.ID)
))
.toString() .toString()
) )
@ -162,11 +146,7 @@ class FilterCriteriaProvider @Inject constructor(
context.getString(R.string.custom_filter_is_subtask), context.getString(R.string.custom_filter_is_subtask),
select(Task.ID) select(Task.ID)
.from(Task.TABLE) .from(Task.TABLE)
.join(left(GoogleTask.TABLE, GoogleTask.TASK.eq(Task.ID))) .where(field("${Task.PARENT}>0").eq(1))
.where(or(
field("${Task.PARENT}>0").eq(1),
field("${GoogleTask.PARENT}>0").eq(1)
))
.toString() .toString()
) )
@ -297,26 +277,26 @@ class FilterCriteriaProvider @Inject constructor(
r.getString(R.string.CFC_title_contains_name)) r.getString(R.string.CFC_title_contains_name))
private suspend fun gtasksFilterCriteria(): CustomFilterCriterion { private suspend fun gtasksFilterCriteria(): CustomFilterCriterion {
val lists = googleTaskListDao.getAllLists() val lists = caldavDao.getGoogleTaskLists()
val listNames = arrayOfNulls<String>(lists.size) val listNames = arrayOfNulls<String>(lists.size)
val listIds = arrayOfNulls<String>(lists.size) val listIds = arrayOfNulls<String>(lists.size)
for (i in lists.indices) { for (i in lists.indices) {
listNames[i] = lists[i].title listNames[i] = lists[i].name
listIds[i] = lists[i].remoteId listIds[i] = lists[i].uuid
} }
val values: MutableMap<String, Any> = HashMap() val values: MutableMap<String, Any> = HashMap()
values[GoogleTask.KEY] = "?" values[GoogleTask.KEY] = "?"
return MultipleSelectCriterion( return MultipleSelectCriterion(
IDENTIFIER_GTASKS, IDENTIFIER_GTASKS,
context.getString(R.string.CFC_gtasks_list_text), context.getString(R.string.CFC_gtasks_list_text),
select(GoogleTask.TASK) select(CaldavTask.TASK)
.from(GoogleTask.TABLE) .from(CaldavTask.TABLE)
.join(inner(Task.TABLE, GoogleTask.TASK.eq(Task.ID))) .join(inner(Task.TABLE, CaldavTask.TASK.eq(Task.ID)))
.where( .where(
and( and(
activeAndVisible(), activeAndVisible(),
GoogleTask.DELETED.eq(0), CaldavTask.DELETED.eq(0),
GoogleTask.LIST.eq("?"))) CaldavTask.CALENDAR.eq("?")))
.toString(), .toString(),
values, values,
listNames, listNames,

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

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

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

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

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

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

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

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

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

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

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

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

@ -10,7 +10,6 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.composethemeadapter.MdcTheme import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.sql.Criterion import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.andlib.utility.DateUtilities.now import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
@ -23,7 +22,6 @@ import kotlinx.coroutines.launch
import org.tasks.R import org.tasks.R
import org.tasks.compose.collectAsStateLifecycleAware import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.edit.SubtaskRow import org.tasks.compose.edit.SubtaskRow
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao import org.tasks.data.GoogleTaskDao
import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
@ -57,8 +55,9 @@ class SubtaskControlSet : TaskEditControlFragment() {
setContent { setContent {
MdcTheme { MdcTheme {
SubtaskRow( SubtaskRow(
originalFilter = viewModel.originalList,
filter = viewModel.selectedList.collectAsStateLifecycleAware().value, filter = viewModel.selectedList.collectAsStateLifecycleAware().value,
googleTask = googleTaskDao.watchGoogleTask(viewModel.task.id).collectAsStateLifecycleAware(initial = null).value, hasParent = viewModel.hasParent,
desaturate = preferences.desaturateDarkMode, desaturate = preferences.desaturateDarkMode,
existingSubtasks = listViewModel.tasks.observeAsState(initial = emptyList()).value, existingSubtasks = listViewModel.tasks.observeAsState(initial = emptyList()).value,
newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value, newSubtasks = viewModel.newSubtasks.collectAsStateLifecycleAware().value,
@ -109,20 +108,10 @@ class SubtaskControlSet : TaskEditControlFragment() {
companion object { companion object {
const val TAG = R.string.TEA_ctrl_subtask_pref const val TAG = R.string.TEA_ctrl_subtask_pref
private fun getQueryTemplate(task: Task): QueryTemplate = QueryTemplate() 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( .where(
Criterion.and( Criterion.and(
activeAndVisible(), 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 estimatedSeconds = MutableStateFlow(task.estimatedSeconds)
val elapsedSeconds = MutableStateFlow(task.elapsedSeconds) val elapsedSeconds = MutableStateFlow(task.elapsedSeconds)
var newSubtasks = MutableStateFlow(emptyList<Task>()) var newSubtasks = MutableStateFlow(emptyList<Task>())
val hasParent: Boolean
get() = task.parent > 0
val dueDate = MutableStateFlow(task.dueDate) val dueDate = MutableStateFlow(task.dueDate)
@ -128,7 +130,7 @@ class TaskEditViewModel @Inject constructor(
} }
var selectedCalendar = MutableStateFlow(originalCalendar) var selectedCalendar = MutableStateFlow(originalCalendar)
private val originalList: Filter = savedStateHandle[TaskEditFragment.EXTRA_LIST]!! val originalList: Filter = savedStateHandle[TaskEditFragment.EXTRA_LIST]!!
var selectedList = MutableStateFlow(originalList) var selectedList = MutableStateFlow(originalList)
private var originalLocation: Location? = savedStateHandle[TaskEditFragment.EXTRA_LOCATION] private var originalLocation: Location? = savedStateHandle[TaskEditFragment.EXTRA_LOCATION]
@ -292,10 +294,10 @@ class TaskEditViewModel @Inject constructor(
firebase?.addTask("subtasks") firebase?.addTask("subtasks")
when (selectedList.value) { when (selectedList.value) {
is GtasksFilter -> { is GtasksFilter -> {
val googleTask = GoogleTask(subtask.id, (selectedList.value as GtasksFilter).remoteId) val googleTask = CaldavTask(subtask.id, (selectedList.value as GtasksFilter).remoteId, remoteId = null)
googleTask.parent = task.id subtask.parent = task.id
googleTask.isMoved = true googleTask.isMoved = true
googleTaskDao.insertAndShift(googleTask, false) googleTaskDao.insertAndShift(subtask, googleTask, false)
} }
is CaldavFilter -> { is CaldavFilter -> {
val caldavTask = CaldavTask(subtask.id, (selectedList.value as CaldavFilter).uuid) 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.andlib.utility.DateUtilities
import com.todoroo.astrid.api.CaldavFilter import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter import com.todoroo.astrid.api.TagFilter
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -74,13 +73,9 @@ class ChipProvider @Inject constructor(
} }
fun getListChip(filter: Filter?, task: TaskContainer): RemoteViews? { 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 task.caldav
?.takeIf { filter !is CaldavFilter } ?.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 } ?.let { return it }
return null return null
} }

@ -12,19 +12,18 @@ import net.fortuna.ical4j.model.property.Status
import org.junit.Assert.* import org.junit.Assert.*
import org.junit.Test import org.junit.Test
import org.tasks.date.DateTimeUtils.newDateTime 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.REMOTE_PARENT
import org.tasks.makers.CaldavTaskMaker.newCaldavTask import org.tasks.makers.CaldavTaskMaker.newCaldavTask
import org.tasks.makers.TaskMaker import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.COMPLETION_TIME import org.tasks.makers.TaskMaker.COMPLETION_TIME
import org.tasks.makers.TaskMaker.CREATION_TIME import org.tasks.makers.TaskMaker.CREATION_TIME
import org.tasks.makers.TaskMaker.newTask import org.tasks.makers.TaskMaker.newTask
import org.tasks.makers.iCalMaker
import org.tasks.makers.iCalMaker.COLLAPSED import org.tasks.makers.iCalMaker.COLLAPSED
import org.tasks.makers.iCalMaker.COMPLETED_AT import org.tasks.makers.iCalMaker.COMPLETED_AT
import org.tasks.makers.iCalMaker.CREATED_AT import org.tasks.makers.iCalMaker.CREATED_AT
import org.tasks.makers.iCalMaker.DESCRIPTION import org.tasks.makers.iCalMaker.DESCRIPTION
import org.tasks.makers.iCalMaker.DUE_DATE import org.tasks.makers.iCalMaker.DUE_DATE
import org.tasks.makers.iCalMaker.ORDER
import org.tasks.makers.iCalMaker.PARENT import org.tasks.makers.iCalMaker.PARENT
import org.tasks.makers.iCalMaker.PRIORITY import org.tasks.makers.iCalMaker.PRIORITY
import org.tasks.makers.iCalMaker.RRULE import org.tasks.makers.iCalMaker.RRULE
@ -567,9 +566,9 @@ class iCalendarMergeTest {
@Test @Test
fun remoteSetsOrder() = fun remoteSetsOrder() =
newCaldavTask() newTask()
.applyRemote( .applyRemote(
remote = newIcal(with(ORDER, 1234)), remote = newIcal(with(iCalMaker.ORDER, 1234)),
local = null local = null
) )
.let { .let {
@ -578,10 +577,10 @@ class iCalendarMergeTest {
@Test @Test
fun remoteRemovesOrder() = fun remoteRemovesOrder() =
newCaldavTask(with(REMOTE_ORDER, 1234)) newTask(with(TaskMaker.ORDER, 1234))
.applyRemote( .applyRemote(
remote = newIcal(), remote = newIcal(),
local = newIcal(with(ORDER, 1234)) local = newIcal(with(iCalMaker.ORDER, 1234))
) )
.let { .let {
assertNull(it.order) assertNull(it.order)
@ -589,10 +588,10 @@ class iCalendarMergeTest {
@Test @Test
fun localRemovesOrder() = fun localRemovesOrder() =
newCaldavTask() newTask()
.applyRemote( .applyRemote(
remote = newIcal(with(ORDER, 1234)), remote = newIcal(with(iCalMaker.ORDER, 1234)),
local = newIcal(with(ORDER, 1234)) local = newIcal(with(iCalMaker.ORDER, 1234))
) )
.let { .let {
assertNull(it.order) assertNull(it.order)
@ -600,10 +599,10 @@ class iCalendarMergeTest {
@Test @Test
fun localBeatsRemoteOrder() = fun localBeatsRemoteOrder() =
newCaldavTask(with(REMOTE_ORDER, 789)) newTask(with(TaskMaker.ORDER, 789L))
.applyRemote( .applyRemote(
remote = newIcal(with(ORDER, 456)), remote = newIcal(with(iCalMaker.ORDER, 456L)),
local = newIcal(with(ORDER, 123)) local = newIcal(with(iCalMaker.ORDER, 123))
) )
.let { .let {
assertEquals(789L, it.order) 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 import org.tasks.makers.Maker.make
object CaldavCalendarMaker { object CaldavCalendarMaker {
val ID: Property<CaldavCalendar, Long> = newProperty()
val ACCOUNT: Property<CaldavCalendar, String> = newProperty() val ACCOUNT: Property<CaldavCalendar, String> = newProperty()
val NAME: Property<CaldavCalendar, String> = newProperty()
val UUID: Property<CaldavCalendar, String> = newProperty() val UUID: Property<CaldavCalendar, String> = newProperty()
private val instantiator = Instantiator { lookup -> private val instantiator = Instantiator { lookup ->
CaldavCalendar( CaldavCalendar(
id = lookup.valueOf(ID, 0L),
name = lookup.valueOf(NAME, null as String?),
account = lookup.valueOf(ACCOUNT, "account"), 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 TASK: Property<CaldavTask, Long> = newProperty()
val REMOTE_ID: Property<CaldavTask, String?> = newProperty() val REMOTE_ID: Property<CaldavTask, String?> = newProperty()
val REMOTE_PARENT: 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 ETAG: Property<CaldavTask, String?> = newProperty()
val OBJECT: 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")) val task = CaldavTask(it.valueOf(TASK, 1L), it.valueOf(CALENDAR, "calendar"))
task.remoteId = it.valueOf(REMOTE_ID, task.remoteId) task.remoteId = it.valueOf(REMOTE_ID, task.remoteId)
task.remoteParent = it.valueOf(REMOTE_PARENT, null as String?) 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.etag = it.valueOf(ETAG, null as String?)
task.`object` = it.valueOf(OBJECT, task.remoteId?.let { id -> "$id.ics" }) task.`object` = it.valueOf(OBJECT, task.remoteId?.let { id -> "$id.ics" })
task 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 UUID: Property<Task, String> = newProperty()
val COLLAPSED: Property<Task, Boolean> = newProperty() val COLLAPSED: Property<Task, Boolean> = newProperty()
val DESCRIPTION: Property<Task, String?> = newProperty() val DESCRIPTION: Property<Task, String?> = newProperty()
val ORDER: Property<Task, Long> = newProperty()
private val instantiator = Instantiator { lookup: PropertyLookup<Task> -> private val instantiator = Instantiator { lookup: PropertyLookup<Task> ->
val task = Task() val task = Task()
@ -95,6 +96,7 @@ object TaskMaker {
task.creationDate = creationTime.millis task.creationDate = creationTime.millis
task.modificationDate = lookup.valueOf(MODIFICATION_TIME, creationTime).millis task.modificationDate = lookup.valueOf(MODIFICATION_TIME, creationTime).millis
task.parent = lookup.valueOf(PARENT, 0L) task.parent = lookup.valueOf(PARENT, 0L)
task.order = lookup.valueOf(ORDER, null as Long?)
task task
} }

Loading…
Cancel
Save