From 2a05fc072d3e99d5721265aff4bf6c3e583455b2 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:45:26 +0000 Subject: [PATCH] refactor: organize navigation (#2042) --- .../geeksville/mesh/model/MetricsViewModel.kt | 4 +- .../mesh/navigation/ChannelsRoutes.kt | 4 +- .../mesh/navigation/ConnectionsRoutes.kt | 19 +- .../mesh/navigation/ContactsRoutes.kt | 100 +++++++++ .../geeksville/mesh/navigation/NavGraph.kt | 189 +----------------- .../{NodesGraph.kt => NodesRoutes.kt} | 56 ++++-- ...dioConfigGraph.kt => RadioConfigRoutes.kt} | 137 ++++++++++--- .../main/java/com/geeksville/mesh/ui/Main.kt | 14 +- .../mesh/ui/connections/Connections.kt | 5 +- .../geeksville/mesh/ui/contact/Contacts.kt | 4 +- .../com/geeksville/mesh/ui/node/NodeDetail.kt | 20 +- .../ui/radioconfig/RadioConfigViewModel.kt | 19 +- 12 files changed, 306 insertions(+), 265 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt rename app/src/main/java/com/geeksville/mesh/navigation/{NodesGraph.kt => NodesRoutes.kt} (75%) rename app/src/main/java/com/geeksville/mesh/navigation/{RadioConfigGraph.kt => RadioConfigRoutes.kt} (71%) diff --git a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt index 174d99d9e..0aa1f0215 100644 --- a/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/MetricsViewModel.kt @@ -39,7 +39,7 @@ import com.geeksville.mesh.database.MeshLogRepository import com.geeksville.mesh.database.entity.FirmwareRelease import com.geeksville.mesh.database.entity.MeshLog import com.geeksville.mesh.model.map.CustomTileSource -import com.geeksville.mesh.navigation.Route +import com.geeksville.mesh.navigation.NodesRoutes import com.geeksville.mesh.repository.api.DeviceHardwareRepository import com.geeksville.mesh.repository.api.FirmwareReleaseRepository import com.geeksville.mesh.repository.datastore.RadioConfigRepository @@ -205,7 +205,7 @@ class MetricsViewModel @Inject constructor( private val firmwareReleaseRepository: FirmwareReleaseRepository, private val preferences: SharedPreferences, ) : ViewModel(), Logging { - private val destNum = savedStateHandle.toRoute().destNum + private val destNum = savedStateHandle.toRoute().destNum private fun MeshLog.hasValidTraceroute(): Boolean = with(fromRadio.packet) { hasDecoded() && decoded.wantResponse && from == 0 && to == destNum diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt index 49607e463..e7444ec76 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ChannelsRoutes.kt @@ -24,13 +24,13 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.navigation import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.ui.sharing.ChannelScreen import com.geeksville.mesh.ui.radioconfig.components.ChannelConfigScreen import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen +import com.geeksville.mesh.ui.sharing.ChannelScreen import kotlinx.serialization.Serializable @Serializable -sealed interface ChannelsRoutes { +sealed class ChannelsRoutes { @Serializable data object ChannelsGraph : Graph diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt index 718f60efe..4bcecd74d 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/ConnectionsRoutes.kt @@ -30,7 +30,7 @@ import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen import kotlinx.serialization.Serializable @Serializable -sealed interface ConnectionsRoutes { +sealed class ConnectionsRoutes { @Serializable data object ConnectionsGraph : Graph @@ -59,8 +59,8 @@ fun NavGraphBuilder.connectionsGraph(navController: NavHostController, uiViewMod ConnectionsScreen( uiViewModel, radioConfigViewModel = hiltViewModel(parentEntry), - onNavigateToRadioConfig = { navController.navigate(Route.RadioConfig()) }, - onNavigateToNodeDetails = { navController.navigate(Route.NodeDetail(it)) }, + onNavigateToRadioConfig = { navController.navigate(RadioConfigRoutes.RadioConfig()) }, + onNavigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetail(it)) }, onConfigNavigate = { route -> navController.navigate(route) } ) } @@ -71,15 +71,10 @@ fun NavGraphBuilder.connectionsGraph(navController: NavHostController, uiViewMod private fun NavGraphBuilder.configRoutes( navController: NavHostController, ) { - ConfigRoute.entries.forEach { configRoute -> - composable(configRoute.route::class) { backStackEntry -> - val parentEntry = remember(backStackEntry) { - navController.getBackStackEntry() - } - when (configRoute) { - ConfigRoute.LORA -> LoRaConfigScreen(hiltViewModel(parentEntry)) - else -> Unit - } + composable { backStackEntry -> + val parentEntry = remember(backStackEntry) { + navController.getBackStackEntry() } + LoRaConfigScreen(hiltViewModel(parentEntry)) } } diff --git a/app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt b/app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt new file mode 100644 index 000000000..220cd51c9 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/navigation/ContactsRoutes.kt @@ -0,0 +1,100 @@ +/* + * 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 com.geeksville.mesh.navigation + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import androidx.navigation.navDeepLink +import androidx.navigation.navigation +import androidx.navigation.toRoute +import com.geeksville.mesh.model.UIViewModel +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 +} + +fun NavGraphBuilder.contactsGraph( + navController: NavHostController, + uiViewModel: UIViewModel, +) { + navigation( + startDestination = ContactsRoutes.Contacts, + ) { + composable { + ContactsScreen( + uiViewModel, + onNavigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) } + ) + } + composable( + deepLinks = listOf( + navDeepLink { + uriPattern = "$DEEP_LINK_BASE_URI/messages/{contactKey}?message={message}" + action = "android.intent.action.VIEW" + }, + ) + ) { backStackEntry -> + val args = backStackEntry.toRoute() + MessageScreen( + contactKey = args.contactKey, + message = args.message, + viewModel = uiViewModel, + navigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) }, + navigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetail(it)) }, + onNavigateBack = navController::navigateUp, + ) + } + } + composable( + deepLinks = listOf( + navDeepLink { + uriPattern = "$DEEP_LINK_BASE_URI/share?message={message}" + action = "android.intent.action.VIEW" + } + ) + ) { backStackEntry -> + val message = backStackEntry.toRoute().message + ShareScreen(uiViewModel) { + navController.navigate(ContactsRoutes.Messages(it, message)) { + popUpTo { inclusive = true } + } + } + } + composable { + QuickChatScreen() + } +} diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt index 50baf4e0f..869c8475c 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/NavGraph.kt @@ -27,17 +27,11 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import androidx.navigation.navDeepLink -import androidx.navigation.toRoute import com.geeksville.mesh.R import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel -import com.geeksville.mesh.ui.contact.ContactsScreen import com.geeksville.mesh.ui.debug.DebugScreen import com.geeksville.mesh.ui.map.MapView -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 enum class AdminRoute(@StringRes val title: Int) { @@ -50,129 +44,15 @@ enum class AdminRoute(@StringRes val title: Int) { const val DEEP_LINK_BASE_URI = "meshtastic://meshtastic" @Serializable -sealed interface Graph : Route { - @Serializable - data class RadioConfigGraph(val destNum: Int? = null) : Graph -} - +sealed interface Graph : Route @Serializable sealed interface Route { - @Serializable - data object Contacts : Route @Serializable data object Map : Route @Serializable data object DebugPanel : Route - - @Serializable - data class Messages(val contactKey: String, val message: String = "") : Route - - @Serializable - data object QuickChat : Route - - @Serializable - data class Share(val message: String) : Route - - @Serializable - data class RadioConfig(val destNum: Int? = null) : Route - - @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 - - @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 - - @Serializable - data class NodeDetail(val destNum: Int? = null) : Route - - @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 } fun NavDestination.isConfigRoute(): Boolean { @@ -187,8 +67,8 @@ fun NavDestination.isNodeDetailRoute(): Boolean { fun NavDestination.showLongNameTitle(): Boolean { return !this.isTopLevel() && ( - this.hasRoute() || - this.hasRoute() || + this.hasRoute() || + this.hasRoute() || this.isConfigRoute() || this.isNodeDetailRoute() ) @@ -203,70 +83,19 @@ fun NavGraph( ) { NavHost( navController = navController, - startDestination = if (uIViewModel.bondedAddress.isNullOrBlank()) { + startDestination = if (uIViewModel.isConnected()) { ConnectionsRoutes.ConnectionsGraph } else { - Route.Contacts + ContactsRoutes.ContactsGraph }, modifier = modifier, ) { - composable { - ContactsScreen( - uIViewModel, - onNavigate = { navController.navigate(Route.Messages(it)) } - ) - } - composable { - MapView(uIViewModel) - } - + contactsGraph(navController, uIViewModel) + nodesGraph(navController, uIViewModel,) + composable { MapView(uIViewModel) } channelsGraph(navController, uIViewModel) - connectionsGraph(navController, uIViewModel) - - composable { - DebugScreen() - } - composable( - deepLinks = listOf( - navDeepLink { - uriPattern = "$DEEP_LINK_BASE_URI/messages/{contactKey}?message={message}" - action = "android.intent.action.VIEW" - }, - ) - ) { backStackEntry -> - val args = backStackEntry.toRoute() - MessageScreen( - contactKey = args.contactKey, - message = args.message, - viewModel = uIViewModel, - navigateToMessages = { navController.navigate(Route.Messages(it)) }, - navigateToNodeDetails = { navController.navigate(Route.NodeDetail(it)) }, - onNavigateBack = navController::navigateUp, - ) - } - composable { - QuickChatScreen() - } - nodesGraph( - navController, - uIViewModel, - ) + composable { DebugScreen() } radioConfigGraph(navController, uIViewModel) - composable( - deepLinks = listOf( - navDeepLink { - uriPattern = "$DEEP_LINK_BASE_URI/share?message={message}" - action = "android.intent.action.VIEW" - } - ) - ) { backStackEntry -> - val message = backStackEntry.toRoute().message - ShareScreen(uIViewModel) { - navController.navigate(Route.Messages(it, message)) { - popUpTo { inclusive = true } - } - } - } } } diff --git a/app/src/main/java/com/geeksville/mesh/navigation/NodesGraph.kt b/app/src/main/java/com/geeksville/mesh/navigation/NodesRoutes.kt similarity index 75% rename from app/src/main/java/com/geeksville/mesh/navigation/NodesGraph.kt rename to app/src/main/java/com/geeksville/mesh/navigation/NodesRoutes.kt index bf8ed2866..53548c262 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/NodesGraph.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/NodesRoutes.kt @@ -56,6 +56,36 @@ sealed class NodesRoutes { @Serializable data object NodeDetailGraph : 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 } fun NavGraphBuilder.nodesGraph( @@ -69,10 +99,10 @@ fun NavGraphBuilder.nodesGraph( NodeScreen( model = uiViewModel, navigateToMessages = { - navController.navigate(Route.Messages(it)) + navController.navigate(ContactsRoutes.Messages(it)) }, navigateToNodeDetails = { - navController.navigate(Route.NodeDetail(it)) + navController.navigate(NodesRoutes.NodeDetail(it)) }, ) } @@ -85,16 +115,16 @@ fun NavGraphBuilder.nodeDetailGraph( uiViewModel: UIViewModel, ) { navigation( - startDestination = Route.NodeDetail() + startDestination = NodesRoutes.NodeDetail() ) { - composable { backStackEntry -> + composable { backStackEntry -> val parentEntry = remember(backStackEntry) { navController.getBackStackEntry() } NodeDetailScreen( uiViewModel = uiViewModel, navigateToMessages = { - navController.navigate(Route.Messages(it)) + navController.navigate(ContactsRoutes.Messages(it)) }, onNavigate = { navController.navigate(it) @@ -137,12 +167,12 @@ enum class NodeDetailRoute( val route: Route, val icon: ImageVector?, ) { - DEVICE(R.string.device, Route.DeviceMetrics, Icons.Default.Router), - NODE_MAP(R.string.node_map, Route.NodeMap, Icons.Default.LocationOn), - POSITION_LOG(R.string.position_log, Route.PositionLog, Icons.Default.LocationOn), - ENVIRONMENT(R.string.environment, Route.EnvironmentMetrics, Icons.Default.LightMode), - SIGNAL(R.string.signal, Route.SignalMetrics, Icons.Default.CellTower), - TRACEROUTE(R.string.traceroute, Route.TracerouteLog, Icons.Default.PermScanWifi), - POWER(R.string.power, Route.PowerMetrics, Icons.Default.Power), - HOST(R.string.host, Route.HostMetricsLog, Icons.Default.Memory), + DEVICE(R.string.device, NodeDetailRoutes.DeviceMetrics, Icons.Default.Router), + NODE_MAP(R.string.node_map, NodeDetailRoutes.NodeMap, Icons.Default.LocationOn), + POSITION_LOG(R.string.position_log, NodeDetailRoutes.PositionLog, Icons.Default.LocationOn), + ENVIRONMENT(R.string.environment, NodeDetailRoutes.EnvironmentMetrics, Icons.Default.LightMode), + SIGNAL(R.string.signal, NodeDetailRoutes.SignalMetrics, Icons.Default.CellTower), + TRACEROUTE(R.string.traceroute, NodeDetailRoutes.TracerouteLog, Icons.Default.PermScanWifi), + POWER(R.string.power, NodeDetailRoutes.PowerMetrics, Icons.Default.Power), + HOST(R.string.host, NodeDetailRoutes.HostMetricsLog, Icons.Default.Memory), } diff --git a/app/src/main/java/com/geeksville/mesh/navigation/RadioConfigGraph.kt b/app/src/main/java/com/geeksville/mesh/navigation/RadioConfigRoutes.kt similarity index 71% rename from app/src/main/java/com/geeksville/mesh/navigation/RadioConfigGraph.kt rename to app/src/main/java/com/geeksville/mesh/navigation/RadioConfigRoutes.kt index 5402c2297..4b30dd544 100644 --- a/app/src/main/java/com/geeksville/mesh/navigation/RadioConfigGraph.kt +++ b/app/src/main/java/com/geeksville/mesh/navigation/RadioConfigRoutes.kt @@ -76,6 +76,83 @@ import com.geeksville.mesh.ui.radioconfig.components.SerialConfigScreen import com.geeksville.mesh.ui.radioconfig.components.StoreForwardConfigScreen import com.geeksville.mesh.ui.radioconfig.components.TelemetryConfigScreen import com.geeksville.mesh.ui.radioconfig.components.UserConfigScreen +import kotlinx.serialization.Serializable + +sealed class RadioConfigRoutes { + @Serializable + data class RadioConfigGraph(val destNum: Int? = null) : Graph + + @Serializable + data class RadioConfig(val destNum: Int? = null) : Route + + @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 + + @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 +} fun getNavRouteFrom(routeName: String): Route? { return ConfigRoute.entries.find { it.name == routeName }?.route @@ -83,19 +160,19 @@ fun getNavRouteFrom(routeName: String): Route? { } fun NavGraphBuilder.radioConfigGraph(navController: NavHostController, uiViewModel: UIViewModel) { - navigation( - startDestination = Route.RadioConfig(), + navigation( + startDestination = RadioConfigRoutes.RadioConfig(), ) { - composable { backStackEntry -> + composable { backStackEntry -> val parentEntry = remember(backStackEntry) { - navController.getBackStackEntry() + navController.getBackStackEntry() } RadioConfigScreen( uiViewModel = uiViewModel, viewModel = hiltViewModel(parentEntry) ) { navController.navigate(it) { - popUpTo(Route.RadioConfig()) { + popUpTo(RadioConfigRoutes.RadioConfig()) { inclusive = false } } @@ -112,7 +189,7 @@ private fun NavGraphBuilder.configRoutes( ConfigRoute.entries.forEach { configRoute -> composable(configRoute.route::class) { backStackEntry -> val parentEntry = remember(backStackEntry) { - navController.getBackStackEntry() + navController.getBackStackEntry() } when (configRoute) { ConfigRoute.USER -> UserConfigScreen(hiltViewModel(parentEntry)) @@ -137,7 +214,7 @@ private fun NavGraphBuilder.moduleRoutes( ModuleRoute.entries.forEach { moduleRoute -> composable(moduleRoute.route::class) { backStackEntry -> val parentEntry = remember(backStackEntry) { - navController.getBackStackEntry() + navController.getBackStackEntry() } when (moduleRoute) { ModuleRoute.MQTT -> MQTTConfigScreen(hiltViewModel(parentEntry)) @@ -181,16 +258,16 @@ enum class ConfigRoute( val icon: ImageVector?, val type: Int = 0 ) { - USER(R.string.user, Route.User, Icons.Default.Person, 0), - CHANNELS(R.string.channels, Route.ChannelConfig, Icons.AutoMirrored.Default.List, 0), - DEVICE(R.string.device, Route.Device, Icons.Default.Router, 0), - POSITION(R.string.position, Route.Position, Icons.Default.LocationOn, 1), - POWER(R.string.power, Route.Power, Icons.Default.Power, 2), - NETWORK(R.string.network, Route.Network, Icons.Default.Wifi, 3), - DISPLAY(R.string.display, Route.Display, Icons.Default.DisplaySettings, 4), - LORA(R.string.lora, Route.LoRa, Icons.Default.CellTower, 5), - BLUETOOTH(R.string.bluetooth, Route.Bluetooth, Icons.Default.Bluetooth, 6), - SECURITY(R.string.security, Route.Security, Icons.Default.Security, 7), + USER(R.string.user, RadioConfigRoutes.User, Icons.Default.Person, 0), + CHANNELS(R.string.channels, RadioConfigRoutes.ChannelConfig, Icons.AutoMirrored.Default.List, 0), + DEVICE(R.string.device, RadioConfigRoutes.Device, Icons.Default.Router, 0), + POSITION(R.string.position, RadioConfigRoutes.Position, Icons.Default.LocationOn, 1), + POWER(R.string.power, RadioConfigRoutes.Power, Icons.Default.Power, 2), + NETWORK(R.string.network, RadioConfigRoutes.Network, Icons.Default.Wifi, 3), + DISPLAY(R.string.display, RadioConfigRoutes.Display, Icons.Default.DisplaySettings, 4), + LORA(R.string.lora, RadioConfigRoutes.LoRa, Icons.Default.CellTower, 5), + BLUETOOTH(R.string.bluetooth, RadioConfigRoutes.Bluetooth, Icons.Default.Bluetooth, 6), + SECURITY(R.string.security, RadioConfigRoutes.Security, Icons.Default.Security, 7), ; companion object { @@ -213,39 +290,39 @@ enum class ModuleRoute( val icon: ImageVector?, val type: Int = 0 ) { - MQTT(R.string.mqtt, Route.MQTT, Icons.Default.Cloud, 0), - SERIAL(R.string.serial, Route.Serial, Icons.Default.Usb, 1), + MQTT(R.string.mqtt, RadioConfigRoutes.MQTT, Icons.Default.Cloud, 0), + SERIAL(R.string.serial, RadioConfigRoutes.Serial, Icons.Default.Usb, 1), EXT_NOTIFICATION( R.string.external_notification, - Route.ExtNotification, + RadioConfigRoutes.ExtNotification, Icons.Default.Notifications, 2 ), STORE_FORWARD( R.string.store_forward, - Route.StoreForward, + RadioConfigRoutes.StoreForward, Icons.AutoMirrored.Default.Forward, 3 ), - RANGE_TEST(R.string.range_test, Route.RangeTest, Icons.Default.Speed, 4), - TELEMETRY(R.string.telemetry, Route.Telemetry, Icons.Default.DataUsage, 5), + RANGE_TEST(R.string.range_test, RadioConfigRoutes.RangeTest, Icons.Default.Speed, 4), + TELEMETRY(R.string.telemetry, RadioConfigRoutes.Telemetry, Icons.Default.DataUsage, 5), CANNED_MESSAGE( R.string.canned_message, - Route.CannedMessage, + RadioConfigRoutes.CannedMessage, Icons.AutoMirrored.Default.Message, 6 ), - AUDIO(R.string.audio, Route.Audio, Icons.AutoMirrored.Default.VolumeUp, 7), + AUDIO(R.string.audio, RadioConfigRoutes.Audio, Icons.AutoMirrored.Default.VolumeUp, 7), REMOTE_HARDWARE( R.string.remote_hardware, - Route.RemoteHardware, + RadioConfigRoutes.RemoteHardware, Icons.Default.SettingsRemote, 8 ), - NEIGHBOR_INFO(R.string.neighbor_info, Route.NeighborInfo, Icons.Default.People, 9), - AMBIENT_LIGHTING(R.string.ambient_lighting, Route.AmbientLighting, Icons.Default.LightMode, 10), - DETECTION_SENSOR(R.string.detection_sensor, Route.DetectionSensor, Icons.Default.Sensors, 11), - PAXCOUNTER(R.string.paxcounter, Route.Paxcounter, Icons.Default.PermScanWifi, 12), + NEIGHBOR_INFO(R.string.neighbor_info, RadioConfigRoutes.NeighborInfo, Icons.Default.People, 9), + AMBIENT_LIGHTING(R.string.ambient_lighting, RadioConfigRoutes.AmbientLighting, Icons.Default.LightMode, 10), + DETECTION_SENSOR(R.string.detection_sensor, RadioConfigRoutes.DetectionSensor, Icons.Default.Sensors, 11), + PAXCOUNTER(R.string.paxcounter, RadioConfigRoutes.Paxcounter, Icons.Default.PermScanWifi, 12), ; val bitfield: Int get() = 1 shl ordinal 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 555e39bc7..4dec8626e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Main.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Main.kt @@ -77,8 +77,10 @@ import com.geeksville.mesh.model.DeviceVersion import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.ChannelsRoutes import com.geeksville.mesh.navigation.ConnectionsRoutes +import com.geeksville.mesh.navigation.ContactsRoutes import com.geeksville.mesh.navigation.NavGraph import com.geeksville.mesh.navigation.NodesRoutes +import com.geeksville.mesh.navigation.RadioConfigRoutes import com.geeksville.mesh.navigation.Route import com.geeksville.mesh.navigation.showLongNameTitle import com.geeksville.mesh.service.MeshService @@ -89,7 +91,7 @@ import com.geeksville.mesh.ui.common.components.SimpleAlertDialog import com.geeksville.mesh.ui.debug.DebugMenuActions enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, val route: Route) { - Contacts(R.string.contacts, Icons.AutoMirrored.TwoTone.Chat, Route.Contacts), + Contacts(R.string.contacts, Icons.AutoMirrored.TwoTone.Chat, ContactsRoutes.Contacts), Nodes(R.string.nodes, Icons.TwoTone.People, NodesRoutes.Nodes), Map(R.string.map, Icons.TwoTone.Map, Route.Map), Channels(R.string.channels, Icons.TwoTone.Contactless, ChannelsRoutes.Channels), @@ -167,8 +169,8 @@ fun MainScreen( ) { action -> when (action) { MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel) - MainMenuAction.RADIO_CONFIG -> navController.navigate(Route.RadioConfig()) - MainMenuAction.QUICK_CHAT -> navController.navigate(Route.QuickChat) + MainMenuAction.RADIO_CONFIG -> navController.navigate(RadioConfigRoutes.RadioConfig()) + MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat) else -> onAction(action) } } @@ -271,7 +273,7 @@ private fun MainAppBar( val canNavigateBack = navController.previousBackStackEntry != null val isTopLevelRoute = currentDestination?.isTopLevel() == true val navigateUp: () -> Unit = navController::navigateUp - if (currentDestination?.hasRoute() == true) { + if (currentDestination?.hasRoute() == true) { return } TopAppBar( @@ -288,12 +290,12 @@ private fun MainAppBar( stringResource(id = R.string.debug_panel), ) - currentDestination.hasRoute() -> + currentDestination.hasRoute() -> Text( stringResource(id = R.string.quick_chat), ) - currentDestination.hasRoute() -> + currentDestination.hasRoute() -> Text( stringResource(id = R.string.share_to), ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt index 033730d79..a158ad71c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/connections/Connections.kt @@ -96,6 +96,7 @@ import com.geeksville.mesh.model.NO_DEVICE_SELECTED import com.geeksville.mesh.model.Node import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.navigation.ConfigRoute +import com.geeksville.mesh.navigation.RadioConfigRoutes import com.geeksville.mesh.navigation.Route import com.geeksville.mesh.navigation.getNavRouteFrom import com.geeksville.mesh.repository.network.NetworkRepository @@ -158,7 +159,9 @@ fun ConnectionsScreen( getNavRouteFrom(radioConfigState.route)?.let { route -> isWaiting = false radioConfigViewModel.clearPacketResponse() - onConfigNavigate(route) + if (route == RadioConfigRoutes.LoRa) { + onConfigNavigate(RadioConfigRoutes.LoRa) + } } }, ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt index d5c5e3930..7b8b51611 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/contact/Contacts.kt @@ -66,7 +66,7 @@ import java.util.concurrent.TimeUnit @Composable fun ContactsScreen( uiViewModel: UIViewModel = hiltViewModel(), - onNavigate: (String) -> Unit = {} + onNavigateToMessages: (String) -> Unit = {} ) { var showMuteDialog by remember { mutableStateOf(false) } var showDeleteDialog by remember { mutableStateOf(false) } @@ -96,7 +96,7 @@ fun ContactsScreen( } } else { // If not in selection mode, navigate to messages - onNavigate(contact.contactKey) + onNavigateToMessages(contact.contactKey) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt index 80fc0742a..8a6e85fd8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/node/NodeDetail.kt @@ -119,6 +119,8 @@ import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.Node import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.isUnmessageableRole +import com.geeksville.mesh.navigation.NodeDetailRoutes +import com.geeksville.mesh.navigation.RadioConfigRoutes import com.geeksville.mesh.navigation.Route import com.geeksville.mesh.service.ServiceAction import com.geeksville.mesh.ui.common.components.PreferenceCategory @@ -143,14 +145,14 @@ private enum class LogsType( val icon: ImageVector, val route: Route ) { - DEVICE(R.string.device_metrics_log, Icons.Default.ChargingStation, Route.DeviceMetrics), - NODE_MAP(R.string.node_map, Icons.Default.Map, Route.NodeMap), - POSITIONS(R.string.position_log, Icons.Default.LocationOn, Route.PositionLog), - ENVIRONMENT(R.string.env_metrics_log, Icons.Default.Thermostat, Route.EnvironmentMetrics), - SIGNAL(R.string.sig_metrics_log, Icons.Default.SignalCellularAlt, Route.SignalMetrics), - POWER(R.string.power_metrics_log, Icons.Default.Power, Route.PowerMetrics), - TRACEROUTE(R.string.traceroute_log, Icons.Default.Route, Route.TracerouteLog), - HOST(R.string.host_metrics_log, Icons.Default.Memory, Route.HostMetricsLog), + DEVICE(R.string.device_metrics_log, Icons.Default.ChargingStation, NodeDetailRoutes.DeviceMetrics), + NODE_MAP(R.string.node_map, Icons.Default.Map, NodeDetailRoutes.NodeMap), + POSITIONS(R.string.position_log, Icons.Default.LocationOn, NodeDetailRoutes.PositionLog), + ENVIRONMENT(R.string.env_metrics_log, Icons.Default.Thermostat, NodeDetailRoutes.EnvironmentMetrics), + SIGNAL(R.string.sig_metrics_log, Icons.Default.SignalCellularAlt, NodeDetailRoutes.SignalMetrics), + POWER(R.string.power_metrics_log, Icons.Default.Power, NodeDetailRoutes.PowerMetrics), + TRACEROUTE(R.string.traceroute_log, Icons.Default.Route, NodeDetailRoutes.TracerouteLog), + HOST(R.string.host_metrics_log, Icons.Default.Memory, NodeDetailRoutes.HostMetricsLog), } @Suppress("LongMethod") @@ -327,7 +329,7 @@ private fun NodeDetailList( icon = Icons.Default.Settings, enabled = true ) { - onAction(Route.RadioConfig(node.num)) + onAction(RadioConfigRoutes.RadioConfig(node.num)) } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/radioconfig/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/ui/radioconfig/RadioConfigViewModel.kt index e08ff5ccd..f097eb04b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/radioconfig/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/radioconfig/RadioConfigViewModel.kt @@ -21,6 +21,7 @@ import android.app.Application import android.net.Uri import android.os.RemoteException import androidx.annotation.StringRes +import androidx.core.net.toUri import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -44,12 +45,12 @@ 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.repository.datastore.RadioConfigRepository -import com.geeksville.mesh.service.MeshService.ConnectionState import com.geeksville.mesh.navigation.AdminRoute import com.geeksville.mesh.navigation.ConfigRoute import com.geeksville.mesh.navigation.ModuleRoute -import com.geeksville.mesh.navigation.Route +import com.geeksville.mesh.navigation.RadioConfigRoutes +import com.geeksville.mesh.repository.datastore.RadioConfigRepository +import com.geeksville.mesh.service.MeshService.ConnectionState import com.geeksville.mesh.util.UiText import com.google.protobuf.MessageLite import dagger.hilt.android.lifecycle.HiltViewModel @@ -93,7 +94,7 @@ class RadioConfigViewModel @Inject constructor( ) : ViewModel(), Logging { private val meshService: IMeshService? get() = radioConfigRepository.meshService - private val destNum = savedStateHandle.toRoute().destNum + private val destNum = savedStateHandle.toRoute().destNum private val _destNode = MutableStateFlow(null) val destNode: StateFlow get() = _destNode @@ -214,7 +215,7 @@ class RadioConfigViewModel @Inject constructor( } private fun setChannels(channelUrl: String) = viewModelScope.launch { - val new = Uri.parse(channelUrl).toChannelSet() + val new = channelUrl.toUri().toChannelSet() val old = radioConfigRepository.channelSetFlow.firstOrNull() ?: return@launch updateChannels(new.settingsList, old.settingsList) } @@ -568,9 +569,11 @@ class RadioConfigViewModel @Inject constructor( // Stop once we get to the first disabled entry if (response.role != ChannelProtos.Channel.Role.DISABLED) { _radioConfigState.update { state -> - state.copy(channelList = state.channelList.toMutableList().apply { - add(response.index, response.settings) - }) + state.copy( + channelList = state.channelList.toMutableList().apply { + add(response.index, response.settings) + } + ) } incrementCompleted() if (response.index + 1 < maxChannels && route == ConfigRoute.CHANNELS.name) {