Synchronize snooze time

pull/1369/head
Alex Baker 3 years ago
parent bfd1316c33
commit 211f56a343

@ -1,3 +1,10 @@
### Next release
* Sync snooze time with Tasks.org, DAVx⁵, CalDAV, EteSync, and DecSync
* Compatible with Thunderbird
* Update translations
* Indonesian - when we were sober
### 11.4 (2021-02-09)
* Sync collapsed subtask state with Tasks.org, DAVx⁵, CalDAV, EteSync, and

@ -7,9 +7,12 @@ import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.runBlocking
import org.junit.Assert.*
import org.junit.Test
import org.tasks.SuspendFreeze.Companion.freezeAt
import org.tasks.TestUtilities.withTZ
import org.tasks.caldav.iCalendar.Companion.collapsed
import org.tasks.caldav.iCalendar.Companion.order
import org.tasks.caldav.iCalendar.Companion.parent
import org.tasks.caldav.iCalendar.Companion.snooze
import org.tasks.data.TagDao
import org.tasks.data.TagDataDao
import org.tasks.injection.ProductionModule
@ -25,7 +28,10 @@ import org.tasks.makers.TagMaker.TASK
import org.tasks.makers.TagMaker.newTag
import org.tasks.makers.TaskMaker
import org.tasks.makers.TaskMaker.COLLAPSED
import org.tasks.makers.TaskMaker.SNOOZE_TIME
import org.tasks.makers.TaskMaker.newTask
import org.tasks.time.DateTime
import java.util.*
import javax.inject.Inject
@UninstallModules(ProductionModule::class)
@ -193,12 +199,89 @@ class OpenTasksPropertiesTests : OpenTasksTest() {
)
}
@Test
fun readSnoozeTime() = runBlocking {
val (_, list) = withVtodo(SNOOZED)
withTZ(CHICAGO) {
synchronizer.sync()
}
val task = caldavDao
.getTaskByRemoteId(list.uuid!!, "4CBBC669-70E3-474D-A0A3-0FC42A14A5A5")
?.let { taskDao.fetch(it.task) }
assertEquals(1612972355000, task!!.reminderSnooze)
}
@Test
fun pushSnoozeTime() = withTZ(CHICAGO) {
val (listId, list) = openTaskDao.insertList()
val taskId = taskDao.createNew(newTask(
with(SNOOZE_TIME, DateTime(2021, 2, 4, 13, 30))
))
caldavDao.insert(newCaldavTask(
with(CALENDAR, list.uuid),
with(CaldavTaskMaker.TASK, taskId),
with(REMOTE_ID, "abcd")
))
freezeAt(DateTime(2021, 2, 4, 12, 30, 45, 125)) {
synchronizer.sync()
}
assertEquals(1612467000000, openTaskDao.getTask(listId, "abcd")?.task!!.snooze)
}
@Test
fun dontPushLapsedSnoozeTime() = withTZ(CHICAGO) {
val (listId, list) = openTaskDao.insertList()
val taskId = taskDao.createNew(newTask(
with(SNOOZE_TIME, DateTime(2021, 2, 4, 13, 30))
))
caldavDao.insert(newCaldavTask(
with(CALENDAR, list.uuid),
with(CaldavTaskMaker.TASK, taskId),
with(REMOTE_ID, "abcd")
))
freezeAt(DateTime(2021, 2, 4, 13, 30, 45, 125)) {
synchronizer.sync()
}
assertNull(openTaskDao.getTask(listId, "abcd")?.task!!.snooze)
}
@Test
fun removeSnoozeTime() = runBlocking {
val (listId, list) = withVtodo(SNOOZED)
synchronizer.sync()
val task = caldavDao.getTaskByRemoteId(list.uuid!!, "4CBBC669-70E3-474D-A0A3-0FC42A14A5A5")
taskDao.snooze(listOf(task!!.task), 0L)
synchronizer.sync()
assertNull(
openTaskDao
.getTask(listId, "4CBBC669-70E3-474D-A0A3-0FC42A14A5A5")
?.task
!!.snooze
)
}
private suspend fun insertTag(task: Task, name: String) =
newTagData(with(NAME, name))
.apply { tagDataDao.createNew(this) }
.let { tagDao.insert(newTag(with(TASK, task), with(TAGDATA, it))) }
companion object {
private val CHICAGO = TimeZone.getTimeZone("America/Chicago")
private val SUBTASK = """
BEGIN:VCALENDAR
VERSION:2.0
@ -260,5 +343,25 @@ class OpenTasksPropertiesTests : OpenTasksTest() {
END:VTODO
END:VCALENDAR
""".trimIndent()
private val SNOOZED = """
BEGIN:VCALENDAR
PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN
VERSION:2.0
BEGIN:VTODO
CREATED:20210210T151826Z
LAST-MODIFIED:20210210T152235Z
DTSTAMP:20210210T152235Z
UID:4CBBC669-70E3-474D-A0A3-0FC42A14A5A5
SUMMARY:Test snooze
STATUS:NEEDS-ACTION
X-MOZ-LASTACK:20210210T152235Z
DTSTART;TZID=America/Chicago:20210210T091900
DUE;TZID=America/Chicago:20210210T091900
X-MOZ-SNOOZE-TIME:20210210T155235Z
X-MOZ-GENERATION:1
END:VTODO
END:VCALENDAR
""".trimIndent()
}
}

@ -5,6 +5,7 @@
*/
package com.todoroo.astrid.dao
import com.todoroo.andlib.utility.DateUtilities.now
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.reminders.ReminderService
@ -16,7 +17,7 @@ import org.tasks.LocalBroadcastManager
import org.tasks.data.SubtaskInfo
import org.tasks.data.TaskContainer
import org.tasks.data.TaskDao
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.isAfterNow
import org.tasks.db.SuspendDbUtils.eachChunk
import org.tasks.jobs.WorkManager
import org.tasks.location.GeofenceApi
@ -49,6 +50,11 @@ class TaskDao @Inject constructor(
suspend fun setCompletionDate(remoteId: String, completionDate: Long) =
taskDao.setCompletionDate(remoteId, completionDate)
suspend fun snooze(taskIds: List<Long>, snoozeTime: Long, updateTime: Long = now()) {
taskDao.snooze(taskIds, snoozeTime, updateTime)
syncAdapters.sync()
}
suspend fun getGoogleTasksToPush(account: String): List<Task> =
taskDao.getGoogleTasksToPush(account)
@ -109,7 +115,10 @@ class TaskDao @Inject constructor(
timerPlugin.stopTimer(task)
}
}
if (task.dueDate != original?.dueDate && newDateTime(task.dueDate).isAfterNow) {
if (task.reminderSnooze.isAfterNow()) {
notificationManager.cancel(task.id)
}
if (task.dueDate != original?.dueDate && task.dueDate.isAfterNow()) {
notificationManager.cancel(task.id)
}
if (completionDateModified || deletionDateModified) {

@ -317,6 +317,7 @@ class Task : Parcelable {
&& calendarURI == task.calendarURI
&& parent == task.parent
&& remoteId == task.remoteId
&& reminderSnooze == task.reminderSnooze
}
fun googleTaskUpToDate(original: Task?): Boolean {
@ -350,6 +351,7 @@ class Task : Parcelable {
&& parent == original.parent
&& repeatUntil == original.repeatUntil
&& isCollapsed == original.isCollapsed
&& reminderSnooze == original.reminderSnooze
}
val isSaved: Boolean

@ -23,6 +23,7 @@ import org.tasks.caldav.GeoUtils.toGeo
import org.tasks.caldav.GeoUtils.toLikeString
import org.tasks.data.*
import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.jobs.WorkManager
import org.tasks.location.GeofenceApi
import org.tasks.preferences.Preferences
@ -172,6 +173,8 @@ class iCalendar @Inject constructor(
companion object {
private const val APPLE_SORT_ORDER = "X-APPLE-SORT-ORDER"
private const val OC_HIDESUBTASKS = "X-OC-HIDESUBTASKS"
private const val MOZ_SNOOZE_TIME = "X-MOZ-SNOOZE-TIME"
private const val MOZ_LASTACK = "X-MOZ-LASTACK"
private const val HIDE_SUBTASKS = "1"
private val IS_PARENT = { r: RelatedTo ->
r.parameters.getParameter<RelType>(Parameter.RELTYPE).let {
@ -179,13 +182,10 @@ class iCalendar @Inject constructor(
}
}
private val IS_APPLE_SORT_ORDER = { x: Property? ->
x?.name.equals(APPLE_SORT_ORDER, true)
}
private val IS_OC_HIDESUBTASKS = { x: Property? ->
x?.name.equals(OC_HIDESUBTASKS, true)
}
private val IS_APPLE_SORT_ORDER = { x: Property? -> x?.name.equals(APPLE_SORT_ORDER, true) }
private val IS_OC_HIDESUBTASKS = { x: Property? -> x?.name.equals(OC_HIDESUBTASKS, true) }
private val IS_MOZ_SNOOZE_TIME = { x: Property? -> x?.name.equals(MOZ_SNOOZE_TIME, true) }
private val IS_MOZ_LASTACK = { x: Property? -> x?.name.equals(MOZ_LASTACK, true) }
fun Due?.apply(task: com.todoroo.astrid.data.Task) {
task.dueDate = when (this?.date) {
@ -270,6 +270,32 @@ class iCalendar @Inject constructor(
}
}
var Task.snooze: Long?
get() = unknownProperties.find(IS_MOZ_SNOOZE_TIME)?.value?.let {
org.tasks.time.DateTime.from(DateTime(it)).toLocal().millis
}
set(value) {
value
?.toDateTime()
?.takeIf { it.isAfterNow }
?.toUTC()
?.let { DateTime(true).apply { time = it.millis } }
?.let { utc ->
unknownProperties.find(IS_MOZ_SNOOZE_TIME)
?.let { it.value = utc.toString() }
?: unknownProperties.add(
XProperty(MOZ_SNOOZE_TIME, utc.toString())
)
val lastAck = DateTime(true).apply { time = lastModified!! }
unknownProperties.find(IS_MOZ_LASTACK)
?.let { it.value = lastAck.toString() }
?: unknownProperties.add(
XProperty(MOZ_LASTACK, lastAck.toString())
)
}
?: unknownProperties.removeIf(IS_MOZ_SNOOZE_TIME)
}
fun com.todoroo.astrid.data.Task.applyRemote(remote: Task) {
val completedAt = remote.completedAt
if (completedAt != null) {
@ -297,6 +323,7 @@ class iCalendar @Inject constructor(
remote.due.apply(this)
remote.dtStart.apply(this)
isCollapsed = remote.collapsed
reminderSnooze = remote.snooze ?: 0
}
fun Task.applyLocal(caldavTask: CaldavTask, task: com.todoroo.astrid.data.Task) {
@ -358,6 +385,7 @@ class iCalendar @Inject constructor(
parent = if (task.parent == 0L) null else caldavTask.remoteParent
order = caldavTask.order
collapsed = task.isCollapsed
snooze = task.reminderSnooze
}
private fun getDate(timestamp: Long): Date {

@ -53,8 +53,8 @@ abstract class TaskDao(private val database: Database) {
@Query("UPDATE tasks SET completed = :completionDate " + "WHERE remoteId = :remoteId")
abstract suspend fun setCompletionDate(remoteId: String, completionDate: Long)
@Query("UPDATE tasks SET snoozeTime = :millis WHERE _id in (:taskIds)")
abstract suspend fun snooze(taskIds: List<Long>, millis: Long)
@Query("UPDATE tasks SET snoozeTime = :snoozeTime, modified = :updateTime WHERE _id in (:taskIds)")
internal abstract suspend fun snooze(taskIds: List<Long>, snoozeTime: Long, updateTime: Long = now())
@Query("SELECT tasks.* FROM tasks "
+ "LEFT JOIN google_tasks ON tasks._id = google_tasks.gt_task "
@ -186,9 +186,6 @@ FROM recursive_tasks
if (!task.insignificantChange(original)) {
task.modificationDate = now()
}
if (task.dueDate != original?.dueDate) {
task.reminderSnooze = 0
}
return updateInternal(task) == 1
}

@ -27,4 +27,6 @@ object DateTimeUtils {
fun Long.toAppleEpoch(): Long = DateTime(this).toAppleEpoch()
fun Long.toDateTime(): DateTime = DateTime(this)
fun Long.isAfterNow(): Boolean = DateTime(this).isAfterNow
}

@ -6,17 +6,15 @@ import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.reminders.ReminderService
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import org.tasks.activities.DateAndTimePickerActivity
import org.tasks.data.TaskDao
import org.tasks.dialogs.MyTimePickerDialog
import org.tasks.injection.InjectingAppCompatActivity
import org.tasks.notifications.NotificationManager
import org.tasks.reminders.SnoozeActivity.Companion.EXTRA_TASK_IDS
import org.tasks.reminders.SnoozeActivity.Companion.FLAGS
import org.tasks.themes.ThemeAccent
import org.tasks.time.DateTime
import java.util.*

Loading…
Cancel
Save