Bring a muted calendar down to Android, and let it build again
Reminders only ever travelled app -> server here: reconcileCalendarVisibility read sidebar_hidden and nothing else, and reminderDisabledCalendarKeys came purely from SharedPreferences, so a calendar muted on the web or on iOS stayed loud on Android. It now reconciles reminders_enabled for all five sources, which also means fetching local calendars and iCal subscriptions — a failed fetch yields an empty list and reconciles nothing, so it can never clear a mute by accident. Local calendars go through the new reminders endpoint, so a share recipient's mute is stored per user instead of being refused. EventEditorSheet could not compile: a KDoc comment spelled out the catch-all MIME wildcard, and the slash inside it closed the comment two lines early, so the remainder parsed as code. Rewritten as line comments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
96cbbb0dc5
commit
2bdf134d9c
@@ -357,7 +357,7 @@ class CalendarRepository @Inject constructor(
|
|||||||
val body = jsonBody("reminders_enabled" to enabled)
|
val body = jsonBody("reminders_enabled" to enabled)
|
||||||
when (source) {
|
when (source) {
|
||||||
"caldav" -> api.updateCalDAVCalendar(calendarId, body).ensureSuccess()
|
"caldav" -> api.updateCalDAVCalendar(calendarId, body).ensureSuccess()
|
||||||
"local" -> api.updateLocalCalendar(calendarId, body).ensureSuccess()
|
"local" -> api.setLocalCalendarReminders(calendarId, body).ensureSuccess()
|
||||||
"ical" -> api.updateICalSubscription(calendarId, body).ensureSuccess()
|
"ical" -> api.updateICalSubscription(calendarId, body).ensureSuccess()
|
||||||
"google" -> api.updateGoogleCalendar(calendarId, body).ensureSuccess()
|
"google" -> api.updateGoogleCalendar(calendarId, body).ensureSuccess()
|
||||||
"homeassistant" -> api.updateHACalendar(calendarId, body).ensureSuccess()
|
"homeassistant" -> api.updateHACalendar(calendarId, body).ensureSuccess()
|
||||||
|
|||||||
@@ -192,6 +192,11 @@ interface CalendarrApi {
|
|||||||
@PUT("api/local/calendars/{id}/color")
|
@PUT("api/local/calendars/{id}/color")
|
||||||
suspend fun setLocalCalendarColor(@Path("id") id: Int, @Body body: RequestBody): Response<ResponseBody>
|
suspend fun setLocalCalendarColor(@Path("id") id: Int, @Body body: RequestBody): Response<ResponseBody>
|
||||||
|
|
||||||
|
// 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<ResponseBody>
|
||||||
|
|
||||||
@DELETE("api/local/calendars/{id}")
|
@DELETE("api/local/calendars/{id}")
|
||||||
suspend fun deleteLocalCalendar(@Path("id") id: Int): Response<ResponseBody>
|
suspend fun deleteLocalCalendar(@Path("id") id: Int): Response<ResponseBody>
|
||||||
|
|
||||||
|
|||||||
+24
-3
@@ -490,6 +490,11 @@ class CalendarViewModel @Inject constructor(
|
|||||||
val caldav = runCatching { repository.getCalDAVAccounts() }.getOrDefault(emptyList())
|
val caldav = runCatching { repository.getCalDAVAccounts() }.getOrDefault(emptyList())
|
||||||
val google = runCatching { repository.getGoogleAccounts() }.getOrDefault(emptyList())
|
val google = runCatching { repository.getGoogleAccounts() }.getOrDefault(emptyList())
|
||||||
val ha = runCatching { repository.getHomeAssistantAccounts() }.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()
|
val banished = settingsStore.banishedCalendarKeys.toMutableSet()
|
||||||
fun apply(source: String, id: Int, serverHidden: Boolean) {
|
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) } }
|
google.forEach { acc -> acc.calendars?.forEach { apply("google", it.id, it.sidebarHidden) } }
|
||||||
ha.forEach { acc -> acc.calendars?.forEach { apply("homeassistant", it.id, it.sidebarHidden) } }
|
ha.forEach { acc -> acc.calendars?.forEach { apply("homeassistant", it.id, it.sidebarHidden) } }
|
||||||
|
|
||||||
if (banished == settingsStore.banishedCalendarKeys) return false
|
// Reminders were only ever pushed app -> server, so a mute set on the
|
||||||
settingsStore.banishedCalendarKeys = banished
|
// web or on iOS never arrived here.
|
||||||
_state.update { it.copy(banishedKeys = banished) }
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -473,8 +473,10 @@ private fun ReminderRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Mirrors the server allowlist. "*/*" is deliberately absent — the picker
|
// Mirrors the server allowlist. The catch-all wildcard type is deliberately
|
||||||
* should not offer files the upload would reject. */
|
// 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(
|
private val ATTACHMENT_MIME_TYPES = arrayOf(
|
||||||
"application/pdf",
|
"application/pdf",
|
||||||
"image/png", "image/jpeg", "image/webp", "image/gif",
|
"image/png", "image/jpeg", "image/webp", "image/gif",
|
||||||
|
|||||||
Reference in New Issue
Block a user