diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml
index fae4c81d4..360c359e3 100644
--- a/app/detekt-baseline.xml
+++ b/app/detekt-baseline.xml
@@ -11,6 +11,7 @@
ComposableParamOrder:EmptyStateContent.kt$EmptyStateContent
ComposableParamOrder:Share.kt$ShareScreen
CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)
+ CyclomaticComplexMethod:SettingsNavigation.kt$@Suppress("LongMethod") fun NavGraphBuilder.settingsGraph(navController: NavHostController)
EmptyClassBlock:DebugLogFile.kt$BinaryLogFile${ }
EmptyFunctionBlock:NopInterface.kt$NopInterface${ }
EmptyFunctionBlock:NsdManager.kt$<no name provided>${ }
diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt
index 3e2866346..ccf513922 100644
--- a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsNavigation.kt
@@ -27,7 +27,7 @@ import androidx.navigation.navigation
import com.geeksville.mesh.ui.sharing.ChannelScreen
import org.meshtastic.core.navigation.ChannelsRoutes
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
-import org.meshtastic.feature.settings.navigation.ConfigRoute
+import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.feature.settings.radio.channel.ChannelConfigScreen
import org.meshtastic.feature.settings.radio.component.LoRaConfigScreen
@@ -44,19 +44,13 @@ fun NavGraphBuilder.channelsGraph(navController: NavHostController) {
onNavigateUp = { navController.navigateUp() },
)
}
- configRoutes(navController)
- }
-}
-private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
- ConfigRoute.entries.forEach { configRoute ->
- composable(configRoute.route::class) { backStackEntry ->
- val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(ChannelsRoutes.ChannelsGraph) }
- when (configRoute) {
- ConfigRoute.CHANNELS -> ChannelConfigScreen(hiltViewModel(parentEntry), navController::popBackStack)
- ConfigRoute.LORA -> LoRaConfigScreen(hiltViewModel(parentEntry), navController::popBackStack)
- else -> Unit // Should not happen if ConfigRoute enum is exhaustive for this context
- }
+ navController.configComposable {
+ ChannelConfigScreen(viewModel = it, onBack = navController::popBackStack)
+ }
+
+ navController.configComposable {
+ LoRaConfigScreen(viewModel = it, onBack = navController::popBackStack)
}
}
}
diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt
index 425905908..8c94d688e 100644
--- a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsNavigation.kt
@@ -29,7 +29,6 @@ import org.meshtastic.core.navigation.ConnectionsRoutes
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.core.navigation.SettingsRoutes
-import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.feature.settings.radio.component.LoRaConfigScreen
/** Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections]. */
@@ -55,14 +54,9 @@ fun NavGraphBuilder.connectionsGraph(navController: NavHostController) {
onConfigNavigate = { route -> navController.navigate(route) },
)
}
- configRoutes(navController)
- }
-}
-private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
- composable { backStackEntry ->
- val parentEntry =
- remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
- LoRaConfigScreen(viewModel = hiltViewModel(parentEntry), navController::popBackStack)
+ navController.configComposable {
+ LoRaConfigScreen(viewModel = it, onBack = navController::popBackStack)
+ }
}
}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt b/app/src/main/java/com/geeksville/mesh/navigation/SettingsNavigation.kt
similarity index 84%
rename from feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt
rename to app/src/main/java/com/geeksville/mesh/navigation/SettingsNavigation.kt
index f7d2d6530..cf2098ce4 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavigation.kt
+++ b/app/src/main/java/com/geeksville/mesh/navigation/SettingsNavigation.kt
@@ -15,8 +15,11 @@
* along with this program. If not, see .
*/
-package org.meshtastic.feature.settings.navigation
+@file:Suppress("Wrapping", "SpacingAroundColon")
+package com.geeksville.mesh.navigation
+
+import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavGraphBuilder
@@ -25,11 +28,14 @@ import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import androidx.navigation.navigation
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
+import org.meshtastic.core.navigation.Graph
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.feature.settings.SettingsScreen
import org.meshtastic.feature.settings.debugging.DebugScreen
+import org.meshtastic.feature.settings.navigation.ConfigRoute
+import org.meshtastic.feature.settings.navigation.ModuleRoute
import org.meshtastic.feature.settings.radio.CleanNodeDatabaseScreen
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.feature.settings.radio.channel.ChannelConfigScreen
@@ -55,9 +61,7 @@ import org.meshtastic.feature.settings.radio.component.SerialConfigScreen
import org.meshtastic.feature.settings.radio.component.StoreForwardConfigScreen
import org.meshtastic.feature.settings.radio.component.TelemetryConfigScreen
import org.meshtastic.feature.settings.radio.component.UserConfigScreen
-
-fun getNavRouteFrom(routeName: String): Route? =
- ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route
+import kotlin.reflect.KClass
@Suppress("LongMethod")
fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
@@ -92,11 +96,10 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
}
ConfigRoute.entries.forEach { entry ->
- composable(entry.route::class) { backStackEntry ->
- val parentEntry =
- remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
- val viewModel = hiltViewModel(parentEntry)
-
+ navController.configComposable(
+ route = entry.route::class,
+ parentGraphRoute = SettingsRoutes.SettingsGraph::class,
+ ) { viewModel ->
when (entry) {
ConfigRoute.USER -> UserConfigScreen(viewModel, onBack = navController::popBackStack)
@@ -122,11 +125,10 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
}
ModuleRoute.entries.forEach { entry ->
- composable(entry.route::class) { backStackEntry ->
- val parentEntry =
- remember(backStackEntry) { navController.getBackStackEntry() }
- val viewModel = hiltViewModel(parentEntry)
-
+ navController.configComposable(
+ route = entry.route::class,
+ parentGraphRoute = SettingsRoutes.SettingsGraph::class,
+ ) { viewModel ->
when (entry) {
ModuleRoute.MQTT -> MQTTConfigScreen(viewModel, onBack = navController::popBackStack)
@@ -172,3 +174,22 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
}
}
}
+
+context(_: NavGraphBuilder)
+inline fun NavHostController.configComposable(
+ noinline content: @Composable (RadioConfigViewModel) -> Unit,
+) {
+ configComposable(route = R::class, parentGraphRoute = G::class, content = content)
+}
+
+context(navGraphBuilder: NavGraphBuilder)
+fun NavHostController.configComposable(
+ route: KClass,
+ parentGraphRoute: KClass,
+ content: @Composable (RadioConfigViewModel) -> Unit,
+) {
+ navGraphBuilder.composable(route = route) { backStackEntry ->
+ val parentEntry = remember(backStackEntry) { getBackStackEntry(parentGraphRoute) }
+ content(hiltViewModel(parentEntry))
+ }
+}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/Main.kt b/app/src/main/java/com/geeksville/mesh/ui/Main.kt
index 856c6d63d..c28d2b2fc 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt
@@ -90,6 +90,7 @@ import com.geeksville.mesh.navigation.connectionsGraph
import com.geeksville.mesh.navigation.contactsGraph
import com.geeksville.mesh.navigation.mapGraph
import com.geeksville.mesh.navigation.nodesGraph
+import com.geeksville.mesh.navigation.settingsGraph
import com.geeksville.mesh.repository.radio.MeshActivity
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.ui.connections.DeviceType
@@ -120,7 +121,6 @@ import org.meshtastic.core.ui.share.SharedContactDialog
import org.meshtastic.core.ui.theme.StatusColors.StatusBlue
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
import org.meshtastic.feature.node.metrics.annotateTraceroute
-import org.meshtastic.feature.settings.navigation.settingsGraph
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
diff --git a/feature/settings/detekt-baseline.xml b/feature/settings/detekt-baseline.xml
index 8961b388d..809b837cf 100644
--- a/feature/settings/detekt-baseline.xml
+++ b/feature/settings/detekt-baseline.xml
@@ -12,7 +12,6 @@
CyclomaticComplexMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit)
CyclomaticComplexMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit)
CyclomaticComplexMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)
- CyclomaticComplexMethod:SettingsNavigation.kt$@Suppress("LongMethod") fun NavGraphBuilder.settingsGraph(navController: NavHostController)
LambdaParameterEventTrailing:NodeActionButton.kt$onClick
LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit)
LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit)
@@ -36,6 +35,7 @@
MagicNumber:EditChannelDialog.kt$32
MagicNumber:PacketResponseStateDialog.kt$100
ModifierMissing:CleanNodeDatabaseScreen.kt$CleanNodeDatabaseScreen
+ ModifierMissing:Debug.kt$DebugScreen
ModifierMissing:DeviceConfigItemList.kt$DeviceConfigScreen
ModifierMissing:MapReportingPreference.kt$MapReportingPreference
ModifierMissing:NetworkConfigItemList.kt$NetworkConfigScreen
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt
index f2b7cd9ec..1f69872fb 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/debugging/Debug.kt
@@ -103,7 +103,7 @@ private var redactedKeys: List = listOf("session_passkey", "private_key"
@Suppress("LongMethod")
@Composable
-internal fun DebugScreen(onNavigateUp: () -> Unit, viewModel: DebugViewModel = hiltViewModel()) {
+fun DebugScreen(onNavigateUp: () -> Unit, viewModel: DebugViewModel = hiltViewModel()) {
val listState = rememberLazyListState()
val logs by viewModel.meshLog.collectAsStateWithLifecycle()
val searchState by viewModel.searchState.collectAsStateWithLifecycle()
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavUtils.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavUtils.kt
new file mode 100644
index 000000000..59b533579
--- /dev/null
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/navigation/SettingsNavUtils.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2025 Meshtastic LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.meshtastic.feature.settings.navigation
+
+import org.meshtastic.core.navigation.Route
+
+fun getNavRouteFrom(routeName: String): Route? =
+ ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route