From 20a2313408fae1c5911cc0b9d03eea6485184046 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 1 Oct 2026 13:14:21 +0200 Subject: [PATCH] Save single-occurrence edits on coloured Google series (#338) The provider crashes on an exception insert that carries a colour key, so the key is now set with an update after the exception row exists. --- CHANGELOG.md | 2 ++ .../data/calendar/CalendarDataSource.kt | 7 +++++++ .../calendula/data/calendar/EventWriteMapper.kt | 6 +++++- .../data/calendar/EventWriteMapperTest.kt | 17 +++++++++++++++-- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9e14168..97e1cbcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Multi-day events stay above shorter events.** Month and Week bars, Split day lists, and Agenda now place multi-day events before one-day all-day events, then timed events; declined events remain last ([#291]). +- **Editing one occurrence of a coloured Google series saves again.** Saving with "Only this event" kept you on the edit screen without saving anything ([#338]). ## [2.21.0] — 2026-09-23 @@ -1789,3 +1790,4 @@ automatically, with zero telemetry and no internet permission. [#321]: https://codeberg.org/jlmakiola/calendula/issues/321 [#329]: https://codeberg.org/jlmakiola/calendula/issues/329 [#285]: https://codeberg.org/jlmakiola/calendula/issues/285 +[#338]: https://codeberg.org/jlmakiola/calendula/issues/338 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 fd3283dc..6f873afe 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 @@ -1261,6 +1261,13 @@ class AndroidCalendarDataSource @Inject constructor( values.toContentValues(), ) ?: throw WriteFailedException("modify occurrence event id=$eventId begin=$beginMillis") val exceptionId = ContentUris.parseId(uri) + form.colorKey?.let { key -> + resolver.update( + ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, exceptionId), + ContentValues().apply { put(CalendarContract.Events.EVENT_COLOR_KEY, key) }, + null, null, + ) + } // Whether the provider copied the parent's reminder rows is its // business — reconciling against the actual rows handles both ways. reconcileReminders(exceptionId, encodedReminders(form, allDayReminderTimeMinutes)) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapper.kt b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapper.kt index 138da17c..34c36b29 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapper.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/data/calendar/EventWriteMapper.kt @@ -247,6 +247,10 @@ internal fun buildEventUpdateValues( * DTSTART + DURATION itself, clearing the inherited RRULE in the process. This * matches how AOSP Calendar/Etar write exceptions; sending DTEND is what made * "only this event" fail on-device (Codeberg #16). + * + * A palette key is left out: the provider crashes on an exception insert that + * carries `EVENT_COLOR_KEY` (Codeberg #338), so the caller sets it with a plain + * update once the row exists. */ internal fun buildOccurrenceExceptionValues( form: EventForm, @@ -264,7 +268,7 @@ internal fun buildOccurrenceExceptionValues( put(CalendarContract.Events.ACCESS_LEVEL, form.accessLevel.toProviderValue()) put(CalendarContract.Events.EVENT_LOCATION, form.location.trim().ifEmpty { null }) put(CalendarContract.Events.DESCRIPTION, form.description.trim().ifEmpty { null }) - putAll(eventColorColumns(form.colorKey, form.color)) + if (form.colorKey == null) putAll(eventColorColumns(null, form.color)) } /** 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 c369cf61..db59c958 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 @@ -1075,16 +1075,29 @@ class EventWriteMapperTest { } @Test - fun `occurrence exception carries the palette key`() { + fun `occurrence exception leaves the palette key out`() { + // The provider crashes on an exception insert carrying a colour key + // (Codeberg #338); the data source sets it with an update afterwards. val values = buildOccurrenceExceptionValues( form = form().copy(colorKey = "2", color = 0xFFE67C00.toInt()), originalInstanceMillis = 0L, zone = berlin, ) - assertThat(values).containsEntry(CalendarContract.Events.EVENT_COLOR_KEY, "2") + assertThat(values).doesNotContainKey(CalendarContract.Events.EVENT_COLOR_KEY) assertThat(values).doesNotContainKey(CalendarContract.Events.EVENT_COLOR) } + @Test + fun `occurrence exception with a custom colour writes it directly`() { + val values = buildOccurrenceExceptionValues( + form = form().copy(color = 0xFFE67C00.toInt()), + originalInstanceMillis = 0L, + zone = berlin, + ) + assertThat(values).containsEntry(CalendarContract.Events.EVENT_COLOR, 0xFFE67C00.toInt()) + assertThat(values).containsEntry(CalendarContract.Events.EVENT_COLOR_KEY, null) + } + @Test fun `occurrence exception with no colour clears both columns`() { val values = buildOccurrenceExceptionValues(