refactor: streamline main screen navigation and ViewModel injection

- Update `MainScreen` (Android) and `DesktopMainScreen` to manage their own `NavBackStack` initialization internally.
- Refactor `MainScreen` to obtain `UIViewModel` via Koin injection instead of receiving it as a parameter from `MainActivity`.
- Remove default bottom padding from `MeshtasticAppShell` and associated screen-level modifiers to allow for more flexible layout orchestration.
- Simplify `DesktopMainScreen` by moving backstack management inside the composable and cleaning up the navigation provider logic.
- Remove redundant lint suppressions in `Main.kt` following the simplification of the main screen composable structure.
- Clean up imports and normalize the usage of `MeshtasticNavDisplay` and `MeshtasticNavigationSuite` across platforms.
This commit is contained in:
James Rich
2026-03-26 15:53:15 -05:00
parent 63faf20fc5
commit 4e027ca28e
6 changed files with 39 additions and 49 deletions

View File

@@ -139,7 +139,7 @@ class MainActivity : ComponentActivity() {
ReportDrawnWhen { true }
if (appIntroCompleted) {
MainScreen(uIViewModel = model)
MainScreen()
} else {
val introViewModel = koinViewModel<IntroViewModel>()
AppIntroductionScreen(onDone = { model.onAppIntroCompleted() }, viewModel = introViewModel)

View File

@@ -19,15 +19,12 @@
package org.meshtastic.app.ui
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.recalculateWindowInsets
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
@@ -43,6 +40,7 @@ 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.component.MeshtasticNavigationSuite
import org.meshtastic.core.ui.viewmodel.UIViewModel
import org.meshtastic.feature.connections.navigation.connectionsGraph
import org.meshtastic.feature.firmware.navigation.firmwareGraph
@@ -52,38 +50,36 @@ import org.meshtastic.feature.node.navigation.nodesGraph
import org.meshtastic.feature.settings.navigation.settingsGraph
import org.meshtastic.feature.settings.radio.channel.channelsGraph
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun MainScreen(uIViewModel: UIViewModel = koinViewModel()) {
fun MainScreen() {
val viewModel: UIViewModel = koinViewModel()
val backStack = rememberNavBackStack(MeshtasticNavSavedStateConfig, NodesRoutes.NodesGraph as NavKey)
AndroidAppVersionCheck(uIViewModel)
AndroidAppVersionCheck(viewModel)
MeshtasticAppShell(
backStack = backStack,
uiViewModel = uIViewModel,
hostModifier = Modifier.safeDrawingPadding().padding(bottom = 16.dp),
uiViewModel = viewModel,
hostModifier = Modifier,
) {
org.meshtastic.core.ui.component.MeshtasticNavigationSuite(
MeshtasticNavigationSuite(
backStack = backStack,
uiViewModel = uIViewModel,
uiViewModel = viewModel,
modifier = Modifier.fillMaxSize(),
) {
val provider =
entryProvider<NavKey> {
contactsGraph(backStack, uIViewModel.scrollToTopEventFlow)
nodesGraph(
backStack = backStack,
scrollToTopEvents = uIViewModel.scrollToTopEventFlow,
onHandleDeepLink = uIViewModel::handleDeepLink,
)
mapGraph(backStack)
channelsGraph(backStack)
connectionsGraph(backStack)
settingsGraph(backStack)
firmwareGraph(backStack)
}
val provider = entryProvider<NavKey> {
contactsGraph(backStack, viewModel.scrollToTopEventFlow)
nodesGraph(
backStack = backStack,
scrollToTopEvents = viewModel.scrollToTopEventFlow,
onHandleDeepLink = viewModel::handleDeepLink,
)
mapGraph(backStack)
channelsGraph(backStack)
connectionsGraph(backStack)
settingsGraph(backStack)
firmwareGraph(backStack)
}
MeshtasticNavDisplay(
backStack = backStack,
entryProvider = provider,
@@ -99,7 +95,6 @@ private fun AndroidAppVersionCheck(viewModel: UIViewModel) {
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
val myNodeInfo by viewModel.myNodeInfo.collectAsStateWithLifecycle()
// Check if the device is running an old app version
LaunchedEffect(connectionState, myNodeInfo) {
if (connectionState == ConnectionState.Connected) {
myNodeInfo?.let { info ->
@@ -120,4 +115,4 @@ private fun AndroidAppVersionCheck(viewModel: UIViewModel) {
}
}
}
}
}

View File

@@ -16,11 +16,9 @@
*/
package org.meshtastic.core.ui.component
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import org.meshtastic.core.navigation.NodeDetailRoutes
@@ -36,7 +34,7 @@ import org.meshtastic.core.ui.viewmodel.UIViewModel
fun MeshtasticAppShell(
backStack: NavBackStack<NavKey>,
uiViewModel: UIViewModel,
hostModifier: Modifier = Modifier.padding(bottom = 16.dp),
hostModifier: Modifier = Modifier,
content: @Composable () -> Unit,
) {
LaunchedEffect(uiViewModel) {

View File

@@ -321,7 +321,7 @@ fun main(args: Array<String>) = application(exitProcessOnExit = false) {
// re-reads Locale.current and all stringResource() calls update. Unlike key(), this
// preserves remembered state (including the navigation backstack).
CompositionLocalProvider(LocalAppLocale provides localePref) {
AppTheme(darkTheme = isDarkTheme) { DesktopMainScreen(backStack) }
AppTheme(darkTheme = isDarkTheme) { DesktopMainScreen(uiViewModel) }
}
}
}

View File

@@ -18,41 +18,37 @@ package org.meshtastic.desktop.ui
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import org.koin.compose.viewmodel.koinViewModel
import org.meshtastic.core.navigation.MeshtasticNavSavedStateConfig
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.core.ui.component.MeshtasticAppShell
import org.meshtastic.core.ui.component.MeshtasticNavDisplay
import org.meshtastic.core.ui.component.MeshtasticNavigationSuite
import org.meshtastic.core.ui.viewmodel.UIViewModel
import org.meshtastic.desktop.navigation.desktopNavGraph
/**
* Desktop main screen — Navigation 3 shell with adaptive navigation and shared [MeshtasticNavDisplay].
*
* Uses the same shared routes from `core:navigation` and the same `MeshtasticNavDisplay` + `entryProvider` pattern as
* the Android app, proving the shared backstack architecture works across targets.
* Desktop main screen — uses shared navigation components.
*/
@Composable
@Suppress("LongMethod")
fun DesktopMainScreen(backStack: NavBackStack<NavKey>, uiViewModel: UIViewModel = koinViewModel()) {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
fun DesktopMainScreen(uiViewModel: UIViewModel) {
val backStack = androidx.navigation3.runtime.rememberNavBackStack(MeshtasticNavSavedStateConfig, NodesRoutes.NodesGraph as NavKey)
Surface(modifier = Modifier.fillMaxSize()) {
MeshtasticAppShell(
backStack = backStack,
uiViewModel = uiViewModel,
hostModifier = Modifier.padding(bottom = 24.dp),
) {
org.meshtastic.core.ui.component.MeshtasticNavigationSuite(
backStack = backStack,
uiViewModel = uiViewModel,
) {
val provider = entryProvider<NavKey> { desktopNavGraph(backStack, uiViewModel) }
MeshtasticNavigationSuite(backStack = backStack, uiViewModel = uiViewModel, modifier = Modifier.fillMaxSize()) {
val provider = entryProvider<NavKey> {
desktopNavGraph(backStack, uiViewModel)
}
MeshtasticNavDisplay(backStack = backStack, entryProvider = provider, modifier = Modifier.fillMaxSize())
}
}

View File

@@ -24,6 +24,7 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.permissions.rememberPermissionState
import org.meshtastic.core.ui.component.MeshtasticNavDisplay
/**
* Main application introduction screen. This Composable hosts the navigation flow and hoists the permission states.
@@ -56,7 +57,7 @@ fun AppIntroductionScreen(onDone: () -> Unit, viewModel: IntroViewModel) {
val backStack = rememberNavBackStack(Welcome)
org.meshtastic.core.ui.component.MeshtasticNavDisplay(
MeshtasticNavDisplay(
backStack = backStack,
entryProvider =
introNavGraph(