mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(navigation): Add deep links to other screens (#2811)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
7d33365095
commit
3fceb1fae1
8 changed files with 605 additions and 191 deletions
|
|
@ -22,6 +22,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.navigation
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.ChannelConfigScreen
|
||||
|
|
@ -31,48 +32,36 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
sealed class ChannelsRoutes {
|
||||
@Serializable
|
||||
data object ChannelsGraph : Graph
|
||||
@Serializable data object ChannelsGraph : Graph
|
||||
|
||||
@Serializable
|
||||
data object Channels : Route
|
||||
@Serializable data object Channels : Route
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation graph for for the top level ChannelScreen - [ChannelsRoutes.Channels].
|
||||
*/
|
||||
/** Navigation graph for for the top level ChannelScreen - [ChannelsRoutes.Channels]. */
|
||||
fun NavGraphBuilder.channelsGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<ChannelsRoutes.ChannelsGraph>(
|
||||
startDestination = ChannelsRoutes.Channels,
|
||||
) {
|
||||
composable<ChannelsRoutes.Channels> { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
navigation<ChannelsRoutes.ChannelsGraph>(startDestination = ChannelsRoutes.Channels) {
|
||||
composable<ChannelsRoutes.Channels>(
|
||||
deepLinks = listOf(navDeepLink<ChannelsRoutes.Channels>(basePath = "$DEEP_LINK_BASE_URI/channels")),
|
||||
) { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(ChannelsRoutes.ChannelsGraph) }
|
||||
ChannelScreen(
|
||||
viewModel = uiViewModel,
|
||||
radioConfigViewModel = hiltViewModel(parentEntry),
|
||||
onNavigate = { route -> navController.navigate(route) }
|
||||
onNavigate = { route -> navController.navigate(route) },
|
||||
)
|
||||
}
|
||||
configRoutes(navController)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavGraphBuilder.configRoutes(
|
||||
navController: NavHostController,
|
||||
) {
|
||||
private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
|
||||
ConfigRoute.entries.forEach { configRoute ->
|
||||
composable(configRoute.route::class) { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(ChannelsRoutes.ChannelsGraph) }
|
||||
when (configRoute) {
|
||||
ConfigRoute.CHANNELS -> ChannelConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.LORA -> LoRaConfigScreen(hiltViewModel(parentEntry))
|
||||
else -> Unit
|
||||
else -> Unit // Should not happen if ConfigRoute enum is exhaustive for this context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,57 +32,43 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
@Serializable
|
||||
sealed class ConnectionsRoutes {
|
||||
@Serializable
|
||||
data object ConnectionsGraph : Graph
|
||||
@Serializable data object ConnectionsGraph : Graph
|
||||
|
||||
@Serializable
|
||||
data object Connections : Route
|
||||
@Serializable data object Connections : Route
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections].
|
||||
*/
|
||||
/** Navigation graph for for the top level ConnectionsScreen - [ConnectionsRoutes.Connections]. */
|
||||
fun NavGraphBuilder.connectionsGraph(
|
||||
navController: NavHostController,
|
||||
uiViewModel: UIViewModel,
|
||||
bluetoothViewModel: BluetoothViewModel
|
||||
bluetoothViewModel: BluetoothViewModel,
|
||||
) {
|
||||
navigation<ConnectionsRoutes.ConnectionsGraph>(
|
||||
startDestination = ConnectionsRoutes.Connections,
|
||||
) {
|
||||
@Suppress("ktlint:standard:max-line-length")
|
||||
navigation<ConnectionsRoutes.ConnectionsGraph>(startDestination = ConnectionsRoutes.Connections) {
|
||||
composable<ConnectionsRoutes.Connections>(
|
||||
deepLinks = listOf(
|
||||
navDeepLink {
|
||||
uriPattern = "$DEEP_LINK_BASE_URI/connections"
|
||||
action = "android.intent.action.VIEW"
|
||||
}
|
||||
)
|
||||
navDeepLink<ConnectionsRoutes.Connections>(basePath = "$DEEP_LINK_BASE_URI/connections"),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
|
||||
ConnectionsScreen(
|
||||
uiViewModel = uiViewModel,
|
||||
bluetoothViewModel = bluetoothViewModel,
|
||||
radioConfigViewModel = hiltViewModel(parentEntry),
|
||||
onNavigateToRadioConfig = { navController.navigate(RadioConfigRoutes.RadioConfig()) },
|
||||
onNavigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) },
|
||||
onConfigNavigate = { route -> navController.navigate(route) }
|
||||
onConfigNavigate = { route -> navController.navigate(route) },
|
||||
)
|
||||
}
|
||||
configRoutes(navController)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavGraphBuilder.configRoutes(
|
||||
navController: NavHostController,
|
||||
) {
|
||||
private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
|
||||
composable<RadioConfigRoutes.LoRa> { backStackEntry ->
|
||||
val parentEntry = remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
|
||||
LoRaConfigScreen(hiltViewModel(parentEntry))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,12 @@ sealed class ContactsRoutes {
|
|||
@Serializable data object ContactsGraph : Graph
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun NavGraphBuilder.contactsGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<ContactsRoutes.ContactsGraph>(startDestination = ContactsRoutes.Contacts) {
|
||||
composable<ContactsRoutes.Contacts> {
|
||||
composable<ContactsRoutes.Contacts>(
|
||||
deepLinks = listOf(navDeepLink<ContactsRoutes.Contacts>(basePath = "$DEEP_LINK_BASE_URI/contacts")),
|
||||
) {
|
||||
ContactsScreen(
|
||||
uiViewModel,
|
||||
onNavigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) },
|
||||
|
|
@ -55,10 +58,10 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, uiViewModel:
|
|||
composable<ContactsRoutes.Messages>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink {
|
||||
uriPattern = "$DEEP_LINK_BASE_URI/messages/{contactKey}?message={message}"
|
||||
action = "android.intent.action.VIEW"
|
||||
},
|
||||
navDeepLink<ContactsRoutes.Messages>(
|
||||
basePath =
|
||||
"$DEEP_LINK_BASE_URI/messages", // {contactKey} and ?message={message} are auto-appended
|
||||
),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val args = backStackEntry.toRoute<ContactsRoutes.Messages>()
|
||||
|
|
@ -76,10 +79,9 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, uiViewModel:
|
|||
composable<ContactsRoutes.Share>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink {
|
||||
uriPattern = "$DEEP_LINK_BASE_URI/share?message={message}"
|
||||
action = "android.intent.action.VIEW"
|
||||
},
|
||||
navDeepLink<ContactsRoutes.Share>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/share", // ?message={message} is auto-appended
|
||||
),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val message = backStackEntry.toRoute<ContactsRoutes.Share>().message
|
||||
|
|
@ -89,5 +91,9 @@ fun NavGraphBuilder.contactsGraph(navController: NavHostController, uiViewModel:
|
|||
}
|
||||
}
|
||||
}
|
||||
composable<ContactsRoutes.QuickChat> { QuickChatScreen() }
|
||||
composable<ContactsRoutes.QuickChat>(
|
||||
deepLinks = listOf(navDeepLink<ContactsRoutes.QuickChat>(basePath = "$DEEP_LINK_BASE_URI/quick_chat")),
|
||||
) {
|
||||
QuickChatScreen()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package com.geeksville.mesh.navigation
|
|||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.map.MapView
|
||||
import com.geeksville.mesh.ui.map.MapViewModel
|
||||
|
|
@ -30,7 +31,7 @@ sealed class MapRoutes {
|
|||
}
|
||||
|
||||
fun NavGraphBuilder.mapGraph(navController: NavHostController, uiViewModel: UIViewModel, mapViewModel: MapViewModel) {
|
||||
composable<MapRoutes.Map> {
|
||||
composable<MapRoutes.Map>(deepLinks = listOf(navDeepLink<MapRoutes.Map>(basePath = "$DEEP_LINK_BASE_URI/map"))) {
|
||||
MapView(
|
||||
uiViewModel = uiViewModel,
|
||||
mapViewModel = mapViewModel,
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.BluetoothViewModel
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
|
|
@ -79,7 +80,11 @@ fun NavGraph(
|
|||
mapGraph(navController, uIViewModel, mapViewModel)
|
||||
channelsGraph(navController, uIViewModel)
|
||||
connectionsGraph(navController, uIViewModel, bluetoothViewModel)
|
||||
composable<Route.DebugPanel> { DebugScreen() }
|
||||
composable<Route.DebugPanel>(
|
||||
deepLinks = listOf(navDeepLink<Route.DebugPanel>(basePath = "$DEEP_LINK_BASE_URI/debug_panel")),
|
||||
) {
|
||||
DebugScreen()
|
||||
}
|
||||
radioConfigGraph(navController, uIViewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,18 @@ import androidx.compose.material.icons.filled.People
|
|||
import androidx.compose.material.icons.filled.PermScanWifi
|
||||
import androidx.compose.material.icons.filled.Power
|
||||
import androidx.compose.material.icons.filled.Router
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navigation
|
||||
import androidx.navigation.navDeepLink
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.metrics.DeviceMetricsScreen
|
||||
import com.geeksville.mesh.ui.metrics.EnvironmentMetricsScreen
|
||||
|
|
@ -60,7 +64,6 @@ sealed class NodesRoutes {
|
|||
}
|
||||
|
||||
sealed class NodeDetailRoutes {
|
||||
|
||||
@Serializable data object DeviceMetrics : Route
|
||||
|
||||
@Serializable data object NodeMap : Route
|
||||
|
|
@ -82,7 +85,9 @@ sealed class NodeDetailRoutes {
|
|||
|
||||
fun NavGraphBuilder.nodesGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<NodesRoutes.NodesGraph>(startDestination = NodesRoutes.Nodes) {
|
||||
composable<NodesRoutes.Nodes> {
|
||||
composable<NodesRoutes.Nodes>(
|
||||
deepLinks = listOf(navDeepLink<NodesRoutes.Nodes>(basePath = "$DEEP_LINK_BASE_URI/nodes")),
|
||||
) {
|
||||
NodeScreen(
|
||||
model = uiViewModel,
|
||||
navigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) },
|
||||
|
|
@ -93,14 +98,19 @@ fun NavGraphBuilder.nodesGraph(navController: NavHostController, uiViewModel: UI
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<NodesRoutes.NodeDetailGraph>(startDestination = NodesRoutes.NodeDetail()) {
|
||||
composable<NodesRoutes.NodeDetail> { backStackEntry ->
|
||||
composable<NodesRoutes.NodeDetail>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<NodesRoutes.NodeDetail>( // Handles both /node and /node/{destNum} due to destNum: Int?
|
||||
basePath = "$DEEP_LINK_BASE_URI/node",
|
||||
),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
NodeDetailScreen(
|
||||
uiViewModel = uiViewModel,
|
||||
navigateToMessages = { navController.navigate(ContactsRoutes.Messages(it)) },
|
||||
|
|
@ -109,39 +119,171 @@ fun NavGraphBuilder.nodeDetailGraph(navController: NavHostController, uiViewMode
|
|||
viewModel = hiltViewModel(parentEntry),
|
||||
)
|
||||
}
|
||||
NodeDetailRoute.entries.forEach { nodeDetailRoute ->
|
||||
composable(nodeDetailRoute.route::class) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
when (nodeDetailRoute) {
|
||||
NodeDetailRoute.DEVICE -> DeviceMetricsScreen(hiltViewModel(parentEntry))
|
||||
NodeDetailRoute.NODE_MAP -> NodeMapScreen(uiViewModel, hiltViewModel(parentEntry))
|
||||
NodeDetailRoute.POSITION_LOG -> PositionLogScreen(hiltViewModel(parentEntry))
|
||||
NodeDetailRoute.ENVIRONMENT -> EnvironmentMetricsScreen(hiltViewModel(parentEntry))
|
||||
|
||||
NodeDetailRoute.SIGNAL -> SignalMetricsScreen(hiltViewModel(parentEntry))
|
||||
NodeDetailRoute.TRACEROUTE -> TracerouteLogScreen(viewModel = hiltViewModel(parentEntry))
|
||||
|
||||
NodeDetailRoute.POWER -> PowerMetricsScreen(hiltViewModel(parentEntry))
|
||||
NodeDetailRoute.HOST -> HostMetricsLogScreen(hiltViewModel(parentEntry))
|
||||
NodeDetailRoute.PAX -> PaxMetricsScreen(hiltViewModel(parentEntry))
|
||||
}
|
||||
NodeDetailRoute.entries.forEach { entry ->
|
||||
when (entry.route) {
|
||||
is NodeDetailRoutes.DeviceMetrics ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.DeviceMetrics>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.NodeMap ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.NodeMap>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.PositionLog ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.PositionLog>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.EnvironmentMetrics ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.EnvironmentMetrics>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.SignalMetrics ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.SignalMetrics>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.PowerMetrics ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.PowerMetrics>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.TracerouteLog ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.TracerouteLog>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.HostMetricsLog ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.HostMetricsLog>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is NodeDetailRoutes.PaxMetrics ->
|
||||
addNodeDetailScreenComposable<NodeDetailRoutes.PaxMetrics>(
|
||||
navController,
|
||||
uiViewModel,
|
||||
entry,
|
||||
entry.screenComposable,
|
||||
)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class NodeDetailRoute(@StringRes val title: Int, val route: Route, val icon: ImageVector?) {
|
||||
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),
|
||||
PAX(R.string.pax, NodeDetailRoutes.PaxMetrics, Icons.Default.People),
|
||||
/**
|
||||
* Helper to define a composable route for a screen within the node detail graph.
|
||||
*
|
||||
* This function simplifies adding screens by handling common tasks like:
|
||||
* - Setting up deep links based on the [NodeDetailRoute] definition.
|
||||
* - Retrieving the parent [NavBackStackEntry] for the [NodesRoutes.NodeDetailGraph].
|
||||
* - Providing the [MetricsViewModel] scoped to the parent graph.
|
||||
*
|
||||
* @param R The type of the [Route] object, must be serializable.
|
||||
* @param navController The [NavHostController] for navigation.
|
||||
* @param uiViewModel The shared [UIViewModel], passed to the [screenContent].
|
||||
* @param routeInfo The [NodeDetailRoute] enum entry that defines the path and metadata for this route.
|
||||
* @param screenContent A lambda that defines the composable content for the screen. It receives the shared
|
||||
* [MetricsViewModel] and the [UIViewModel].
|
||||
*/
|
||||
private inline fun <reified R : Route> NavGraphBuilder.addNodeDetailScreenComposable(
|
||||
navController: NavHostController,
|
||||
uiViewModel: UIViewModel,
|
||||
routeInfo: NodeDetailRoute,
|
||||
crossinline screenContent: @Composable (metricsViewModel: MetricsViewModel, passedUiViewModel: UIViewModel) -> Unit,
|
||||
) {
|
||||
composable<R>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/node/{destNum}/${routeInfo.name.lowercase()}"),
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/node/${routeInfo.name.lowercase()}"),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentGraphBackStackEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(NodesRoutes.NodeDetailGraph::class) }
|
||||
val metricsViewModel = hiltViewModel<MetricsViewModel>(parentGraphBackStackEntry)
|
||||
screenContent(metricsViewModel, uiViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
enum class NodeDetailRoute(
|
||||
@StringRes val title: Int,
|
||||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val screenComposable: @Composable (metricsViewModel: MetricsViewModel, uiViewModel: UIViewModel) -> Unit,
|
||||
) {
|
||||
DEVICE(
|
||||
R.string.device,
|
||||
NodeDetailRoutes.DeviceMetrics,
|
||||
Icons.Default.Router,
|
||||
{ metricsVM, _ -> DeviceMetricsScreen(metricsVM) },
|
||||
),
|
||||
NODE_MAP(
|
||||
R.string.node_map,
|
||||
NodeDetailRoutes.NodeMap,
|
||||
Icons.Default.LocationOn,
|
||||
{ metricsVM, uiVM -> NodeMapScreen(uiVM, metricsVM) },
|
||||
),
|
||||
POSITION_LOG(
|
||||
R.string.position_log,
|
||||
NodeDetailRoutes.PositionLog,
|
||||
Icons.Default.LocationOn,
|
||||
{ metricsVM, _ -> PositionLogScreen(metricsVM) },
|
||||
),
|
||||
ENVIRONMENT(
|
||||
R.string.environment,
|
||||
NodeDetailRoutes.EnvironmentMetrics,
|
||||
Icons.Default.LightMode,
|
||||
{ metricsVM, _ -> EnvironmentMetricsScreen(metricsVM) },
|
||||
),
|
||||
SIGNAL(
|
||||
R.string.signal,
|
||||
NodeDetailRoutes.SignalMetrics,
|
||||
Icons.Default.CellTower,
|
||||
{ metricsVM, _ -> SignalMetricsScreen(metricsVM) },
|
||||
),
|
||||
TRACEROUTE(
|
||||
R.string.traceroute,
|
||||
NodeDetailRoutes.TracerouteLog,
|
||||
Icons.Default.PermScanWifi,
|
||||
{ metricsVM, _ -> TracerouteLogScreen(viewModel = metricsVM) },
|
||||
),
|
||||
POWER(
|
||||
R.string.power,
|
||||
NodeDetailRoutes.PowerMetrics,
|
||||
Icons.Default.Power,
|
||||
{ metricsVM, _ -> PowerMetricsScreen(metricsVM) },
|
||||
),
|
||||
HOST(
|
||||
R.string.host,
|
||||
NodeDetailRoutes.HostMetricsLog,
|
||||
Icons.Default.Memory,
|
||||
{ metricsVM, _ -> HostMetricsLogScreen(metricsVM) },
|
||||
),
|
||||
PAX(
|
||||
R.string.pax,
|
||||
NodeDetailRoutes.PaxMetrics,
|
||||
Icons.Default.People,
|
||||
{ metricsVM, _ -> PaxMetricsScreen(metricsVM) },
|
||||
),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,18 +42,23 @@ import androidx.compose.material.icons.filled.SettingsRemote
|
|||
import androidx.compose.material.icons.filled.Speed
|
||||
import androidx.compose.material.icons.filled.Usb
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.navigation
|
||||
import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.MeshProtos.DeviceMetadata
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.CleanNodeDatabaseScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.AmbientLightingConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.AudioConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.BluetoothConfigScreen
|
||||
|
|
@ -136,100 +141,301 @@ sealed class RadioConfigRoutes {
|
|||
fun getNavRouteFrom(routeName: String): Route? =
|
||||
ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun NavGraphBuilder.radioConfigGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<RadioConfigRoutes.RadioConfigGraph>(startDestination = RadioConfigRoutes.RadioConfig()) {
|
||||
composable<RadioConfigRoutes.RadioConfig> { backStackEntry ->
|
||||
composable<RadioConfigRoutes.RadioConfig>(
|
||||
deepLinks =
|
||||
listOf(navDeepLink<RadioConfigRoutes.RadioConfig>(basePath = "$DEEP_LINK_BASE_URI/radio_config")),
|
||||
) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
remember(backStackEntry) { navController.getBackStackEntry(RadioConfigRoutes.RadioConfigGraph::class) }
|
||||
RadioConfigScreen(uiViewModel = uiViewModel, viewModel = hiltViewModel(parentEntry)) {
|
||||
navController.navigate(it) { popUpTo(RadioConfigRoutes.RadioConfig()) { inclusive = false } }
|
||||
}
|
||||
}
|
||||
composable<RadioConfigRoutes.CleanNodeDb> { CleanNodeDatabaseScreen() }
|
||||
configRoutes(navController)
|
||||
moduleRoutes(navController)
|
||||
composable<RadioConfigRoutes.CleanNodeDb>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<RadioConfigRoutes.CleanNodeDb>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/radio_config/clean_node_db",
|
||||
),
|
||||
),
|
||||
) {
|
||||
CleanNodeDatabaseScreen()
|
||||
}
|
||||
configRoutesScreens(navController)
|
||||
moduleRoutesScreens(navController)
|
||||
}
|
||||
}
|
||||
|
||||
private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
|
||||
ConfigRoute.entries.forEach { configRoute ->
|
||||
composable(configRoute.route::class) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
when (configRoute) {
|
||||
ConfigRoute.USER -> UserConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.CHANNELS -> ChannelConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.DEVICE -> DeviceConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.POSITION -> PositionConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.POWER -> PowerConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.NETWORK -> NetworkConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.DISPLAY -> DisplayConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.LORA -> LoRaConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.BLUETOOTH -> BluetoothConfigScreen(hiltViewModel(parentEntry))
|
||||
ConfigRoute.SECURITY -> SecurityConfigScreen(hiltViewModel(parentEntry))
|
||||
}
|
||||
/**
|
||||
* Helper to define a composable route for a radio configuration screen within the radio config graph.
|
||||
*
|
||||
* This function simplifies adding screens by handling common tasks like:
|
||||
* - Setting up deep links based on the route's name.
|
||||
* - Retrieving the parent [NavBackStackEntry] for the [RadioConfigRoutes.RadioConfigGraph].
|
||||
* - Providing the [RadioConfigViewModel] scoped to the parent graph, which the [screenContent] will use.
|
||||
*
|
||||
* @param R The type of the [Route] object, must be serializable.
|
||||
* @param navController The [NavHostController] for navigation.
|
||||
* @param routeNameString The string name of the route (from the enum entry's name) used for deep link paths.
|
||||
* @param screenContent A lambda that defines the composable content for the screen. It receives the parent-scoped
|
||||
* [RadioConfigViewModel].
|
||||
*/
|
||||
private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenComposable(
|
||||
navController: NavHostController,
|
||||
routeNameString: String,
|
||||
crossinline screenContent: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
composable<R>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/radio_config/{destNum}/${routeNameString.lowercase()}"),
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/radio_config/${routeNameString.lowercase()}"),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(RadioConfigRoutes.RadioConfigGraph::class) }
|
||||
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
|
||||
screenContent(viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController) {
|
||||
ConfigRoute.entries.forEach { entry ->
|
||||
when (entry.route) {
|
||||
is RadioConfigRoutes.User ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.User>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.ChannelConfig ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.ChannelConfig>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Device ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Device>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Position ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Position>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Power ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Power>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Network ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Network>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Display ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Display>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.LoRa ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.LoRa>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Bluetooth ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Bluetooth>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Security ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Security>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
else -> Unit // Should not happen if ConfigRoute enum is exhaustive for this context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private fun NavGraphBuilder.moduleRoutes(navController: NavHostController) {
|
||||
ModuleRoute.entries.forEach { moduleRoute ->
|
||||
composable(moduleRoute.route::class) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) {
|
||||
val parentRoute = backStackEntry.destination.parent!!.route!!
|
||||
navController.getBackStackEntry(parentRoute)
|
||||
}
|
||||
when (moduleRoute) {
|
||||
ModuleRoute.MQTT -> MQTTConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.SERIAL -> SerialConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.EXT_NOTIFICATION -> ExternalNotificationConfigScreen(hiltViewModel(parentEntry))
|
||||
|
||||
ModuleRoute.STORE_FORWARD -> StoreForwardConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.RANGE_TEST -> RangeTestConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.TELEMETRY -> TelemetryConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.CANNED_MESSAGE -> CannedMessageConfigScreen(hiltViewModel(parentEntry))
|
||||
|
||||
ModuleRoute.AUDIO -> AudioConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.REMOTE_HARDWARE -> RemoteHardwareConfigScreen(hiltViewModel(parentEntry))
|
||||
|
||||
ModuleRoute.NEIGHBOR_INFO -> NeighborInfoConfigScreen(hiltViewModel(parentEntry))
|
||||
ModuleRoute.AMBIENT_LIGHTING -> AmbientLightingConfigScreen(hiltViewModel(parentEntry))
|
||||
|
||||
ModuleRoute.DETECTION_SENSOR -> DetectionSensorConfigScreen(hiltViewModel(parentEntry))
|
||||
|
||||
ModuleRoute.PAXCOUNTER -> PaxcounterConfigScreen(hiltViewModel(parentEntry))
|
||||
}
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
private fun NavGraphBuilder.moduleRoutesScreens(navController: NavHostController) {
|
||||
ModuleRoute.entries.forEach { entry ->
|
||||
when (entry.route) {
|
||||
is RadioConfigRoutes.MQTT ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.MQTT>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Serial ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Serial>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.ExtNotification ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.ExtNotification>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.StoreForward ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.StoreForward>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.RangeTest ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.RangeTest>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Telemetry ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Telemetry>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.CannedMessage ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.CannedMessage>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Audio ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Audio>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.RemoteHardware ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.RemoteHardware>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.NeighborInfo ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.NeighborInfo>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.AmbientLighting ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.AmbientLighting>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.DetectionSensor ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.DetectionSensor>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Paxcounter ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Paxcounter>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
else -> Unit // Should not happen if ModuleRoute enum is exhaustive for this context
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Config (type = AdminProtos.AdminMessage.ConfigType)
|
||||
@Suppress("MagicNumber")
|
||||
enum class ConfigRoute(@StringRes val title: Int, val route: Route, val icon: ImageVector?, val type: Int = 0) {
|
||||
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),
|
||||
enum class ConfigRoute(
|
||||
@StringRes val title: Int,
|
||||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
USER(R.string.user, RadioConfigRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
|
||||
CHANNELS(
|
||||
R.string.channels,
|
||||
RadioConfigRoutes.ChannelConfig,
|
||||
Icons.AutoMirrored.Default.List,
|
||||
0,
|
||||
{ vm -> ChannelConfigScreen(vm) },
|
||||
),
|
||||
DEVICE(
|
||||
R.string.device,
|
||||
RadioConfigRoutes.Device,
|
||||
Icons.Default.Router,
|
||||
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
|
||||
{ vm -> DeviceConfigScreen(vm) },
|
||||
),
|
||||
POSITION(
|
||||
R.string.position,
|
||||
RadioConfigRoutes.Position,
|
||||
Icons.Default.LocationOn,
|
||||
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
|
||||
{ vm -> PositionConfigScreen(vm) },
|
||||
),
|
||||
POWER(
|
||||
R.string.power,
|
||||
RadioConfigRoutes.Power,
|
||||
Icons.Default.Power,
|
||||
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
|
||||
{ vm -> PowerConfigScreen(vm) },
|
||||
),
|
||||
NETWORK(
|
||||
R.string.network,
|
||||
RadioConfigRoutes.Network,
|
||||
Icons.Default.Wifi,
|
||||
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
|
||||
{ vm -> NetworkConfigScreen(vm) },
|
||||
),
|
||||
DISPLAY(
|
||||
R.string.display,
|
||||
RadioConfigRoutes.Display,
|
||||
Icons.Default.DisplaySettings,
|
||||
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
|
||||
{ vm -> DisplayConfigScreen(vm) },
|
||||
),
|
||||
LORA(
|
||||
R.string.lora,
|
||||
RadioConfigRoutes.LoRa,
|
||||
Icons.Default.CellTower,
|
||||
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
|
||||
{ vm -> LoRaConfigScreen(vm) },
|
||||
),
|
||||
BLUETOOTH(
|
||||
R.string.bluetooth,
|
||||
RadioConfigRoutes.Bluetooth,
|
||||
Icons.Default.Bluetooth,
|
||||
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
|
||||
{ vm -> BluetoothConfigScreen(vm) },
|
||||
),
|
||||
SECURITY(
|
||||
R.string.security,
|
||||
RadioConfigRoutes.Security,
|
||||
Icons.Default.Security,
|
||||
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
|
||||
{ vm -> SecurityConfigScreen(vm) },
|
||||
),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
|
||||
when {
|
||||
metadata == null -> true
|
||||
metadata == null -> true // Include all routes if metadata is null
|
||||
it == BLUETOOTH -> metadata.hasBluetooth
|
||||
it == NETWORK -> metadata.hasWifi || metadata.hasEthernet
|
||||
else -> true // Include all other routes by default
|
||||
|
|
@ -238,22 +444,105 @@ enum class ConfigRoute(@StringRes val title: Int, val route: Route, val icon: Im
|
|||
}
|
||||
}
|
||||
|
||||
// ModuleConfig (type = AdminProtos.AdminMessage.ModuleConfigType)
|
||||
@Suppress("MagicNumber")
|
||||
enum class ModuleRoute(@StringRes val title: Int, val route: Route, val icon: ImageVector?, val type: Int = 0) {
|
||||
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, RadioConfigRoutes.ExtNotification, Icons.Default.Notifications, 2),
|
||||
STORE_FORWARD(R.string.store_forward, RadioConfigRoutes.StoreForward, Icons.AutoMirrored.Default.Forward, 3),
|
||||
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, RadioConfigRoutes.CannedMessage, Icons.AutoMirrored.Default.Message, 6),
|
||||
AUDIO(R.string.audio, RadioConfigRoutes.Audio, Icons.AutoMirrored.Default.VolumeUp, 7),
|
||||
REMOTE_HARDWARE(R.string.remote_hardware, RadioConfigRoutes.RemoteHardware, Icons.Default.SettingsRemote, 8),
|
||||
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),
|
||||
enum class ModuleRoute(
|
||||
@StringRes val title: Int,
|
||||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
MQTT(
|
||||
R.string.mqtt,
|
||||
RadioConfigRoutes.MQTT,
|
||||
Icons.Default.Cloud,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
|
||||
{ vm -> MQTTConfigScreen(vm) },
|
||||
),
|
||||
SERIAL(
|
||||
R.string.serial,
|
||||
RadioConfigRoutes.Serial,
|
||||
Icons.Default.Usb,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
|
||||
{ vm -> SerialConfigScreen(vm) },
|
||||
),
|
||||
EXT_NOTIFICATION(
|
||||
R.string.external_notification,
|
||||
RadioConfigRoutes.ExtNotification,
|
||||
Icons.Default.Notifications,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
|
||||
{ vm -> ExternalNotificationConfigScreen(vm) },
|
||||
),
|
||||
STORE_FORWARD(
|
||||
R.string.store_forward,
|
||||
RadioConfigRoutes.StoreForward,
|
||||
Icons.AutoMirrored.Default.Forward,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
|
||||
{ vm -> StoreForwardConfigScreen(vm) },
|
||||
),
|
||||
RANGE_TEST(
|
||||
R.string.range_test,
|
||||
RadioConfigRoutes.RangeTest,
|
||||
Icons.Default.Speed,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
|
||||
{ vm -> RangeTestConfigScreen(vm) },
|
||||
),
|
||||
TELEMETRY(
|
||||
R.string.telemetry,
|
||||
RadioConfigRoutes.Telemetry,
|
||||
Icons.Default.DataUsage,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
|
||||
{ vm -> TelemetryConfigScreen(vm) },
|
||||
),
|
||||
CANNED_MESSAGE(
|
||||
R.string.canned_message,
|
||||
RadioConfigRoutes.CannedMessage,
|
||||
Icons.AutoMirrored.Default.Message,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
|
||||
{ vm -> CannedMessageConfigScreen(vm) },
|
||||
),
|
||||
AUDIO(
|
||||
R.string.audio,
|
||||
RadioConfigRoutes.Audio,
|
||||
Icons.AutoMirrored.Default.VolumeUp,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
|
||||
{ vm -> AudioConfigScreen(vm) },
|
||||
),
|
||||
REMOTE_HARDWARE(
|
||||
R.string.remote_hardware,
|
||||
RadioConfigRoutes.RemoteHardware,
|
||||
Icons.Default.SettingsRemote,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
|
||||
{ vm -> RemoteHardwareConfigScreen(vm) },
|
||||
),
|
||||
NEIGHBOR_INFO(
|
||||
R.string.neighbor_info,
|
||||
RadioConfigRoutes.NeighborInfo,
|
||||
Icons.Default.People,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
|
||||
{ vm -> NeighborInfoConfigScreen(vm) },
|
||||
),
|
||||
AMBIENT_LIGHTING(
|
||||
R.string.ambient_lighting,
|
||||
RadioConfigRoutes.AmbientLighting,
|
||||
Icons.Default.LightMode,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
|
||||
{ vm -> AmbientLightingConfigScreen(vm) },
|
||||
),
|
||||
DETECTION_SENSOR(
|
||||
R.string.detection_sensor,
|
||||
RadioConfigRoutes.DetectionSensor,
|
||||
Icons.Default.Sensors,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
|
||||
{ vm -> DetectionSensorConfigScreen(vm) },
|
||||
),
|
||||
PAXCOUNTER(
|
||||
R.string.paxcounter,
|
||||
RadioConfigRoutes.Paxcounter,
|
||||
Icons.Default.PermScanWifi,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
|
||||
{ vm -> PaxcounterConfigScreen(vm) },
|
||||
),
|
||||
;
|
||||
|
||||
val bitfield: Int
|
||||
|
|
@ -262,7 +551,7 @@ enum class ModuleRoute(@StringRes val title: Int, val route: Route, val icon: Im
|
|||
companion object {
|
||||
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ModuleRoute> = entries.filter {
|
||||
when (metadata) {
|
||||
null -> true
|
||||
null -> true // Include all routes if metadata is null
|
||||
else -> metadata.excludedModules and it.bitfield == 0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue