diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt index e3044aa8c..fac65a01e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/EventActivity.kt @@ -1155,7 +1155,7 @@ class EventActivity : SimpleActivity() { DeleteEventDialog(this, arrayListOf(mEvent.id!!), mEvent.repeatInterval > 0) { ensureBackgroundThread { when (it) { - DELETE_SELECTED_OCCURRENCE -> eventsHelper.addEventRepetitionException(mEvent.id!!, mEventOccurrenceTS, true) + DELETE_SELECTED_OCCURRENCE -> eventsHelper.deleteRepeatingEventOccurrence(mEvent.id!!, mEventOccurrenceTS, true) DELETE_FUTURE_OCCURRENCES -> eventsHelper.addEventRepeatLimit(mEvent.id!!, mEventOccurrenceTS) DELETE_ALL_OCCURRENCES -> eventsHelper.deleteEvent(mEvent.id!!, true) } @@ -1361,7 +1361,6 @@ class EventActivity : SimpleActivity() { when (it) { EDIT_SELECTED_OCCURRENCE -> { ensureBackgroundThread { - eventsHelper.addEventRepetitionException(mEvent.id!!, mEventOccurrenceTS, true) mEvent.apply { parentId = id!!.toLong() id = null @@ -1370,7 +1369,7 @@ class EventActivity : SimpleActivity() { repeatLimit = 0 } - eventsHelper.insertEvent(mEvent, true, true) { + eventsHelper.insertEvent(mEvent, addToCalDAV = true, showToasts = true) { finish() } } diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt index 8479b7bf6..ead2286d4 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/activities/TaskActivity.kt @@ -474,7 +474,6 @@ class TaskActivity : SimpleActivity() { when (it) { EDIT_SELECTED_OCCURRENCE -> { ensureBackgroundThread { - eventsHelper.addEventRepetitionException(mTask.id!!, mTaskOccurrenceTS, addToCalDAV = false) mTask.apply { parentId = id!!.toLong() id = null @@ -533,9 +532,9 @@ class TaskActivity : SimpleActivity() { DeleteEventDialog(this, arrayListOf(mTask.id!!), mTask.repeatInterval > 0, isTask = true) { ensureBackgroundThread { when (it) { - DELETE_SELECTED_OCCURRENCE -> eventsHelper.addEventRepetitionException(mTask.id!!, mTaskOccurrenceTS, true) + DELETE_SELECTED_OCCURRENCE -> eventsHelper.deleteRepeatingEventOccurrence(mTask.id!!, mTaskOccurrenceTS, false) DELETE_FUTURE_OCCURRENCES -> eventsHelper.addEventRepeatLimit(mTask.id!!, mTaskOccurrenceTS) - DELETE_ALL_OCCURRENCES -> eventsHelper.deleteEvent(mTask.id!!, true) + DELETE_ALL_OCCURRENCES -> eventsHelper.deleteEvent(mTask.id!!, false) } runOnUiThread { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt index e3487a8b4..a34202da1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/extensions/Context.kt @@ -697,7 +697,7 @@ fun Context.handleEventDeleting(eventIds: List, timestamps: List, ac when (action) { DELETE_SELECTED_OCCURRENCE -> { eventIds.forEachIndexed { index, value -> - eventsHelper.addEventRepetitionException(value, timestamps[index], true) + eventsHelper.deleteRepeatingEventOccurrence(value, timestamps[index], true) } } DELETE_FUTURE_OCCURRENCES -> { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt index 03151d2ab..8d4cc206c 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/CalDAVHelper.kt @@ -185,6 +185,7 @@ class CalDAVHelper(val context: Context) { Events.CALENDAR_TIME_ZONE, Events.DELETED, Events.AVAILABILITY, + Events.STATUS, Events.EVENT_COLOR ) @@ -214,6 +215,7 @@ class CalDAVHelper(val context: Context) { val reminders = getCalDAVEventReminders(id) val attendees = Gson().toJson(getCalDAVEventAttendees(id)) val availability = cursor.getIntValue(Events.AVAILABILITY) + val status = cursor.getIntValue(Events.STATUS) val color = cursor.getIntValueOrNull(Events.EVENT_COLOR) val displayColor = if (color != null) { getDisplayColorFromColor(color) @@ -255,14 +257,28 @@ class CalDAVHelper(val context: Context) { val parentImportId = "$source-$originalId" val parentEvent = context.eventsDB.getEventWithImportId(parentImportId) val originalDayCode = Formatter.getDayCodeFromTS(originalInstanceTime / 1000L) - if (parentEvent != null && !parentEvent.repetitionExceptions.contains(originalDayCode)) { - val storedEventId = context.eventsDB.getEventIdWithImportId(importId) - if (storedEventId != null) { - event.id = storedEventId + if (parentEvent != null) { + // add this event to the parent event's list of exceptions + if (!parentEvent.repetitionExceptions.contains(originalDayCode)) { + parentEvent.addRepetitionException(originalDayCode) + eventsHelper.insertEvent(parentEvent, addToCalDAV = false, showToasts = false) + } + + // store the event in the local db only if it is an occurrence that has been modified and not deleted + if (status != Events.STATUS_CANCELED && title.isNotEmpty()) { + val storedEventId = context.eventsDB.getEventIdWithImportId(importId) + if (storedEventId != null) { + event.id = storedEventId + } + event.parentId = parentEvent.id!! + eventsHelper.insertEvent(event, addToCalDAV = false, showToasts = false) + } else { + // delete the deleted exception event from local db + val storedEventId = context.eventsDB.getEventIdWithImportId(importId) + if (storedEventId != null) { + eventsHelper.deleteEvent(storedEventId, true) + } } - event.parentId = parentEvent.id!! - parentEvent.addRepetitionException(originalDayCode) - eventsHelper.insertEvent(parentEvent, addToCalDAV = false, showToasts = false) return@queryCursorInlined } @@ -432,6 +448,25 @@ class CalDAVHelper(val context: Context) { put(Events.ALL_DAY, 0) } + val parentEventId = event.parentId + if (parentEventId != 0L) { + val parentEvent = context.eventsDB.getEventWithId(parentEventId) ?: return@apply + val isParentAllDay = parentEvent.getIsAllDay() + // original instance time must be in UTC when the parent is an all-day event + val originalInstanceTS = if (isParentAllDay && !event.getIsAllDay()) { + Formatter.getShiftedUtcTS(event.startTS) + } else { + event.startTS + } + put(Events.ORIGINAL_ID, parentEvent.getCalDAVEventId()) + put(Events.ORIGINAL_INSTANCE_TIME, originalInstanceTS * 1000L) + if (isParentAllDay) { + put(Events.ORIGINAL_ALL_DAY, 1) + } else { + put(Events.ORIGINAL_ALL_DAY, 0) + } + } + put(Events.DTSTART, event.startTS * 1000L) put(Events.EVENT_TIMEZONE, event.getTimeZoneString()) if (event.repeatInterval > 0) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt index fc9327c71..38f593d1e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/helpers/EventsHelper.kt @@ -116,6 +116,7 @@ class EventsHelper(val context: Context) { return } + maybeUpdateParentExceptions(event) event.id = eventsDB.insertOrUpdate(event) context.updateWidgets() @@ -129,12 +130,24 @@ class EventsHelper(val context: Context) { } fun insertTask(task: Event, showToasts: Boolean, callback: () -> Unit) { + maybeUpdateParentExceptions(task) task.id = eventsDB.insertOrUpdate(task) context.updateWidgets() context.scheduleNextEventReminder(task, showToasts) callback() } + private fun maybeUpdateParentExceptions(event: Event) { + // if the event is an exception from another event, update the parent event's exceptions list + val parentEventId = event.parentId + if (parentEventId != 0L) { + val parentEvent = eventsDB.getEventOrTaskWithId(parentEventId) ?: return + val startDayCode = Formatter.getDayCodeFromTS(event.startTS) + parentEvent.addRepetitionException(startDayCode) + eventsDB.updateEventRepetitionExceptions(parentEvent.repetitionExceptions.toString(), parentEventId) + } + } + fun insertEvents(events: ArrayList, addToCalDAV: Boolean) { try { for (event in events) { @@ -232,14 +245,12 @@ class EventsHelper(val context: Context) { } } - fun addEventRepetitionException(parentEventId: Long, occurrenceTS: Long, addToCalDAV: Boolean) { + fun deleteRepeatingEventOccurrence(parentEventId: Long, occurrenceTS: Long, addToCalDAV: Boolean) { ensureBackgroundThread { val parentEvent = eventsDB.getEventOrTaskWithId(parentEventId) ?: return@ensureBackgroundThread - var repetitionExceptions = parentEvent.repetitionExceptions - repetitionExceptions.add(Formatter.getDayCodeFromTS(occurrenceTS)) - repetitionExceptions = repetitionExceptions.distinct().toMutableList() as ArrayList - - eventsDB.updateEventRepetitionExceptions(repetitionExceptions.toString(), parentEventId) + val occurrenceDayCode = Formatter.getDayCodeFromTS(occurrenceTS) + parentEvent.addRepetitionException(occurrenceDayCode) + eventsDB.updateEventRepetitionExceptions(parentEvent.repetitionExceptions.toString(), parentEventId) context.scheduleNextEventReminder(parentEvent, false) if (addToCalDAV && config.caldavSync) { diff --git a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt index 17575838e..956e4257e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt +++ b/app/src/main/kotlin/com/simplemobiletools/calendar/pro/interfaces/EventsDao.kt @@ -103,7 +103,7 @@ interface EventsDao { @Query("SELECT id FROM events WHERE event_type IN (:eventTypeIds) AND type = $TYPE_EVENT") fun getEventIdsByEventType(eventTypeIds: List): List - @Query("SELECT id FROM events WHERE parent_id IN (:parentIds) AND type = $TYPE_EVENT") + @Query("SELECT id FROM events WHERE parent_id IN (:parentIds)") fun getEventIdsWithParentIds(parentIds: List): List @Query("SELECT id FROM events WHERE source = :source AND import_id != \"\" AND type = $TYPE_EVENT")