feat: implement MeshtasticNavDisplay and centralize Navigation 3 configuration

- Introduce `MeshtasticNavDisplay` in `core:ui` to encapsulate shared Navigation 3 configuration, including entry decorators, scene strategies, and transitions.
- Integrate `rememberViewModelStoreNavEntryDecorator` to provide entry-scoped `ViewModelStoreOwner` support, ensuring ViewModels are automatically cleared when their backstack entry is popped.
- Configure `DialogSceneStrategy` to enable navigation-driven dialogs that respect backstack lifecycle and predictive back gestures.
- Implement a standardized 350 ms crossfade transition for both forward and pop navigation across all platforms.
- Refactor `app` and `desktop` host modules to utilize `MeshtasticNavDisplay`, removing redundant manual configuration of `NavDisplay` and decorators.
- Update `core:ui` dependencies to include `lifecycle-viewmodel-navigation3` and move it away from platform-specific host modules.
- Update architectural documentation (`AGENTS.md`, `GEMINI.md`, and decision logs) to reflect the adoption of centralized navigation and ViewModel scoping patterns.
This commit is contained in:
James Rich 2026-03-26 13:54:27 -05:00
parent 37729c13d8
commit 829aecd888
10 changed files with 122 additions and 57 deletions

View file

@ -26,19 +26,12 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import androidx.lifecycle.viewmodel.navigation3.ViewModelStoreNavEntryDecorator
import androidx.lifecycle.viewmodel.navigation3.ViewModelStoreNavEntryDecoratorDefaults
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.SaveableStateHolderNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import co.touchlab.kermit.Logger
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.app.BuildConfig
@ -49,6 +42,7 @@ import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.app_too_old
import org.meshtastic.core.resources.must_update
import org.meshtastic.core.ui.component.MeshtasticAppShell
import org.meshtastic.core.ui.component.MeshtasticNavDisplay
import org.meshtastic.core.ui.viewmodel.UIViewModel
import org.meshtastic.feature.connections.navigation.connectionsGraph
import org.meshtastic.feature.firmware.navigation.firmwareGraph
@ -90,24 +84,9 @@ fun MainScreen(uIViewModel: UIViewModel = koinViewModel()) {
settingsGraph(backStack)
firmwareGraph(backStack)
}
val saveableStateHolder = rememberSaveableStateHolder()
val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current)
val removeOnPop = ViewModelStoreNavEntryDecoratorDefaults.removeViewModelStoreOnPop()
NavDisplay(
MeshtasticNavDisplay(
backStack = backStack,
entryProvider = provider,
entryDecorators =
listOf(
remember(saveableStateHolder) {
SaveableStateHolderNavEntryDecorator<NavKey>(saveableStateHolder)
},
remember(viewModelStoreOwner) {
ViewModelStoreNavEntryDecorator<NavKey>(
viewModelStore = viewModelStoreOwner.viewModelStore,
removeViewModelStoreOnPop = removeOnPop,
)
},
),
modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding(),
)
}