refactor: organize navigation (#2042)

pull/2045/head
James Rich 2025-06-06 20:45:26 +00:00 zatwierdzone przez GitHub
rodzic c757224269
commit 2a05fc072d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
12 zmienionych plików z 306 dodań i 265 usunięć

Wyświetl plik

@ -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<Route.NodeDetail>().destNum
private val destNum = savedStateHandle.toRoute<NodesRoutes.NodeDetail>().destNum
private fun MeshLog.hasValidTraceroute(): Boolean = with(fromRadio.packet) {
hasDecoded() && decoded.wantResponse && from == 0 && to == destNum

Wyświetl plik

@ -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

Wyświetl plik

@ -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<ConnectionsRoutes.ConnectionsGraph>()
}
when (configRoute) {
ConfigRoute.LORA -> LoRaConfigScreen(hiltViewModel(parentEntry))
else -> Unit
}
composable<RadioConfigRoutes.LoRa> { backStackEntry ->
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry<ConnectionsRoutes.ConnectionsGraph>()
}
LoRaConfigScreen(hiltViewModel(parentEntry))
}
}

Wyświetl plik

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ContactsRoutes.ContactsGraph>(
startDestination = ContactsRoutes.Contacts,
) {
composable<ContactsRoutes.Contacts> {
ContactsScreen(
uiViewModel,
onNavigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) }
)
}
composable<ContactsRoutes.Messages>(
deepLinks = listOf(
navDeepLink {
uriPattern = "$DEEP_LINK_BASE_URI/messages/{contactKey}?message={message}"
action = "android.intent.action.VIEW"
},
)
) { backStackEntry ->
val args = backStackEntry.toRoute<ContactsRoutes.Messages>()
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<ContactsRoutes.Share>(
deepLinks = listOf(
navDeepLink {
uriPattern = "$DEEP_LINK_BASE_URI/share?message={message}"
action = "android.intent.action.VIEW"
}
)
) { backStackEntry ->
val message = backStackEntry.toRoute<ContactsRoutes.Share>().message
ShareScreen(uiViewModel) {
navController.navigate(ContactsRoutes.Messages(it, message)) {
popUpTo<ContactsRoutes.Share> { inclusive = true }
}
}
}
composable<ContactsRoutes.QuickChat> {
QuickChatScreen()
}
}

Wyświetl plik

@ -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<Route.RadioConfig>() ||
this.hasRoute<Route.NodeDetail>() ||
this.hasRoute<RadioConfigRoutes.RadioConfig>() ||
this.hasRoute<NodesRoutes.NodeDetail>() ||
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<Route.Contacts> {
ContactsScreen(
uIViewModel,
onNavigate = { navController.navigate(Route.Messages(it)) }
)
}
composable<Route.Map> {
MapView(uIViewModel)
}
contactsGraph(navController, uIViewModel)
nodesGraph(navController, uIViewModel,)
composable<Route.Map> { MapView(uIViewModel) }
channelsGraph(navController, uIViewModel)
connectionsGraph(navController, uIViewModel)
composable<Route.DebugPanel> {
DebugScreen()
}
composable<Route.Messages>(
deepLinks = listOf(
navDeepLink {
uriPattern = "$DEEP_LINK_BASE_URI/messages/{contactKey}?message={message}"
action = "android.intent.action.VIEW"
},
)
) { backStackEntry ->
val args = backStackEntry.toRoute<Route.Messages>()
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<Route.QuickChat> {
QuickChatScreen()
}
nodesGraph(
navController,
uIViewModel,
)
composable<Route.DebugPanel> { DebugScreen() }
radioConfigGraph(navController, uIViewModel)
composable<Route.Share>(
deepLinks = listOf(
navDeepLink {
uriPattern = "$DEEP_LINK_BASE_URI/share?message={message}"
action = "android.intent.action.VIEW"
}
)
) { backStackEntry ->
val message = backStackEntry.toRoute<Route.Share>().message
ShareScreen(uIViewModel) {
navController.navigate(Route.Messages(it, message)) {
popUpTo<Route.Share> { inclusive = true }
}
}
}
}
}

Wyświetl plik

@ -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<NodesRoutes.NodeDetailGraph>(
startDestination = Route.NodeDetail()
startDestination = NodesRoutes.NodeDetail()
) {
composable<Route.NodeDetail> { backStackEntry ->
composable<NodesRoutes.NodeDetail> { backStackEntry ->
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry<NodesRoutes.NodeDetailGraph>()
}
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),
}

Wyświetl plik

@ -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<Graph.RadioConfigGraph>(
startDestination = Route.RadioConfig(),
navigation<RadioConfigRoutes.RadioConfigGraph>(
startDestination = RadioConfigRoutes.RadioConfig(),
) {
composable<Route.RadioConfig> { backStackEntry ->
composable<RadioConfigRoutes.RadioConfig> { backStackEntry ->
val parentEntry = remember(backStackEntry) {
navController.getBackStackEntry<Graph.RadioConfigGraph>()
navController.getBackStackEntry<RadioConfigRoutes.RadioConfigGraph>()
}
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<Graph.RadioConfigGraph>()
navController.getBackStackEntry<RadioConfigRoutes.RadioConfigGraph>()
}
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<Graph.RadioConfigGraph>()
navController.getBackStackEntry<RadioConfigRoutes.RadioConfigGraph>()
}
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

Wyświetl plik

@ -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<Route.Messages>() == true) {
if (currentDestination?.hasRoute<ContactsRoutes.Messages>() == true) {
return
}
TopAppBar(
@ -288,12 +290,12 @@ private fun MainAppBar(
stringResource(id = R.string.debug_panel),
)
currentDestination.hasRoute<Route.QuickChat>() ->
currentDestination.hasRoute<ContactsRoutes.QuickChat>() ->
Text(
stringResource(id = R.string.quick_chat),
)
currentDestination.hasRoute<Route.Share>() ->
currentDestination.hasRoute<ContactsRoutes.Share>() ->
Text(
stringResource(id = R.string.share_to),
)

Wyświetl plik

@ -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)
}
}
},
)

Wyświetl plik

@ -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)
}
}

Wyświetl plik

@ -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))
}
}
}

Wyświetl plik

@ -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<Route.RadioConfig>().destNum
private val destNum = savedStateHandle.toRoute<RadioConfigRoutes.RadioConfig>().destNum
private val _destNode = MutableStateFlow<Node?>(null)
val destNode: StateFlow<Node?> 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) {