fix(visibility): auto-reconcile server sidebar_hidden on launch/resume/sync
Hiding or showing a calendar on the web sets enabled=false + sidebar_hidden server-side, so the server stops returning that calendar's events entirely. The app synced the filter toggle TO the server (setCalendarHiddenSynced) but never pulled the server's sidebar_hidden flags back, so a calendar hidden or re-enabled on the web (or another device) was never reflected until a manual sync or relaunch — the "hide it on the web and the app won't bring it back" bug. Add reconcileCalendarVisibility(): pulls the account lists and updates the local hidden set from the server's sidebar_hidden flags for external calendars (local/ical keys untouched). It runs before the initial load, on manual sync, and on a new ON_RESUME lifecycle hook — forcing a refetch when the set changed so a re-enabled calendar's events reappear on their own. Mirrors the iOS fix. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,17 @@ fun CalendarScreen(
|
||||
context, state.events, state.reminderDisabledKeys, vm.defaultReminderMinutes
|
||||
)
|
||||
}
|
||||
// Re-check server-side calendar visibility whenever the app returns to the
|
||||
// foreground (e.g. after hiding/showing a calendar on the web or another
|
||||
// device); reloads only if something changed.
|
||||
val lifecycleOwner = androidx.compose.ui.platform.LocalLifecycleOwner.current
|
||||
androidx.compose.runtime.DisposableEffect(lifecycleOwner) {
|
||||
val observer = androidx.lifecycle.LifecycleEventObserver { _, event ->
|
||||
if (event == androidx.lifecycle.Lifecycle.Event.ON_RESUME) vm.onAppResumed()
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||
}
|
||||
|
||||
var viewMenuOpen by remember { mutableStateOf(false) }
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -89,7 +89,7 @@ class CalendarViewModel @Inject constructor(
|
||||
init {
|
||||
loadWritableCalendars()
|
||||
loadGroups()
|
||||
initialLoad()
|
||||
initialLoad(reconcile = true)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,12 +97,15 @@ class CalendarViewModel @Inject constructor(
|
||||
* mark ready. Entering the app fully-loaded avoids the first-open jank of
|
||||
* loading a big batch while the user is already scrolling.
|
||||
*/
|
||||
private fun initialLoad() {
|
||||
private fun initialLoad(reconcile: Boolean = false) {
|
||||
val months = settingsStore.cacheMonths.toLong()
|
||||
val today = LocalDate.now().withDayOfMonth(1)
|
||||
val start = instant(today.minusMonths(months))
|
||||
val end = instant(today.plusMonths(months + 1))
|
||||
viewModelScope.launch {
|
||||
// Honour a calendar hidden/shown on the web BEFORE the first load so
|
||||
// the visibility filter is correct before events are rendered.
|
||||
if (reconcile) reconcileCalendarVisibility()
|
||||
loadRange(start, end, background = false)
|
||||
markReady()
|
||||
}
|
||||
@@ -365,7 +368,50 @@ class CalendarViewModel @Inject constructor(
|
||||
fun syncWithServer() {
|
||||
invalidateCache()
|
||||
loadGroups()
|
||||
initialLoad()
|
||||
initialLoad(reconcile = true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the app returns to the foreground. Re-checks the server's
|
||||
* per-calendar visibility (a calendar may have been hidden/shown on the web
|
||||
* or another device meanwhile) and reloads only if something changed.
|
||||
*/
|
||||
fun onAppResumed() {
|
||||
viewModelScope.launch {
|
||||
if (reconcileCalendarVisibility()) {
|
||||
invalidateCache()
|
||||
loadGroups()
|
||||
initialLoad(reconcile = false) // just reconciled above
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile the local hidden set with the server's per-calendar
|
||||
* `sidebar_hidden` flags for external calendars (CalDAV / Google / HA).
|
||||
* Returns `true` if the hidden set changed, so the caller can force a
|
||||
* refetch — a calendar re-enabled on the web has NO events in the cache
|
||||
* (the server excludes a hidden calendar's events entirely). Local / iCal
|
||||
* hidden keys have no server flag and are left untouched.
|
||||
*/
|
||||
private suspend fun reconcileCalendarVisibility(): Boolean {
|
||||
val caldav = runCatching { repository.getCalDAVAccounts() }.getOrDefault(emptyList())
|
||||
val google = runCatching { repository.getGoogleAccounts() }.getOrDefault(emptyList())
|
||||
val ha = runCatching { repository.getHomeAssistantAccounts() }.getOrDefault(emptyList())
|
||||
|
||||
val hidden = settingsStore.hiddenCalendarKeys.toMutableSet()
|
||||
fun apply(source: String, id: Int, serverHidden: Boolean) {
|
||||
val key = calendarKey(source, id.toString())
|
||||
if (serverHidden) hidden.add(key) else hidden.remove(key)
|
||||
}
|
||||
caldav.forEach { acc -> acc.calendars?.forEach { apply("caldav", 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) } }
|
||||
|
||||
if (hidden == settingsStore.hiddenCalendarKeys) return false
|
||||
settingsStore.hiddenCalendarKeys = hidden
|
||||
_state.update { it.copy(hiddenKeys = hidden) }
|
||||
return true
|
||||
}
|
||||
|
||||
fun clearError() = _state.update { it.copy(error = null) }
|
||||
|
||||
Reference in New Issue
Block a user