kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
refactor: organize navigation (#2042)
rodzic
c757224269
commit
2a05fc072d
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue