Move nav routes to new :navigation project module (#3124)

This commit is contained in:
Phil Oliver 2025-09-17 06:46:43 -04:00 committed by GitHub
parent 299dac415d
commit 7afab16011
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 281 additions and 235 deletions

View file

@ -28,14 +28,6 @@ import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.settings.radio.components.ChannelConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
import com.geeksville.mesh.ui.sharing.ChannelScreen
import kotlinx.serialization.Serializable
@Serializable
sealed class ChannelsRoutes {
@Serializable data object ChannelsGraph : Graph
@Serializable data object Channels : Route
}
/** Navigation graph for for the top level ChannelScreen - [ChannelsRoutes.Channels]. */
fun NavGraphBuilder.channelsGraph(navController: NavHostController, uiViewModel: UIViewModel) {

View file

@ -28,14 +28,6 @@ import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.connections.ConnectionsScreen
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
import kotlinx.serialization.Serializable
@Serializable
sealed class ConnectionsRoutes {
@Serializable data object ConnectionsGraph : Graph
@Serializable data object Connections : Route
}
/** Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections]. */
fun NavGraphBuilder.connectionsGraph(

View file

@ -28,19 +28,6 @@ import com.geeksville.mesh.ui.contact.ContactsScreen
import com.geeksville.mesh.ui.message.MessageScreen
import com.geeksville.mesh.ui.message.QuickChatScreen
import com.geeksville.mesh.ui.sharing.ShareScreen
import kotlinx.serialization.Serializable
sealed class ContactsRoutes {
@Serializable data object Contacts : Route
@Serializable data class Messages(val contactKey: String, val message: String = "") : Route
@Serializable data class Share(val message: String) : Route
@Serializable data object QuickChat : Route
@Serializable data object ContactsGraph : Graph
}
@Suppress("LongMethod")
fun NavGraphBuilder.contactsGraph(navController: NavHostController, uiViewModel: UIViewModel) {

View file

@ -23,18 +23,11 @@ import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.map.MapView
import com.geeksville.mesh.ui.map.MapViewModel
import kotlinx.serialization.Serializable
sealed class MapRoutes {
@Serializable data object Map : Route
}
fun NavGraphBuilder.mapGraph(navController: NavHostController, uiViewModel: UIViewModel, mapViewModel: MapViewModel) {
fun NavGraphBuilder.mapGraph(navController: NavHostController, uiViewModel: UIViewModel) {
composable<MapRoutes.Map>(deepLinks = listOf(navDeepLink<MapRoutes.Map>(basePath = "$DEEP_LINK_BASE_URI/map"))) {
MapView(
uiViewModel = uiViewModel,
mapViewModel = mapViewModel,
navigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) },
)
}

View file

@ -1,85 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.PowerSettingsNew
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Restore
import androidx.compose.material.icons.rounded.Storage
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.rememberNavController
import com.geeksville.mesh.R
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
import com.geeksville.mesh.ui.map.MapViewModel
import kotlinx.serialization.Serializable
enum class AdminRoute(val icon: ImageVector, @StringRes val title: Int) {
REBOOT(Icons.Rounded.RestartAlt, R.string.reboot),
SHUTDOWN(Icons.Rounded.PowerSettingsNew, R.string.shutdown),
FACTORY_RESET(Icons.Rounded.Restore, R.string.factory_reset),
NODEDB_RESET(Icons.Rounded.Storage, R.string.nodedb_reset),
}
const val DEEP_LINK_BASE_URI = "meshtastic://meshtastic"
@Serializable sealed interface Graph : Route
@Serializable sealed interface Route
fun NavDestination.isConfigRoute(): Boolean =
ConfigRoute.entries.any { hasRoute(it.route::class) } || ModuleRoute.entries.any { hasRoute(it.route::class) }
fun NavDestination.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any { hasRoute(it.route::class) }
fun NavDestination.showLongNameTitle(): Boolean = !this.isTopLevel() &&
(
this.hasRoute<SettingsRoutes.Settings>() ||
this.hasRoute<NodesRoutes.NodeDetail>() ||
this.isConfigRoute() ||
this.isNodeDetailRoute()
)
@Suppress("LongMethod")
@Composable
fun NavGraph(
modifier: Modifier = Modifier,
uIViewModel: UIViewModel = hiltViewModel(),
bluetoothViewModel: BluetoothViewModel = hiltViewModel(),
mapViewModel: MapViewModel = hiltViewModel(),
navController: NavHostController = rememberNavController(),
) {
NavHost(navController = navController, startDestination = NodesRoutes.NodesGraph, modifier = modifier) {
contactsGraph(navController, uIViewModel)
nodesGraph(navController, uIViewModel)
mapGraph(navController, uIViewModel, mapViewModel)
channelsGraph(navController, uIViewModel)
connectionsGraph(navController, uIViewModel, bluetoothViewModel)
settingsGraph(navController, uIViewModel)
}
}

View file

@ -32,6 +32,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
@ -51,37 +53,6 @@ import com.geeksville.mesh.ui.metrics.TracerouteLogScreen
import com.geeksville.mesh.ui.node.NodeDetailScreen
import com.geeksville.mesh.ui.node.NodeMapScreen
import com.geeksville.mesh.ui.node.NodeScreen
import kotlinx.serialization.Serializable
sealed class NodesRoutes {
@Serializable data object Nodes : Route
@Serializable data object NodesGraph : Graph
@Serializable data class NodeDetailGraph(val destNum: Int? = null) : Graph
@Serializable data class NodeDetail(val destNum: Int? = null) : Route
}
sealed class NodeDetailRoutes {
@Serializable data object DeviceMetrics : Route
@Serializable data object NodeMap : Route
@Serializable data object PositionLog : Route
@Serializable data object EnvironmentMetrics : Route
@Serializable data object SignalMetrics : Route
@Serializable data object PowerMetrics : Route
@Serializable data object TracerouteLog : Route
@Serializable data object HostMetricsLog : Route
@Serializable data object PaxMetrics : Route
}
fun NavGraphBuilder.nodesGraph(navController: NavHostController, uiViewModel: UIViewModel) {
navigation<NodesRoutes.NodesGraph>(startDestination = NodesRoutes.Nodes) {
@ -191,6 +162,8 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, uiViewMode
}
}
fun NavDestination.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any { hasRoute(it.route::class) }
/**
* Helper to define a composable route for a screen within the node detail graph.
*

View file

@ -47,6 +47,8 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
@ -83,75 +85,6 @@ import com.geeksville.mesh.ui.settings.radio.components.SerialConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.StoreForwardConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.TelemetryConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.UserConfigScreen
import kotlinx.serialization.Serializable
sealed class SettingsRoutes {
@Serializable data class SettingsGraph(val destNum: Int? = null) : Graph
@Serializable data class Settings(val destNum: Int? = null) : Route
// region radio Config Routes
@Serializable data object User : Route
@Serializable data object ChannelConfig : Route
@Serializable data object Device : Route
@Serializable data object Position : Route
@Serializable data object Power : Route
@Serializable data object Network : Route
@Serializable data object Display : Route
@Serializable data object LoRa : Route
@Serializable data object Bluetooth : Route
@Serializable data object Security : Route
// endregion
// region module config routes
@Serializable data object MQTT : Route
@Serializable data object Serial : Route
@Serializable data object ExtNotification : Route
@Serializable data object StoreForward : Route
@Serializable data object RangeTest : Route
@Serializable data object Telemetry : Route
@Serializable data object CannedMessage : Route
@Serializable data object Audio : Route
@Serializable data object RemoteHardware : Route
@Serializable data object NeighborInfo : Route
@Serializable data object AmbientLighting : Route
@Serializable data object DetectionSensor : Route
@Serializable data object Paxcounter : Route
// endregion
// region advanced config routes
@Serializable data object CleanNodeDb : Route
@Serializable data object DebugPanel : Route
// endregion
}
fun getNavRouteFrom(routeName: String): Route? =
ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route
@ -189,6 +122,9 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController, uiViewModel:
}
}
fun NavDestination.isConfigRoute(): Boolean =
ConfigRoute.entries.any { hasRoute(it.route::class) } || ModuleRoute.entries.any { hasRoute(it.route::class) }
/**
* Helper to define a composable route for a radio configuration screen within the radio config graph.
*

View file

@ -70,6 +70,7 @@ import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.geeksville.mesh.BuildConfig
@ -86,10 +87,15 @@ import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.ConnectionsRoutes
import com.geeksville.mesh.navigation.ContactsRoutes
import com.geeksville.mesh.navigation.MapRoutes
import com.geeksville.mesh.navigation.NavGraph
import com.geeksville.mesh.navigation.NodesRoutes
import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.navigation.channelsGraph
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.ConnectionState
import com.geeksville.mesh.service.MeshService
@ -363,12 +369,19 @@ fun MainScreen(
}
},
)
NavGraph(
modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding().imePadding(),
uIViewModel = uIViewModel,
bluetoothViewModel = bluetoothViewModel,
NavHost(
navController = navController,
)
startDestination = NodesRoutes.NodesGraph,
modifier = Modifier.fillMaxSize().recalculateWindowInsets().safeDrawingPadding().imePadding(),
) {
contactsGraph(navController, uiViewModel = uIViewModel)
nodesGraph(navController, uiViewModel = uIViewModel)
mapGraph(navController, uiViewModel = uIViewModel)
channelsGraph(navController, uiViewModel = uIViewModel)
connectionsGraph(navController, uiViewModel = uIViewModel, bluetoothViewModel)
settingsGraph(navController, uiViewModel = uIViewModel)
}
}
}
}

View file

@ -42,6 +42,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavHostController
import androidx.navigation.compose.currentBackStackEntryAsState
@ -51,7 +52,8 @@ import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.ContactsRoutes
import com.geeksville.mesh.navigation.NodesRoutes
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.navigation.showLongNameTitle
import com.geeksville.mesh.navigation.isConfigRoute
import com.geeksville.mesh.navigation.isNodeDetailRoute
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.debug.DebugMenuActions
@ -178,6 +180,14 @@ private fun MainAppBar(
)
}
fun NavDestination.showLongNameTitle(): Boolean = !this.isTopLevel() &&
(
this.hasRoute<SettingsRoutes.Settings>() ||
this.hasRoute<NodesRoutes.NodeDetail>() ||
this.isConfigRoute() ||
this.isNodeDetailRoute()
)
@Composable
private fun TopBarActions(
ourNode: Node?,

View file

@ -17,6 +17,7 @@
package com.geeksville.mesh.ui.settings.radio
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
@ -24,6 +25,10 @@ import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material.icons.rounded.BugReport
import androidx.compose.material.icons.rounded.CleaningServices
import androidx.compose.material.icons.rounded.PowerSettingsNew
import androidx.compose.material.icons.rounded.RestartAlt
import androidx.compose.material.icons.rounded.Restore
import androidx.compose.material.icons.rounded.Storage
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@ -33,11 +38,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.navigation.AdminRoute
import com.geeksville.mesh.navigation.ConfigRoute
import com.geeksville.mesh.navigation.ModuleRoute
import com.geeksville.mesh.navigation.Route
@ -160,6 +165,13 @@ fun RadioConfigItemList(
}
}
enum class AdminRoute(val icon: ImageVector, @StringRes val title: Int) {
REBOOT(Icons.Rounded.RestartAlt, R.string.reboot),
SHUTDOWN(Icons.Rounded.PowerSettingsNew, R.string.shutdown),
FACTORY_RESET(Icons.Rounded.Restore, R.string.factory_reset),
NODEDB_RESET(Icons.Rounded.Storage, R.string.nodedb_reset),
}
@Preview(showBackground = true)
@Composable
private fun RadioSettingsScreenPreview() = AppTheme {

View file

@ -56,7 +56,6 @@ import com.geeksville.mesh.model.getChannelList
import com.geeksville.mesh.model.getStringResFrom
import com.geeksville.mesh.model.toChannelSet
import com.geeksville.mesh.moduleConfig
import com.geeksville.mesh.navigation.AdminRoute
import com.geeksville.mesh.navigation.ConfigRoute
import com.geeksville.mesh.navigation.ModuleRoute
import com.geeksville.mesh.navigation.SettingsRoutes