From b47bd78ecd50afcb388b370196bf3ecedc0ca40d Mon Sep 17 00:00:00 2001 From: atsunatsu Date: Thu, 17 Sep 2026 12:57:36 +0800 Subject: [PATCH] feat(map): keep award filter selection across page switches The award chip (VUCC/DXCC/WAJA/...) now lives in an Activity-scoped MapFilterViewModel instead of a per-composition remember, so switching to another page and back keeps the last selection; it resets to VUCC only once per process (cold start). Removes the effect that forced VUCC on every grid-mode entry. --- .../java/com/rtbishop/look4sat/MainScreen.kt | 9 ++++++- .../feature/map/MapFilterViewModel.kt | 27 +++++++++++++++++++ .../look4sat/feature/map/MapScreen.kt | 25 ++++++++--------- 3 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapFilterViewModel.kt diff --git a/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt b/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt index e0a6a5ab..1970d06f 100644 --- a/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt +++ b/app/src/main/java/com/rtbishop/look4sat/MainScreen.kt @@ -81,6 +81,7 @@ import com.rtbishop.look4sat.core.presentation.Screen import com.rtbishop.look4sat.core.presentation.hasEnoughHeight import com.rtbishop.look4sat.core.presentation.hasEnoughWidth import com.rtbishop.look4sat.feature.map.MapDestination +import com.rtbishop.look4sat.feature.map.MapFilterViewModel import com.rtbishop.look4sat.feature.mutual.MutualScreen import com.rtbishop.look4sat.feature.mutual.MutualViewModel import com.rtbishop.look4sat.feature.passes.PassesDestination @@ -169,6 +170,12 @@ fun MainScreen( viewModelStoreOwner = context as ViewModelStoreOwner, factory = MutualViewModel.factory(container) ) + // Activity-scoped so the map's award filter survives page switches; + // resets to VUCC only on cold start (fresh process). + val mapFilterViewModel: MapFilterViewModel = viewModel( + viewModelStoreOwner = context as ViewModelStoreOwner, + factory = MapFilterViewModel.factory() + ) CompositionLocalProvider( LocalElevationThresholds provides ElevationThresholds( @@ -237,7 +244,7 @@ fun MainScreen( ) } entry { - MapDestination() + MapDestination(mapFilterViewModel = mapFilterViewModel) } entry { MutualScreen( diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapFilterViewModel.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapFilterViewModel.kt new file mode 100644 index 00000000..54410c8c --- /dev/null +++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapFilterViewModel.kt @@ -0,0 +1,27 @@ +package com.rtbishop.look4sat.feature.map + +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.rtbishop.look4sat.core.domain.model.AwardType + +/** + * Session-scoped holder for the map's award filter selection. + * + * Scoped to the Activity (not the navigation entry) so the selection survives + * switching to another page and back; it resets to VUCC only when the process + * starts fresh (cold start), which is what the user expects as the default. + */ +class MapFilterViewModel : ViewModel() { + + /** Last chosen award chip; null means "All". Initialized to VUCC once per process. */ + val selectedAward: MutableState = mutableStateOf(AwardType.VUCC) + + companion object { + fun factory() = viewModelFactory { + initializer { MapFilterViewModel() } + } + } +} diff --git a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt index f472be10..b9c9716e 100644 --- a/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt +++ b/feature/map/src/main/java/com/rtbishop/look4sat/feature/map/MapScreen.kt @@ -144,7 +144,9 @@ private val moonIconPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { } @Composable -fun MapDestination() { +fun MapDestination( + mapFilterViewModel: MapFilterViewModel +) { val context = LocalContext.current val container = (context.applicationContext as IContainerProvider).getMainContainer() val viewModel: MapViewModel = viewModel(factory = MapViewModel.factory(container)) @@ -165,11 +167,16 @@ fun MapDestination() { viewModel.onAction(MapAction.SetVisible(false)) } } - MapScreen(uiState, viewModel::onAction, mapView) + MapScreen(uiState, viewModel::onAction, mapView, mapFilterViewModel) } @Composable -private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView: MapView) { +private fun MapScreen( + uiState: MapState, + onAction: (MapAction) -> Unit, + mapView: MapView, + mapFilterViewModel: MapFilterViewModel +) { val rotateMod = Modifier.rotate(180f) val timeString = uiState.mapData?.aosTime ?: "00:00:00" val isTimeAos = uiState.mapData?.isTimeAos ?: true @@ -177,9 +184,9 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView: // Tapped worked grid -> centered QSO dialog. Local UI state: the map is the // only consumer and it resets when leaving the page. var selectedGrid by remember { mutableStateOf(null) } - // Selected award filter. Entering grid mode defaults to VUCC (the plain - // worked-grid view); the reset effect below re-asserts that on every entry. - var selectedAward by remember { mutableStateOf(AwardType.VUCC) } + // Selected award filter. Lives in an Activity-scoped ViewModel so it + // survives page switches; defaults to VUCC only once per process (cold start). + var selectedAward by mapFilterViewModel.selectedAward // Six-award progress derived from the confirmed QSO store; recomputed when // the store changes (LoTW/Wavelog sync). // Per operated-grid VUCC breakdown: myGrid -> set of worked grids worked from it. @@ -240,12 +247,6 @@ private fun MapScreen(uiState: MapState, onAction: (MapAction) -> Unit, mapView: (mapView.overlays.getOrNull(OVERLAY_GRID) as? MaidenheadGridOverlay)?.selectedGrid = selectedGrid mapView.invalidate() } - // Re-assert the VUCC default each time grid mode is entered; while already - // in grid mode the user's chip choice is preserved. - LaunchedEffect(uiState.isGridMode) { - if (uiState.isGridMode) selectedAward = AwardType.VUCC - } - LaunchedEffect(uiState.track) { // In grid mode the map is centered on the local grid square; following // the satellite subpoint here would override that centering on every