diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt index 7f96a33e..8c2fab8a 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/CalendarDataSource.kt @@ -1024,9 +1024,8 @@ class AndroidCalendarDataSource @Inject constructor( updateEventRow(eventId, values.toContentValues(), "update event id=$eventId") } // Untouched reminder sets are left alone so unrelated edits can't - // disturb provider rows the form never knew about. The diff is on the - // form's semantic minutes; reconcile works in encoded provider minutes. - if (updated.reminders.toSet() != original.reminders.toSet()) { + // disturb provider rows the form never knew about. + if (remindersNeedRewrite(original, updated)) { reconcileReminders(eventId, encodedReminders(updated, allDayReminderTimeMinutes)) } // Same untouched-set guard for guests: only reconcile when the form's @@ -1785,3 +1784,11 @@ class AndroidCalendarDataSource @Inject constructor( const val MAX_SEARCH_TOKENS = 8 } } + +/** + * Whether an update must rewrite the reminder rows. The diff is on the form's + * semantic minutes, but the provider stores encoded minutes, so switching all-day + * on or off re-encodes an otherwise unchanged set. + */ +internal fun remindersNeedRewrite(original: EventForm, updated: EventForm): Boolean = + updated.reminders.toSet() != original.reminders.toSet() || updated.isAllDay != original.isAllDay diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt index 041642a2..2accd39d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditViewModel.kt @@ -538,9 +538,10 @@ class EventEditViewModel @Inject constructor( if (opened != session) return@launch val current = _form.value ?: return@launch if (_editTarget.value != null) return@launch - if (default.isEmpty() || default == current.firingReminders()) return@launch + val firing = current.firingReminders() + if (default.isEmpty() || default == firing) return@launch _importReminderPrompt.value = ImportReminderPrompt( - currentReminderCount = current.firingReminders().size, + currentReminderCount = firing.size, defaultReminders = default, ) } @@ -967,7 +968,7 @@ class EventEditViewModel @Inject constructor( /** * Reminders as they fire: an all-day event fires on whole days, so a sub-day lead * time kept from the timed form reads as its day. The form keeps the raw values, - * so switching all-day off again restores them. + * so switching all-day off again restores any the user did not remove. */ internal fun EventForm.firingReminders(): List = if (isAllDay) wholeDayLeadTimes(reminders) else reminders diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapperTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapperTest.kt index a783bafe..9cd03414 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapperTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapperTest.kt @@ -33,6 +33,14 @@ class EventWriteMapperTest { timezone = timezone, ) + @Test + fun `reminders are rewritten when they change or the all-day flag flips`() { + val timed = form().copy(reminders = listOf(10)) + assertThat(remindersNeedRewrite(timed, timed.copy(title = "Renamed"))).isFalse() + assertThat(remindersNeedRewrite(timed, timed.copy(reminders = listOf(10, 30)))).isTrue() + assertThat(remindersNeedRewrite(timed, timed.copy(isAllDay = true))).isTrue() + } + @Test fun `timed event resolves wall clock in the given zone`() { val times = form().toWriteTimes(berlin)