From d5b51f2c01dffe3c9f2adeae90f6eb29b001eb79 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sat, 14 Nov 2026 03:41:14 +0100 Subject: [PATCH] fix(#517): review round 3 - close picker in each request handler, keep hidden set alive across page swaps --- .../jeanlucmakiola/calendula/ui/CalendarHost.kt | 14 +++++++------- .../calendula/ui/calendars/CalendarsScreen.kt | 15 +++++++++------ .../calendula/ui/common/ViewHiddenCalendars.kt | 3 ++- .../calendula/ui/filter/ViewCalendarsPicker.kt | 2 +- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt index df0b04cc..b4b88786 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/CalendarHost.kt @@ -195,10 +195,15 @@ fun CalendarHost( var detailKey by rememberSaveable { mutableStateOf(null) } val onOpenDetail: (EventInstance) -> Unit = { event -> detailKey = event.occurrenceKey() } + // The top bar's view-calendars picker (#517). It is its own window over + // everything here, so each external request below closes it first. + var showViewCalendars by rememberSaveable { mutableStateOf(false) } + // A tapped reminder notification asks for a specific occurrence. val requestedDetailKey = requests.detailKey LaunchedEffect(requestedDetailKey) { if (requestedDetailKey != null) { + showViewCalendars = false detailKey = requestedDetailKey requests.detailKey = null } @@ -214,7 +219,6 @@ fun CalendarHost( // top bar. Sits below the detail/edit overlays so tapping a result reveals // the detail on top and backing out returns to the results. var showSearch by rememberSaveable { mutableStateOf(false) } - var showViewCalendars by rememberSaveable { mutableStateOf(false) } val filterViewModel: FilterViewModel = hiltViewModel() val filterState by filterViewModel.state.collectAsStateWithLifecycle() val onOpenSearch = { showSearch = true } @@ -322,6 +326,7 @@ fun CalendarHost( requests.importUri = null return@LaunchedEffect } + showViewCalendars = false importUri = requestedImportUri importForceMany = false importSession++ @@ -339,6 +344,7 @@ fun CalendarHost( requests.insert = null return@LaunchedEffect } + showViewCalendars = false if (importForm == null) { openImportForm(requestedInsert.form, requestedInsert.source, fromRequest = true) } else { @@ -348,12 +354,6 @@ fun CalendarHost( } } - // The view-calendars picker is its own window over everything here, so a - // reminder tap, an insert request or an .ics file opening under it closes it. - LaunchedEffect(detailKey != null || importUri != null || importForm != null) { - if (detailKey != null || importUri != null || importForm != null) showViewCalendars = false - } - // Close every overlay that can sit over the calendar, so an externally // requested destination (a widget/shortcut/QS-tile launch) is revealed on // top instead of underneath whatever the user had open. The create form, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt index 69338fc3..96c521f8 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/calendars/CalendarsScreen.kt @@ -46,6 +46,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -197,12 +198,14 @@ fun CalendarsScreen( if (calendar == null && calendars.isNotEmpty()) { LaunchedEffect(id) { hidingId = null } } else if (calendar != null && loadedHiddenViews != null) { - HiddenViewsPicker( - calendarName = calendar.displayName.orUnnamed(), - hidden = hiddenViews[id].orEmpty(), - onToggle = { view, hide -> viewModel.setHiddenIn(id, view, hide) }, - onDismiss = { hidingId = null }, - ) + key(id) { + HiddenViewsPicker( + calendarName = calendar.displayName.orUnnamed(), + hidden = hiddenViews[id].orEmpty(), + onToggle = { view, hide -> viewModel.setHiddenIn(id, view, hide) }, + onDismiss = { hidingId = null }, + ) + } } } } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ViewHiddenCalendars.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ViewHiddenCalendars.kt index 63a75362..3d7dc345 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ViewHiddenCalendars.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/ViewHiddenCalendars.kt @@ -30,7 +30,8 @@ fun Flow>>.calendarsHiddenIn( catch { emit(emptyMap()) } .map { it.calendarsHiddenIn(view) } .distinctUntilChanged() - .shareIn(scope, SharingStarted.WhileSubscribed(replayExpirationMillis = 0), replay = 1) + // The grace period outlasts a flatMapLatest swap, so paging doesn't re-read it. + .shareIn(scope, SharingStarted.WhileSubscribed(5_000, replayExpirationMillis = 0), replay = 1) /** * [CalendarRepository.instances] as one view shows them: also without the diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/ViewCalendarsPicker.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/ViewCalendarsPicker.kt index 55fcd226..761da16d 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/ViewCalendarsPicker.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/filter/ViewCalendarsPicker.kt @@ -41,7 +41,7 @@ fun ViewCalendarsPicker( is FilterUiState.Success -> { // Seeded once, then ahead of the store: the picker is the only // writer while open, so a quick second tap still flips. - var hidden by remember { mutableStateOf(state.hiddenViews.calendarsHiddenIn(view)) } + var hidden by remember(view) { mutableStateOf(state.hiddenViews.calendarsHiddenIn(view)) } GroupedHint(stringResource(R.string.view_calendars_hint), verticalPadding = 8.dp) state.groups.forEach { group -> GroupedSectionHeader(accountGroupTitle(group))