From 382cb717a49623789bafcc4c272af1126b30acd8 Mon Sep 17 00:00:00 2001 From: Guido Schmit Date: Thu, 2 Jul 2026 19:00:49 +0200 Subject: [PATCH] fix: match sync errors to calendars by id, not name suffix The server now includes calendar_id in per-calendar sync-error entries. Match CalendarFilterSheet's warning icon on (source, calendarId) like calendarKey() already does everywhere else, instead of a fragile name-suffix comparison that could false-positive across accounts whose calendar names happen to overlap. --- .../com/scarriffle/calendarr/data/CalendarRepository.kt | 6 ++++-- .../calendarr/ui/calendar/CalendarFilterSheet.kt | 7 ++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt b/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt index b0a8987..08cb250 100644 --- a/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt +++ b/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt @@ -35,8 +35,9 @@ data class LoginResult(val token: String, val username: String, val isAdmin: Boo data class TotpSetup(val secret: String, val qrUrl: String) -/** A single calendar's sync failure, surfaced alongside a (still-successful) events fetch. */ -data class SyncError(val source: String, val name: String, val message: String) +/** A single calendar's sync failure, surfaced alongside a (still-successful) events fetch. + * [calendarId] is null for account-wide failures (no single calendar is at fault). */ +data class SyncError(val source: String, val name: String, val calendarId: Int?, val message: String) /** Result of [CalendarRepository.fetchEvents]: the merged events plus any per-calendar sync failures. */ data class EventsResult(val events: List, val errors: List) @@ -322,6 +323,7 @@ class CalendarRepository @Inject constructor( add(SyncError( source = obj.optString("source"), name = obj.optString("name"), + calendarId = if (obj.has("calendar_id")) obj.optInt("calendar_id") else null, message = obj.optString("message"), )) } diff --git a/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarFilterSheet.kt b/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarFilterSheet.kt index 275a64d..7b9e749 100644 --- a/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarFilterSheet.kt +++ b/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarFilterSheet.kt @@ -84,10 +84,11 @@ fun CalendarFilterSheet( } rows.forEach { entry -> val visible = entry.key !in hiddenSet - // Best-effort match: server error `name` is "", - // so a suffix match on this calendar's own name is reliable enough. + // entry.key is "source:id" (see calendarKey()); match sync errors on the + // same (source, calendarId) pair the server already attaches to events. + val entryId = entry.key.substringAfter(":") val hasSyncError = !groupMode && state.syncErrors.any { err -> - err.source == entry.source && err.name.endsWith(entry.name) + err.source == entry.source && err.calendarId?.toString() == entryId } Row( Modifier.fillMaxWidth().padding(vertical = 8.dp),