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
|
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 viewMenuOpen by remember { mutableStateOf(false) }
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class CalendarViewModel @Inject constructor(
|
|||||||
init {
|
init {
|
||||||
loadWritableCalendars()
|
loadWritableCalendars()
|
||||||
loadGroups()
|
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
|
* mark ready. Entering the app fully-loaded avoids the first-open jank of
|
||||||
* loading a big batch while the user is already scrolling.
|
* 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 months = settingsStore.cacheMonths.toLong()
|
||||||
val today = LocalDate.now().withDayOfMonth(1)
|
val today = LocalDate.now().withDayOfMonth(1)
|
||||||
val start = instant(today.minusMonths(months))
|
val start = instant(today.minusMonths(months))
|
||||||
val end = instant(today.plusMonths(months + 1))
|
val end = instant(today.plusMonths(months + 1))
|
||||||
viewModelScope.launch {
|
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)
|
loadRange(start, end, background = false)
|
||||||
markReady()
|
markReady()
|
||||||
}
|
}
|
||||||
@@ -365,7 +368,50 @@ class CalendarViewModel @Inject constructor(
|
|||||||
fun syncWithServer() {
|
fun syncWithServer() {
|
||||||
invalidateCache()
|
invalidateCache()
|
||||||
loadGroups()
|
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) }
|
fun clearError() = _state.update { it.copy(error = null) }
|
||||||
|
|||||||
Reference in New Issue
Block a user