diff --git a/android/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt b/android/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt index f86ce54..7262385 100644 --- a/android/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt +++ b/android/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt @@ -357,7 +357,7 @@ class CalendarRepository @Inject constructor( val body = jsonBody("reminders_enabled" to enabled) when (source) { "caldav" -> api.updateCalDAVCalendar(calendarId, body).ensureSuccess() - "local" -> api.updateLocalCalendar(calendarId, body).ensureSuccess() + "local" -> api.setLocalCalendarReminders(calendarId, body).ensureSuccess() "ical" -> api.updateICalSubscription(calendarId, body).ensureSuccess() "google" -> api.updateGoogleCalendar(calendarId, body).ensureSuccess() "homeassistant" -> api.updateHACalendar(calendarId, body).ensureSuccess() diff --git a/android/app/src/main/java/com/scarriffle/calendarr/data/remote/CalendarrApi.kt b/android/app/src/main/java/com/scarriffle/calendarr/data/remote/CalendarrApi.kt index e841433..4f60a21 100644 --- a/android/app/src/main/java/com/scarriffle/calendarr/data/remote/CalendarrApi.kt +++ b/android/app/src/main/java/com/scarriffle/calendarr/data/remote/CalendarrApi.kt @@ -192,6 +192,11 @@ interface CalendarrApi { @PUT("api/local/calendars/{id}/color") suspend fun setLocalCalendarColor(@Path("id") id: Int, @Body body: RequestBody): Response + // Reminder-only update that works for owners AND share recipients + // (recipients get their own per-user mute; owners flip the calendar's flag). + @PUT("api/local/calendars/{id}/reminders") + suspend fun setLocalCalendarReminders(@Path("id") id: Int, @Body body: RequestBody): Response + @DELETE("api/local/calendars/{id}") suspend fun deleteLocalCalendar(@Path("id") id: Int): Response diff --git a/android/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt b/android/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt index f888931..bb5a6d8 100644 --- a/android/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt +++ b/android/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt @@ -490,6 +490,11 @@ class CalendarViewModel @Inject constructor( val caldav = runCatching { repository.getCalDAVAccounts() }.getOrDefault(emptyList()) val google = runCatching { repository.getGoogleAccounts() }.getOrDefault(emptyList()) val ha = runCatching { repository.getHomeAssistantAccounts() }.getOrDefault(emptyList()) + // Needed for the reminder flag, which exists on all five sources. A + // failed fetch yields an empty list, which simply reconciles nothing — + // never a wrongly cleared mute. + val local = runCatching { repository.getLocalCalendars() }.getOrDefault(emptyList()) + val ical = runCatching { repository.getICalSubscriptions() }.getOrDefault(emptyList()) val banished = settingsStore.banishedCalendarKeys.toMutableSet() fun apply(source: String, id: Int, serverHidden: Boolean) { @@ -500,9 +505,25 @@ class CalendarViewModel @Inject constructor( google.forEach { acc -> acc.calendars?.forEach { apply("google", it.id, it.sidebarHidden) } } ha.forEach { acc -> acc.calendars?.forEach { apply("homeassistant", it.id, it.sidebarHidden) } } - if (banished == settingsStore.banishedCalendarKeys) return false - settingsStore.banishedCalendarKeys = banished - _state.update { it.copy(banishedKeys = banished) } + // Reminders were only ever pushed app -> server, so a mute set on the + // web or on iOS never arrived here. + val muted = settingsStore.reminderDisabledCalendarKeys.toMutableSet() + fun applyReminders(source: String, id: Int, enabled: Boolean) { + val key = calendarKey(source, id.toString()) + if (enabled) muted.remove(key) else muted.add(key) + } + local.forEach { applyReminders("local", it.id, it.remindersEnabled) } + ical.forEach { applyReminders("ical", it.id, it.remindersEnabled) } + caldav.forEach { acc -> acc.calendars?.forEach { applyReminders("caldav", it.id, it.remindersEnabled) } } + google.forEach { acc -> acc.calendars?.forEach { applyReminders("google", it.id, it.remindersEnabled) } } + ha.forEach { acc -> acc.calendars?.forEach { applyReminders("homeassistant", it.id, it.remindersEnabled) } } + + val banishedChanged = banished != settingsStore.banishedCalendarKeys + val mutedChanged = muted != settingsStore.reminderDisabledCalendarKeys + if (!banishedChanged && !mutedChanged) return false + if (banishedChanged) settingsStore.banishedCalendarKeys = banished + if (mutedChanged) settingsStore.reminderDisabledCalendarKeys = muted + _state.update { it.copy(banishedKeys = banished, reminderDisabledKeys = muted) } return true } diff --git a/android/app/src/main/java/com/scarriffle/calendarr/ui/event/EventEditorSheet.kt b/android/app/src/main/java/com/scarriffle/calendarr/ui/event/EventEditorSheet.kt index 734f52b..039ef83 100644 --- a/android/app/src/main/java/com/scarriffle/calendarr/ui/event/EventEditorSheet.kt +++ b/android/app/src/main/java/com/scarriffle/calendarr/ui/event/EventEditorSheet.kt @@ -473,8 +473,10 @@ private fun ReminderRow( } } -/** Mirrors the server allowlist. "*/*" is deliberately absent — the picker - * should not offer files the upload would reject. */ +// Mirrors the server allowlist. The catch-all wildcard type is deliberately +// absent — the picker should not offer files the upload would reject. +// (Written as line comments: spelling that wildcard inside a block comment +// closes it early, which is what used to break this file.) private val ATTACHMENT_MIME_TYPES = arrayOf( "application/pdf", "image/png", "image/jpeg", "image/webp", "image/gif",