Update schema for recurrence

* Move repeat until into recurrence
* Move repeat from out of recurrence
pull/1935/head
Alex Baker 3 years ago
parent c52f90adb9
commit 57ca2f013a

File diff suppressed because it is too large Load Diff

@ -4,39 +4,8 @@ import androidx.room.AutoMigration
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import org.tasks.data.Alarm import org.tasks.data.*
import org.tasks.data.AlarmDao
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.ContentProviderDao
import org.tasks.data.DeletionDao
import org.tasks.data.Filter
import org.tasks.data.FilterDao
import org.tasks.data.Geofence
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskList
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.LocationDao
import org.tasks.data.Place
import org.tasks.data.Principal
import org.tasks.data.PrincipalAccess
import org.tasks.data.PrincipalDao
import org.tasks.data.Tag
import org.tasks.data.TagDao
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.data.TaskAttachment
import org.tasks.data.TaskAttachmentDao
import org.tasks.data.TaskDao import org.tasks.data.TaskDao
import org.tasks.data.TaskListMetadata
import org.tasks.data.TaskListMetadataDao
import org.tasks.data.UpgraderDao
import org.tasks.data.UserActivity
import org.tasks.data.UserActivityDao
import org.tasks.db.Migrations import org.tasks.db.Migrations
import org.tasks.notifications.Notification import org.tasks.notifications.Notification
import org.tasks.notifications.NotificationDao import org.tasks.notifications.NotificationDao
@ -66,7 +35,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 = 84 version = 85
) )
abstract class Database : RoomDatabase() { abstract class Database : RoomDatabase() {
abstract fun notificationDao(): NotificationDao abstract fun notificationDao(): NotificationDao

@ -15,7 +15,6 @@ import org.tasks.Strings
import org.tasks.data.Tag import org.tasks.data.Tag
import org.tasks.date.DateTimeUtils import org.tasks.date.DateTimeUtils
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
import timber.log.Timber import timber.log.Timber
@ -87,8 +86,8 @@ class Task : Parcelable {
@ColumnInfo(name = "recurrence") @ColumnInfo(name = "recurrence")
var recurrence: String? = null var recurrence: String? = null
@ColumnInfo(name = "repeatUntil") @ColumnInfo(name = "repeat_from", defaultValue = RepeatFrom.DUE_DATE.toString())
var repeatUntil = 0L var repeatFrom: Int = RepeatFrom.DUE_DATE
@ColumnInfo(name = "calendarUri") @ColumnInfo(name = "calendarUri")
var calendarURI: String? = null var calendarURI: String? = null
@ -127,7 +126,6 @@ class Task : Parcelable {
recurrence = parcel.readString() recurrence = parcel.readString()
ringFlags = parcel.readInt() ringFlags = parcel.readInt()
reminderLast = parcel.readLong() reminderLast = parcel.readLong()
repeatUntil = parcel.readLong()
timerStart = parcel.readLong() timerStart = parcel.readLong()
title = parcel.readString() title = parcel.readString()
remoteId = parcel.readString() ?: NO_UUID remoteId = parcel.readString() ?: NO_UUID
@ -197,7 +195,7 @@ class Task : Parcelable {
return dueDate < compareTo && !isCompleted return dueDate < compareTo && !isCompleted
} }
fun repeatAfterCompletion(): Boolean = recurrence.isRepeatAfterCompletion() fun repeatAfterCompletion(): Boolean = repeatFrom == RepeatFrom.COMPLETION_DATE
fun setDueDateAdjustingHideUntil(newDueDate: Long) { fun setDueDateAdjustingHideUntil(newDueDate: Long) {
if (dueDate > 0) { if (dueDate > 0) {
@ -211,18 +209,8 @@ class Task : Parcelable {
val isRecurring: Boolean val isRecurring: Boolean
get() = !Strings.isNullOrEmpty(recurrence) get() = !Strings.isNullOrEmpty(recurrence)
fun setRecurrence(rrule: String, afterCompletion: Boolean) {
recurrence = rrule + if (afterCompletion) ";FROM=COMPLETION" else ""
}
fun setRecurrence(rrule: Recur?) { fun setRecurrence(rrule: Recur?) {
if (rrule == null) { recurrence = rrule?.toString()
repeatUntil = 0
recurrence = null
} else {
repeatUntil = rrule.until?.let { DateTime(it).millis } ?: 0
recurrence = rrule.toString() + if (repeatAfterCompletion()) ";FROM=COMPLETION" else ""
}
} }
fun hasNotes(): Boolean { fun hasNotes(): Boolean {
@ -271,7 +259,6 @@ class Task : Parcelable {
dest.writeString(recurrence) dest.writeString(recurrence)
dest.writeInt(ringFlags) dest.writeInt(ringFlags)
dest.writeLong(reminderLast) dest.writeLong(reminderLast)
dest.writeLong(repeatUntil)
dest.writeLong(timerStart) dest.writeLong(timerStart)
dest.writeString(title) dest.writeString(title)
dest.writeString(remoteId) dest.writeString(remoteId)
@ -300,7 +287,6 @@ class Task : Parcelable {
&& elapsedSeconds == task.elapsedSeconds && elapsedSeconds == task.elapsedSeconds
&& ringFlags == task.ringFlags && ringFlags == task.ringFlags
&& recurrence == task.recurrence && recurrence == task.recurrence
&& repeatUntil == task.repeatUntil
&& calendarURI == task.calendarURI && calendarURI == task.calendarURI
&& parent == task.parent && parent == task.parent
&& remoteId == task.remoteId && remoteId == task.remoteId
@ -335,7 +321,6 @@ class Task : Parcelable {
&& notes == original.notes && notes == original.notes
&& recurrence == original.recurrence && recurrence == original.recurrence
&& parent == original.parent && parent == original.parent
&& repeatUntil == original.repeatUntil
&& isCollapsed == original.isCollapsed && isCollapsed == original.isCollapsed
} }
@ -418,7 +403,6 @@ class Task : Parcelable {
if (ringFlags != other.ringFlags) return false if (ringFlags != other.ringFlags) return false
if (reminderLast != other.reminderLast) return false if (reminderLast != other.reminderLast) return false
if (recurrence != other.recurrence) return false if (recurrence != other.recurrence) return false
if (repeatUntil != other.repeatUntil) return false
if (calendarURI != other.calendarURI) return false if (calendarURI != other.calendarURI) return false
if (remoteId != other.remoteId) return false if (remoteId != other.remoteId) return false
if (isCollapsed != other.isCollapsed) return false if (isCollapsed != other.isCollapsed) return false
@ -445,7 +429,6 @@ class Task : Parcelable {
result = 31 * result + ringFlags result = 31 * result + ringFlags
result = 31 * result + reminderLast.hashCode() result = 31 * result + reminderLast.hashCode()
result = 31 * result + (recurrence?.hashCode() ?: 0) result = 31 * result + (recurrence?.hashCode() ?: 0)
result = 31 * result + repeatUntil.hashCode()
result = 31 * result + (calendarURI?.hashCode() ?: 0) result = 31 * result + (calendarURI?.hashCode() ?: 0)
result = 31 * result + remoteId.hashCode() result = 31 * result + remoteId.hashCode()
result = 31 * result + isCollapsed.hashCode() result = 31 * result + isCollapsed.hashCode()
@ -455,7 +438,7 @@ class Task : Parcelable {
} }
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, repeatUntil=$repeatUntil, calendarURI=$calendarURI, remoteId='$remoteId', isCollapsed=$isCollapsed, parent=$parent, transitoryData=$transitoryData)" 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)"
} }
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@ -469,6 +452,15 @@ class Task : Parcelable {
} }
} }
@Retention(AnnotationRetention.SOURCE)
@IntDef(RepeatFrom.DUE_DATE, RepeatFrom.COMPLETION_DATE)
annotation class RepeatFrom {
companion object {
const val DUE_DATE = 0
const val COMPLETION_DATE = 1
}
}
fun clone(): Task { fun clone(): Task {
val parcel = Parcel.obtain() val parcel = Parcel.obtain()
writeToParcel(parcel, 0) writeToParcel(parcel, 0)
@ -615,8 +607,5 @@ class Task : Parcelable {
return NO_UUID == uuid || Strings.isNullOrEmpty(uuid) return NO_UUID == uuid || Strings.isNullOrEmpty(uuid)
} }
fun String?.isRepeatAfterCompletion() = this?.contains("FROM=COMPLETION") ?: false
fun String?.withoutFrom(): String? = this?.replace(";?FROM=[^;]*".toRegex(), "")
} }
} }

@ -65,7 +65,7 @@ class RepeatTaskHelper @Inject constructor(
} }
if (count > 1) { if (count > 1) {
rrule.count = count - 1 rrule.count = count - 1
task.setRecurrence(rrule.toString(), repeatAfterCompletion) task.setRecurrence(rrule)
} }
task.reminderLast = 0L task.reminderLast = 0L
task.completionDate = 0L task.completionDate = 0L
@ -95,7 +95,7 @@ class RepeatTaskHelper @Inject constructor(
if (count > 0) { if (count > 0) {
recur.count = count + 1 recur.count = count + 1
} }
task.setRecurrence(recur.toString(), task.repeatAfterCompletion()) task.setRecurrence(recur)
val newDueDate = task.dueDate val newDueDate = task.dueDate
task.setDueDateAdjustingHideUntil( task.setDueDateAdjustingHideUntil(
if (oldDueDate > 0) { if (oldDueDate > 0) {
@ -128,8 +128,7 @@ class RepeatTaskHelper @Inject constructor(
companion object { companion object {
private val weekdayCompare = Comparator { object1: WeekDay, object2: WeekDay -> WeekDay.getCalendarDay(object1) - WeekDay.getCalendarDay(object2) } private val weekdayCompare = Comparator { object1: WeekDay, object2: WeekDay -> WeekDay.getCalendarDay(object1) - WeekDay.getCalendarDay(object2) }
private fun repeatFinished(newDueDate: Long, repeatUntil: Long): Boolean { private fun repeatFinished(newDueDate: Long, repeatUntil: Long): Boolean {
return (repeatUntil > 0 return repeatUntil > 0 && newDateTime(newDueDate).startOfDay().millis > repeatUntil
&& newDateTime(newDueDate).startOfDay().isAfter(newDateTime(repeatUntil).startOfDay()))
} }
/** Compute next due date */ /** Compute next due date */
@ -267,5 +266,14 @@ class RepeatTaskHelper @Inject constructor(
val newDueDate = startDate.millis + millis * recur.interval val newDueDate = startDate.millis + millis * recur.interval
return createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, newDueDate) return createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, newDueDate)
} }
private val Task.repeatUntil: Long
get() = recurrence
?.takeIf { it.isNotBlank() }
?.let { newRecur(it) }
?.until
?.let { DateTime.from(it) }
?.millis
?: 0L
} }
} }

@ -17,23 +17,11 @@ import com.todoroo.astrid.helper.UUIDHelper
import com.todoroo.astrid.utility.TitleParser.parse import com.todoroo.astrid.utility.TitleParser.parse
import org.tasks.R import org.tasks.R
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.data.Alarm import org.tasks.data.*
import org.tasks.data.Alarm.Companion.TYPE_RANDOM import org.tasks.data.Alarm.Companion.TYPE_RANDOM
import org.tasks.data.Alarm.Companion.whenDue import org.tasks.data.Alarm.Companion.whenDue
import org.tasks.data.Alarm.Companion.whenOverdue import org.tasks.data.Alarm.Companion.whenOverdue
import org.tasks.data.Alarm.Companion.whenStarted import org.tasks.data.Alarm.Companion.whenStarted
import org.tasks.data.AlarmDao
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.Geofence
import org.tasks.data.GoogleTask
import org.tasks.data.GoogleTaskDao
import org.tasks.data.LocationDao
import org.tasks.data.Place
import org.tasks.data.Tag
import org.tasks.data.TagDao
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
@ -116,9 +104,12 @@ class TaskCreator @Inject constructor(
preferences.getStringValue(R.string.p_default_recurrence) preferences.getStringValue(R.string.p_default_recurrence)
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }
?.let { ?.let {
task.setRecurrence( task.recurrence = it
it, task.repeatFrom = if (preferences.getIntegerFromString(R.string.p_default_recurrence_from, 0) == 1) {
preferences.getIntegerFromString(R.string.p_default_recurrence_from, 0) == 1) Task.RepeatFrom.COMPLETION_DATE
} else {
Task.RepeatFrom.DUE_DATE
}
} }
preferences.getStringValue(R.string.p_default_location) preferences.getStringValue(R.string.p_default_location)
?.takeIf { it.isNotBlank() } ?.takeIf { it.isNotBlank() }

@ -18,22 +18,7 @@ import org.tasks.caldav.iCalendar
import org.tasks.caldav.iCalendar.Companion.fromVtodo import org.tasks.caldav.iCalendar.Companion.fromVtodo
import org.tasks.caldav.iCalendar.Companion.order import org.tasks.caldav.iCalendar.Companion.order
import org.tasks.caldav.iCalendar.Companion.parent import org.tasks.caldav.iCalendar.Companion.parent
import org.tasks.data.CaldavDao import org.tasks.data.*
import org.tasks.data.CaldavTask
import org.tasks.data.CaldavTaskContainer
import org.tasks.data.FilterDao
import org.tasks.data.GoogleTaskAccount
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.Location
import org.tasks.data.LocationDao
import org.tasks.data.Tag
import org.tasks.data.TagDao
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.data.TaskAttachmentDao
import org.tasks.data.UpgraderDao
import org.tasks.data.UserActivityDao
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.widget.AppWidgetManager import org.tasks.widget.AppWidgetManager
@ -367,6 +352,7 @@ class Upgrader @Inject constructor(
const val V11_13 = 111300 const val V11_13 = 111300
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
@JvmStatic @JvmStatic
fun getAndroidColor(context: Context, index: Int): Int { fun getAndroidColor(context: Context, index: Int): Int {

@ -12,26 +12,16 @@ import com.todoroo.astrid.service.TaskCreator.Companion.getDefaultAlarms
import com.todoroo.astrid.service.TaskMover import com.todoroo.astrid.service.TaskMover
import com.todoroo.astrid.service.Upgrader import com.todoroo.astrid.service.Upgrader
import com.todoroo.astrid.service.Upgrader.Companion.V12_4 import com.todoroo.astrid.service.Upgrader.Companion.V12_4
import com.todoroo.astrid.service.Upgrader.Companion.V12_8
import com.todoroo.astrid.service.Upgrader.Companion.V6_4 import com.todoroo.astrid.service.Upgrader.Companion.V6_4
import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor import com.todoroo.astrid.service.Upgrader.Companion.getAndroidColor
import org.tasks.LocalBroadcastManager 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.AlarmDao import org.tasks.data.*
import org.tasks.data.CaldavDao
import org.tasks.data.FilterDao
import org.tasks.data.Geofence
import org.tasks.data.GoogleTaskDao
import org.tasks.data.GoogleTaskListDao
import org.tasks.data.LocationDao
import org.tasks.data.Place.Companion.newPlace import org.tasks.data.Place.Companion.newPlace
import org.tasks.data.Tag import org.tasks.db.Migrations.isRepeatAfterCompletion
import org.tasks.data.TagDao import org.tasks.db.Migrations.withoutFrom
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.data.TaskAttachmentDao
import org.tasks.data.TaskListMetadataDao
import org.tasks.data.UserActivityDao
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import timber.log.Timber import timber.log.Timber
import java.io.FileNotFoundException import java.io.FileNotFoundException
@ -166,6 +156,14 @@ class TasksJsonImporter @Inject constructor(
} }
taskDao.save(task) taskDao.save(task)
} }
if (version < V12_8) {
task.repeatFrom = if (task.recurrence.isRepeatAfterCompletion()) {
Task.RepeatFrom.COMPLETION_DATE
} else {
Task.RepeatFrom.DUE_DATE
}
task.recurrence = task.recurrence.withoutFrom()
}
for (comment in backup.comments) { for (comment in backup.comments) {
comment.targetId = taskUuid comment.targetId = taskUuid
if (version < V6_4) { if (version < V6_4) {

@ -17,36 +17,16 @@ import net.fortuna.ical4j.model.Parameter
import net.fortuna.ical4j.model.Property import net.fortuna.ical4j.model.Property
import net.fortuna.ical4j.model.component.VAlarm import net.fortuna.ical4j.model.component.VAlarm
import net.fortuna.ical4j.model.parameter.RelType import net.fortuna.ical4j.model.parameter.RelType
import net.fortuna.ical4j.model.property.Action import net.fortuna.ical4j.model.property.*
import net.fortuna.ical4j.model.property.Completed
import net.fortuna.ical4j.model.property.DateProperty
import net.fortuna.ical4j.model.property.DtStart
import net.fortuna.ical4j.model.property.Due
import net.fortuna.ical4j.model.property.Geo
import net.fortuna.ical4j.model.property.RelatedTo
import net.fortuna.ical4j.model.property.Status
import net.fortuna.ical4j.model.property.XProperty
import org.tasks.Strings.isNullOrEmpty import org.tasks.Strings.isNullOrEmpty
import org.tasks.caldav.GeoUtils.equalish import org.tasks.caldav.GeoUtils.equalish
import org.tasks.caldav.GeoUtils.toGeo import org.tasks.caldav.GeoUtils.toGeo
import org.tasks.caldav.GeoUtils.toLikeString import org.tasks.caldav.GeoUtils.toLikeString
import org.tasks.caldav.extensions.toAlarms import org.tasks.caldav.extensions.toAlarms
import org.tasks.caldav.extensions.toVAlarms import org.tasks.caldav.extensions.toVAlarms
import org.tasks.data.Alarm import org.tasks.data.*
import org.tasks.data.Alarm.Companion.TYPE_RANDOM import org.tasks.data.Alarm.Companion.TYPE_RANDOM
import org.tasks.data.Alarm.Companion.TYPE_SNOOZE import org.tasks.data.Alarm.Companion.TYPE_SNOOZE
import org.tasks.data.AlarmDao
import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.SERVER_SYNOLOGY_CALENDAR
import org.tasks.data.CaldavCalendar
import org.tasks.data.CaldavDao
import org.tasks.data.CaldavTask
import org.tasks.data.Geofence
import org.tasks.data.LocationDao
import org.tasks.data.Place
import org.tasks.data.TagDao
import org.tasks.data.TagData
import org.tasks.data.TagDataDao
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.date.DateTimeUtils.toDateTime import org.tasks.date.DateTimeUtils.toDateTime
import org.tasks.date.DateTimeUtils.toLocal import org.tasks.date.DateTimeUtils.toLocal
@ -55,7 +35,6 @@ import org.tasks.location.GeofenceApi
import org.tasks.notifications.NotificationManager import org.tasks.notifications.NotificationManager
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.repeats.RecurrenceUtils.newRRule import org.tasks.repeats.RecurrenceUtils.newRRule
import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
import org.tasks.time.DateTimeUtils.startOfMinute import org.tasks.time.DateTimeUtils.startOfMinute
import org.tasks.time.DateTimeUtils.toDate import org.tasks.time.DateTimeUtils.toDate
@ -434,14 +413,7 @@ class iCalendar @Inject constructor(
} }
rRule = if (task.isRecurring) { rRule = if (task.isRecurring) {
try { try {
val recur = newRecur(task.recurrence!!) newRRule(task.recurrence!!)
val repeatUntil = task.repeatUntil
recur.until = if (repeatUntil > 0) {
DateTime(newDateTime(repeatUntil).toUTC().millis)
} else {
null
}
newRRule(recur.toString())
} catch (e: ParseException) { } catch (e: ParseException) {
Timber.e(e) Timber.e(e)
null null

@ -2,7 +2,6 @@ package org.tasks.caldav
import at.bitfire.ical4android.Task import at.bitfire.ical4android.Task
import com.todoroo.andlib.utility.DateUtilities import com.todoroo.andlib.utility.DateUtilities
import com.todoroo.astrid.data.Task.Companion.withoutFrom
import com.todoroo.astrid.data.Task.Priority.Companion.HIGH import com.todoroo.astrid.data.Task.Priority.Companion.HIGH
import com.todoroo.astrid.data.Task.Priority.Companion.LOW import com.todoroo.astrid.data.Task.Priority.Companion.LOW
import com.todoroo.astrid.data.Task.Priority.Companion.MEDIUM import com.todoroo.astrid.data.Task.Priority.Companion.MEDIUM
@ -16,7 +15,6 @@ import org.tasks.caldav.iCalendar.Companion.toMillis
import org.tasks.data.CaldavTask import org.tasks.data.CaldavTask
import org.tasks.date.DateTimeUtils.newDateTime import org.tasks.date.DateTimeUtils.newDateTime
import org.tasks.time.DateTime.UTC import org.tasks.time.DateTime.UTC
import org.tasks.time.DateTimeUtils.startOfMinute
import org.tasks.time.DateTimeUtils.startOfSecond import org.tasks.time.DateTimeUtils.startOfSecond
fun com.todoroo.astrid.data.Task.applyRemote( fun com.todoroo.astrid.data.Task.applyRemote(
@ -87,7 +85,7 @@ private fun com.todoroo.astrid.data.Task.applyPriority(remote: Task, local: Task
} }
private fun com.todoroo.astrid.data.Task.applyRecurrence(remote: Task, local: Task?) { private fun com.todoroo.astrid.data.Task.applyRecurrence(remote: Task, local: Task?) {
if (local == null || local.rRule?.recur?.toString() == recurrence.withoutFrom()) { if (local == null || local.rRule?.recur?.toString() == recurrence) {
setRecurrence(remote.rRule?.recur) setRecurrence(remote.rRule?.recur)
} }
} }

@ -6,6 +6,7 @@ import androidx.room.migration.AutoMigrationSpec
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import com.todoroo.astrid.api.FilterListItem.NO_ORDER import com.todoroo.astrid.api.FilterListItem.NO_ORDER
import com.todoroo.astrid.data.Task
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AFTER_DEADLINE import com.todoroo.astrid.data.Task.Companion.NOTIFY_AFTER_DEADLINE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_DEADLINE import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_DEADLINE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_START import com.todoroo.astrid.data.Task.Companion.NOTIFY_AT_START
@ -15,7 +16,10 @@ 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.OpenTaskDao.Companion.getLong
import org.tasks.extensions.getString import org.tasks.extensions.getString
import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTime
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit.HOURS import java.util.concurrent.TimeUnit.HOURS
@ -530,6 +534,36 @@ object Migrations {
} }
} }
private val MIGRATION_84_85 = object : Migration(84, 85) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE `tasks` ADD COLUMN `repeat_from` INTEGER NOT NULL DEFAULT ${Task.RepeatFrom.DUE_DATE}")
database
.query("SELECT `_id`, `repeatUntil`, `recurrence` FROM `tasks` WHERE `repeatUntil` > 0")
.use {
while (it.moveToNext()) {
val id = it.getLong("_id")
val repeatUntil = it.getLong("repeatUntil")
val recurrence = it.getString("recurrence") ?: continue
val recur = newRecur(recurrence.withoutFrom()!!).apply {
until = DateTime(repeatUntil).toDate()
}
val repeatFrom = if (recurrence.isRepeatAfterCompletion()) {
Task.RepeatFrom.COMPLETION_DATE
} else {
Task.RepeatFrom.DUE_DATE
}
database.execSQL("UPDATE `tasks` SET `repeat_from` = $repeatFrom, `recurrence` = '$recur' WHERE `_id` = $id")
}
}
database.execSQL("CREATE TABLE IF NOT EXISTS `_new_tasks` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `importance` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `hideUntil` INTEGER NOT NULL, `created` INTEGER NOT NULL, `modified` INTEGER NOT NULL, `completed` INTEGER NOT NULL, `deleted` INTEGER NOT NULL, `notes` TEXT, `estimatedSeconds` INTEGER NOT NULL, `elapsedSeconds` INTEGER NOT NULL, `timerStart` INTEGER NOT NULL, `notificationFlags` INTEGER NOT NULL, `lastNotified` INTEGER NOT NULL, `recurrence` TEXT, `repeat_from` INTEGER NOT NULL DEFAULT 0, `calendarUri` TEXT, `remoteId` TEXT, `collapsed` INTEGER NOT NULL, `parent` INTEGER NOT NULL)")
database.execSQL("INSERT INTO `_new_tasks` (`parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`,`repeat_from`) SELECT `parent`,`notes`,`timerStart`,`estimatedSeconds`,`importance`,`created`,`collapsed`,`dueDate`,`completed`,`title`,`hideUntil`,`remoteId`,`recurrence`,`deleted`,`notificationFlags`,`calendarUri`,`modified`,`_id`,`lastNotified`,`elapsedSeconds`,`repeat_from` FROM `tasks`")
database.execSQL("DROP TABLE `tasks`")
database.execSQL("ALTER TABLE `_new_tasks` RENAME TO `tasks`")
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `t_rid` ON `tasks` (`remoteId`)")
database.execSQL("CREATE INDEX IF NOT EXISTS `active_and_visible` ON `tasks` (`completed`, `deleted`, `hideUntil`)")
}
}
fun migrations(fileStorage: FileStorage) = arrayOf( fun migrations(fileStorage: FileStorage) = arrayOf(
MIGRATION_35_36, MIGRATION_35_36,
MIGRATION_36_37, MIGRATION_36_37,
@ -570,9 +604,14 @@ object Migrations {
MIGRATION_80_81, MIGRATION_80_81,
migration_81_82(fileStorage), migration_81_82(fileStorage),
MIGRATION_82_83, MIGRATION_82_83,
MIGRATION_84_85,
) )
private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) { private fun noop(from: Int, to: Int): Migration = object : Migration(from, to) {
override fun migrate(database: SupportSQLiteDatabase) {} override fun migrate(database: SupportSQLiteDatabase) {}
} }
fun String?.isRepeatAfterCompletion() = this?.contains("FROM=COMPLETION") ?: false
fun String?.withoutFrom(): String? = this?.replace(";?FROM=[^;]*".toRegex(), "")
} }

@ -1,7 +1,6 @@
package org.tasks.repeats package org.tasks.repeats
import com.todoroo.astrid.data.Task.Companion.sanitizeRecur import com.todoroo.astrid.data.Task.Companion.sanitizeRecur
import com.todoroo.astrid.data.Task.Companion.withoutFrom
import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.property.RRule import net.fortuna.ical4j.model.property.RRule
@ -15,6 +14,6 @@ object RecurrenceUtils {
fun newRecur(rrule: String): Recur = newRRule(rrule).recur fun newRecur(rrule: String): Recur = newRRule(rrule).recur
fun newRRule(rrule: String): RRule = fun newRRule(rrule: String): RRule =
RRule(rrule.replace(LEGACY_RRULE_PREFIX, "").withoutFrom().sanitizeRecur()) RRule(rrule.replace(LEGACY_RRULE_PREFIX, "").sanitizeRecur())
} }

@ -104,10 +104,6 @@ public class DateTime {
} }
} }
public DateTime(Date date) {
this(date.getTime());
}
private DateTime setTime(int hours, int minutes, int seconds, int milliseconds) { private DateTime setTime(int hours, int minutes, int seconds, int milliseconds) {
Calendar calendar = getCalendar(); Calendar calendar = getCalendar();
calendar.set(Calendar.HOUR_OF_DAY, hours); calendar.set(Calendar.HOUR_OF_DAY, hours);

@ -15,8 +15,6 @@ import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_FIVE
import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_NONSTOP import com.todoroo.astrid.data.Task.Companion.NOTIFY_MODE_NONSTOP
import com.todoroo.astrid.data.Task.Companion.createDueDate import com.todoroo.astrid.data.Task.Companion.createDueDate
import com.todoroo.astrid.data.Task.Companion.hasDueTime import com.todoroo.astrid.data.Task.Companion.hasDueTime
import com.todoroo.astrid.data.Task.Companion.isRepeatAfterCompletion
import com.todoroo.astrid.data.Task.Companion.withoutFrom
import com.todoroo.astrid.gcal.GCalHelper import com.todoroo.astrid.gcal.GCalHelper
import com.todoroo.astrid.service.TaskCompleter import com.todoroo.astrid.service.TaskCompleter
import com.todoroo.astrid.service.TaskDeleter import com.todoroo.astrid.service.TaskDeleter
@ -45,7 +43,6 @@ import org.tasks.location.GeofenceApi
import org.tasks.preferences.PermissionChecker import org.tasks.preferences.PermissionChecker
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.repeats.RecurrenceUtils.newRecur import org.tasks.repeats.RecurrenceUtils.newRecur
import org.tasks.time.DateTime
import org.tasks.time.DateTimeUtils.currentTimeMillis import org.tasks.time.DateTimeUtils.currentTimeMillis
import org.tasks.time.DateTimeUtils.startOfDay import org.tasks.time.DateTimeUtils.startOfDay
import timber.log.Timber import timber.log.Timber
@ -171,54 +168,27 @@ class TaskEditViewModel @Inject constructor(
var recurrence: String? = null var recurrence: String? = null
get() = field ?: task.recurrence get() = field ?: task.recurrence
var repeatUntil: Long? = null
get() = field ?: task.repeatUntil
var repeatAfterCompletion: Boolean? = null var repeatAfterCompletion: Boolean? = null
get() = field ?: task.repeatAfterCompletion() get() = field ?: task.repeatAfterCompletion()
set(value) {
field = value
if (value == true) {
if (!recurrence.isRepeatAfterCompletion()) {
recurrence += ";FROM=COMPLETION"
}
} else if (recurrence.isRepeatAfterCompletion()) {
recurrence = recurrence.withoutFrom()
}
}
var recur: Recur? var recur: Recur?
get() = if (recurrence.isNullOrBlank()) { get() = if (recurrence.isNullOrBlank()) {
null null
} else { } else {
val rrule = newRecur(recurrence!!) newRecur(recurrence!!)
repeatUntil?.takeIf { it > 0 }?.let {
rrule.until = DateTime(it).toDate()
}
rrule
} }
set(value) { set(value) {
if (value == null) { if (value == null) {
recurrence = "" recurrence = ""
repeatUntil = 0
return return
} }
val copy = try { val copy = try {
newRecur(value.toString()) newRecur(value.toString())
} catch (e: ParseException) { } catch (e: ParseException) {
recurrence = "" recurrence = ""
repeatUntil = 0
return return
} }
repeatUntil = DateTime.from(copy.until).millis recurrence = copy.toString()
if (repeatUntil ?: 0 > 0) {
copy.until = null
}
var result = copy.toString()
if (repeatAfterCompletion!! && result.isNotBlank()) {
result += ";FROM=COMPLETION"
}
recurrence = result
} }
var originalCalendar: String? = null var originalCalendar: String? = null
@ -295,7 +265,6 @@ class TaskEditViewModel @Inject constructor(
task.recurrence != recurrence task.recurrence != recurrence
} || } ||
task.repeatAfterCompletion() != repeatAfterCompletion || task.repeatAfterCompletion() != repeatAfterCompletion ||
task.repeatUntil != repeatUntil ||
originalCalendar != selectedCalendar.value || originalCalendar != selectedCalendar.value ||
if (task.calendarURI.isNullOrBlank()) { if (task.calendarURI.isNullOrBlank()) {
!eventUri.value.isNullOrBlank() !eventUri.value.isNullOrBlank()
@ -331,7 +300,11 @@ class TaskEditViewModel @Inject constructor(
task.notes = description task.notes = description
task.hideUntil = startDate.value task.hideUntil = startDate.value
task.recurrence = recurrence task.recurrence = recurrence
task.repeatUntil = repeatUntil!! task.repeatFrom = if (repeatAfterCompletion == true) {
Task.RepeatFrom.COMPLETION_DATE
} else {
Task.RepeatFrom.DUE_DATE
}
task.elapsedSeconds = elapsedSeconds.value task.elapsedSeconds = elapsedSeconds.value
task.estimatedSeconds = estimatedSeconds.value task.estimatedSeconds = estimatedSeconds.value
task.ringFlags = getRingFlags() task.ringFlags = getRingFlags()

Loading…
Cancel
Save