refactor: migrate Compose navigation to type-safe args

pull/1397/head^2
andrekir 2024-11-09 06:23:40 -03:00 zatwierdzone przez Andre K
rodzic e72940245c
commit 296f1944b7
4 zmienionych plików z 129 dodań i 84 usunięć

Wyświetl plik

@ -427,7 +427,7 @@ class RadioConfigViewModel @Inject constructor(
ConfigRoute.CHANNELS -> { ConfigRoute.CHANNELS -> {
getChannel(destNum, 0) getChannel(destNum, 0)
getConfig(destNum, ConfigRoute.LORA.configType) getConfig(destNum, ConfigRoute.LORA.type)
// channel editor is synchronous, so we don't use requestIds as total // channel editor is synchronous, so we don't use requestIds as total
setResponseStateTotal(maxChannels + 1) setResponseStateTotal(maxChannels + 1)
} }
@ -438,17 +438,17 @@ class RadioConfigViewModel @Inject constructor(
if (route == ConfigRoute.LORA) { if (route == ConfigRoute.LORA) {
getChannel(destNum, 0) getChannel(destNum, 0)
} }
getConfig(destNum, route.configType) getConfig(destNum, route.type)
} }
is ModuleRoute -> { is ModuleRoute -> {
if (route == ModuleRoute.CANNED_MESSAGE) { if (route == ModuleRoute.CANNED_MESSAGE) {
getCannedMessages(destNum) getCannedMessages(destNum)
} }
if (route == ModuleRoute.EXTERNAL_NOTIFICATION) { if (route == ModuleRoute.EXT_NOTIFICATION) {
getRingtone(destNum) getRingtone(destNum)
} }
getModuleConfig(destNum, route.configType) getModuleConfig(destNum, route.type)
} }
} }
} }

Wyświetl plik

@ -90,6 +90,7 @@ import com.geeksville.mesh.ui.components.config.TelemetryConfigScreen
import com.geeksville.mesh.ui.components.config.UserConfigScreen import com.geeksville.mesh.ui.components.config.UserConfigScreen
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.serialization.Serializable
internal fun FragmentManager.navigateToNavGraph( internal fun FragmentManager.navigateToNavGraph(
destNum: Int? = null, destNum: Int? = null,
@ -115,7 +116,10 @@ class NavGraphFragment : ScreenFragment("NavGraph"), Logging {
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val destNum = arguments?.getInt("destNum") val destNum = arguments?.getInt("destNum")
val startDestination = arguments?.getString("startDestination") ?: "RadioConfig" val startDestination: Any = when (arguments?.getString("startDestination")) {
"NodeDetails" -> Route.NodeDetail(destNum!!)
else -> Route.RadioConfig(destNum)
}
model.setDestNum(destNum) model.setDestNum(destNum)
return ComposeView(requireContext()).apply { return ComposeView(requireContext()).apply {
@ -163,35 +167,73 @@ enum class AdminRoute(@StringRes val title: Int) {
NODEDB_RESET(R.string.nodedb_reset), NODEDB_RESET(R.string.nodedb_reset),
} }
// Config (configType = AdminProtos.AdminMessage.ConfigType) sealed interface Route {
enum class ConfigRoute(val title: String, val icon: ImageVector?, val configType: Int = 0) { @Serializable
USER("User", Icons.Default.Person, 0), data class RadioConfig(val destNum: Int? = null) : Route
CHANNELS("Channels", Icons.AutoMirrored.Default.List, 0), @Serializable data object User : Route
DEVICE("Device", Icons.Default.Router, 0), @Serializable data object Channels : Route
POSITION("Position", Icons.Default.LocationOn, 1), @Serializable data object Device : Route
POWER("Power", Icons.Default.Power, 2), @Serializable data object Position : Route
NETWORK("Network", Icons.Default.Wifi, 3), @Serializable data object Power : Route
DISPLAY("Display", Icons.Default.DisplaySettings, 4), @Serializable data object Network : Route
LORA("LoRa", Icons.Default.CellTower, 5), @Serializable data object Display : Route
BLUETOOTH("Bluetooth", Icons.Default.Bluetooth, 6), @Serializable data object LoRa : Route
SECURITY("Security", Icons.Default.Security, configType = 7), @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) : 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 TracerouteLog : Route
} }
// ModuleConfig (configType = AdminProtos.AdminMessage.ModuleConfigType) // Config (type = AdminProtos.AdminMessage.ConfigType)
enum class ModuleRoute(val title: String, val icon: ImageVector?, val configType: Int = 0) { enum class ConfigRoute(val title: String, val route: Route, val icon: ImageVector?, val type: Int = 0) {
MQTT("MQTT", Icons.Default.Cloud, 0), USER("User", Route.User, Icons.Default.Person, 0),
SERIAL("Serial", Icons.Default.Usb, 1), CHANNELS("Channels", Route.Channels, Icons.AutoMirrored.Default.List, 0),
EXTERNAL_NOTIFICATION("External Notification", Icons.Default.Notifications, 2), DEVICE("Device", Route.Device, Icons.Default.Router, 0),
STORE_FORWARD("Store & Forward", Icons.AutoMirrored.Default.Forward, 3), POSITION("Position", Route.Position, Icons.Default.LocationOn, 1),
RANGE_TEST("Range Test", Icons.Default.Speed, 4), POWER("Power", Route.Power, Icons.Default.Power, 2),
TELEMETRY("Telemetry", Icons.Default.DataUsage, 5), NETWORK("Network", Route.Network, Icons.Default.Wifi, 3),
CANNED_MESSAGE("Canned Message", Icons.AutoMirrored.Default.Message, 6), DISPLAY("Display", Route.Display, Icons.Default.DisplaySettings, 4),
AUDIO("Audio", Icons.AutoMirrored.Default.VolumeUp, 7), LORA("LoRa", Route.LoRa, Icons.Default.CellTower, 5),
REMOTE_HARDWARE("Remote Hardware", Icons.Default.SettingsRemote, 8), BLUETOOTH("Bluetooth", Route.Bluetooth, Icons.Default.Bluetooth, 6),
NEIGHBOR_INFO("Neighbor Info", Icons.Default.People, 9), SECURITY("Security", Route.Security, Icons.Default.Security, type = 7),
AMBIENT_LIGHTING("Ambient Lighting", Icons.Default.LightMode, 10), }
DETECTION_SENSOR("Detection Sensor", Icons.Default.Sensors, 11),
PAXCOUNTER("Paxcounter", Icons.Default.PermScanWifi, 12), // ModuleConfig (type = AdminProtos.AdminMessage.ModuleConfigType)
enum class ModuleRoute(val title: String, val route: Route, val icon: ImageVector?, val type: Int = 0) {
MQTT("MQTT", Route.MQTT, Icons.Default.Cloud, 0),
SERIAL("Serial", Route.Serial, Icons.Default.Usb, 1),
EXT_NOTIFICATION("External Notification", Route.ExtNotification, Icons.Default.Notifications, 2),
STORE_FORWARD("Store & Forward", Route.StoreForward, Icons.AutoMirrored.Default.Forward, 3),
RANGE_TEST("Range Test", Route.RangeTest, Icons.Default.Speed, 4),
TELEMETRY("Telemetry", Route.Telemetry, Icons.Default.DataUsage, 5),
CANNED_MESSAGE("Canned Message", Route.CannedMessage, Icons.AutoMirrored.Default.Message, 6),
AUDIO("Audio", Route.Audio, Icons.AutoMirrored.Default.VolumeUp, 7),
REMOTE_HARDWARE("Remote Hardware", Route.RemoteHardware, Icons.Default.SettingsRemote, 8),
NEIGHBOR_INFO("Neighbor Info", Route.NeighborInfo, Icons.Default.People, 9),
AMBIENT_LIGHTING("Ambient Lighting", Route.AmbientLighting, Icons.Default.LightMode, 10),
DETECTION_SENSOR("Detection Sensor", Route.DetectionSensor, Icons.Default.Sensors, 11),
PAXCOUNTER("Paxcounter", Route.Paxcounter, Icons.Default.PermScanWifi, 12),
} }
/** /**
@ -235,7 +277,7 @@ fun NavGraph(
node: NodeEntity?, node: NodeEntity?,
viewModel: RadioConfigViewModel = hiltViewModel(), viewModel: RadioConfigViewModel = hiltViewModel(),
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
startDestination: String, startDestination: Any,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
NavHost( NavHost(
@ -243,108 +285,108 @@ fun NavGraph(
startDestination = startDestination, startDestination = startDestination,
modifier = modifier, modifier = modifier,
) { ) {
composable("NodeDetails") { composable<Route.NodeDetail> {
NodeDetailScreen( NodeDetailScreen(
node = node, node = node,
) { navController.navigate(route = it) } ) { navController.navigate(route = it) }
} }
composable("DeviceMetrics") { composable<Route.DeviceMetrics> {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") } val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
DeviceMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry)) DeviceMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry))
} }
composable("NodeMap") { composable<Route.NodeMap> {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") } val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
NodeMapScreen(hiltViewModel<MetricsViewModel>(parentEntry)) NodeMapScreen(hiltViewModel<MetricsViewModel>(parentEntry))
} }
composable("PositionLog") { composable<Route.PositionLog> {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") } val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
PositionLogScreen(hiltViewModel<MetricsViewModel>(parentEntry)) PositionLogScreen(hiltViewModel<MetricsViewModel>(parentEntry))
} }
composable("EnvironmentMetrics") { composable<Route.EnvironmentMetrics> {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") } val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
EnvironmentMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry)) EnvironmentMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry))
} }
composable("SignalMetrics") { composable<Route.SignalMetrics> {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") } val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
SignalMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry)) SignalMetricsScreen(hiltViewModel<MetricsViewModel>(parentEntry))
} }
composable("TracerouteList") { composable<Route.TracerouteLog> {
val parentEntry = remember { navController.getBackStackEntry("NodeDetails") } val parentEntry = remember { navController.getBackStackEntry<Route.NodeDetail>() }
TracerouteLogScreen(hiltViewModel<MetricsViewModel>(parentEntry)) TracerouteLogScreen(hiltViewModel<MetricsViewModel>(parentEntry))
} }
composable("RadioConfig") { composable<Route.RadioConfig> {
RadioConfigScreen( RadioConfigScreen(
node = node, node = node,
viewModel = viewModel, viewModel = viewModel,
) { navController.navigate(route = it) } ) { navController.navigate(route = it) }
} }
composable(ConfigRoute.USER.name) { composable<Route.User> {
UserConfigScreen(viewModel) UserConfigScreen(viewModel)
} }
composable(ConfigRoute.CHANNELS.name) { composable<Route.Channels> {
ChannelConfigScreen(viewModel) ChannelConfigScreen(viewModel)
} }
composable(ConfigRoute.DEVICE.name) { composable<Route.Device> {
DeviceConfigScreen(viewModel) DeviceConfigScreen(viewModel)
} }
composable(ConfigRoute.POSITION.name) { composable<Route.Position> {
PositionConfigScreen(viewModel) PositionConfigScreen(viewModel)
} }
composable(ConfigRoute.POWER.name) { composable<Route.Power> {
PowerConfigScreen(viewModel) PowerConfigScreen(viewModel)
} }
composable(ConfigRoute.NETWORK.name) { composable<Route.Network> {
NetworkConfigScreen(viewModel) NetworkConfigScreen(viewModel)
} }
composable(ConfigRoute.DISPLAY.name) { composable<Route.Display> {
DisplayConfigScreen(viewModel) DisplayConfigScreen(viewModel)
} }
composable(ConfigRoute.LORA.name) { composable<Route.LoRa> {
LoRaConfigScreen(viewModel) LoRaConfigScreen(viewModel)
} }
composable(ConfigRoute.BLUETOOTH.name) { composable<Route.Bluetooth> {
BluetoothConfigScreen(viewModel) BluetoothConfigScreen(viewModel)
} }
composable(ConfigRoute.SECURITY.name) { composable<Route.Security> {
SecurityConfigScreen(viewModel) SecurityConfigScreen(viewModel)
} }
composable(ModuleRoute.MQTT.name) { composable<Route.MQTT> {
MQTTConfigScreen(viewModel) MQTTConfigScreen(viewModel)
} }
composable(ModuleRoute.SERIAL.name) { composable<Route.Serial> {
SerialConfigScreen(viewModel) SerialConfigScreen(viewModel)
} }
composable(ModuleRoute.EXTERNAL_NOTIFICATION.name) { composable<Route.ExtNotification> {
ExternalNotificationConfigScreen(viewModel) ExternalNotificationConfigScreen(viewModel)
} }
composable(ModuleRoute.STORE_FORWARD.name) { composable<Route.StoreForward> {
StoreForwardConfigScreen(viewModel) StoreForwardConfigScreen(viewModel)
} }
composable(ModuleRoute.RANGE_TEST.name) { composable<Route.RangeTest> {
RangeTestConfigScreen(viewModel) RangeTestConfigScreen(viewModel)
} }
composable(ModuleRoute.TELEMETRY.name) { composable<Route.Telemetry> {
TelemetryConfigScreen(viewModel) TelemetryConfigScreen(viewModel)
} }
composable(ModuleRoute.CANNED_MESSAGE.name) { composable<Route.CannedMessage> {
CannedMessageConfigScreen(viewModel) CannedMessageConfigScreen(viewModel)
} }
composable(ModuleRoute.AUDIO.name) { composable<Route.Audio> {
AudioConfigScreen(viewModel) AudioConfigScreen(viewModel)
} }
composable(ModuleRoute.REMOTE_HARDWARE.name) { composable<Route.RemoteHardware> {
RemoteHardwareConfigScreen(viewModel) RemoteHardwareConfigScreen(viewModel)
} }
composable(ModuleRoute.NEIGHBOR_INFO.name) { composable<Route.NeighborInfo> {
NeighborInfoConfigScreen(viewModel) NeighborInfoConfigScreen(viewModel)
} }
composable(ModuleRoute.AMBIENT_LIGHTING.name) { composable<Route.AmbientLighting> {
AmbientLightingConfigScreen(viewModel) AmbientLightingConfigScreen(viewModel)
} }
composable(ModuleRoute.DETECTION_SENSOR.name) { composable<Route.DetectionSensor> {
DetectionSensorConfigScreen(viewModel) DetectionSensorConfigScreen(viewModel)
} }
composable(ModuleRoute.PAXCOUNTER.name) { composable<Route.Paxcounter> {
PaxcounterConfigScreen(viewModel) PaxcounterConfigScreen(viewModel)
} }
} }

Wyświetl plik

@ -77,7 +77,7 @@ fun NodeDetailScreen(
node: NodeEntity?, node: NodeEntity?,
viewModel: MetricsViewModel = hiltViewModel(), viewModel: MetricsViewModel = hiltViewModel(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onNavigate: (String) -> Unit, onNavigate: (Any) -> Unit,
) { ) {
val state by viewModel.state.collectAsStateWithLifecycle() val state by viewModel.state.collectAsStateWithLifecycle()
@ -106,7 +106,7 @@ private fun NodeDetailList(
node: NodeEntity, node: NodeEntity,
metricsState: MetricsState, metricsState: MetricsState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onNavigate: (String) -> Unit = {}, onNavigate: (Any) -> Unit = {},
) { ) {
LazyColumn( LazyColumn(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
@ -147,7 +147,7 @@ private fun NodeDetailList(
icon = Icons.Default.Settings, icon = Icons.Default.Settings,
enabled = true enabled = true
) { ) {
onNavigate("RadioConfig") onNavigate(Route.RadioConfig(node.num))
} }
} }
} }
@ -231,13 +231,13 @@ private fun NodeDetailsContent(node: NodeEntity) {
} }
@Composable @Composable
fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) { fun LogNavigationList(state: MetricsState, onNavigate: (Any) -> Unit) {
NavCard( NavCard(
title = stringResource(R.string.device_metrics_log), title = stringResource(R.string.device_metrics_log),
icon = Icons.Default.ChargingStation, icon = Icons.Default.ChargingStation,
enabled = state.hasDeviceMetrics() enabled = state.hasDeviceMetrics()
) { ) {
onNavigate("DeviceMetrics") onNavigate(Route.DeviceMetrics)
} }
NavCard( NavCard(
@ -245,7 +245,7 @@ fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) {
icon = Icons.Default.Map, icon = Icons.Default.Map,
enabled = state.hasPositionLogs() enabled = state.hasPositionLogs()
) { ) {
onNavigate("NodeMap") onNavigate(Route.NodeMap)
} }
NavCard( NavCard(
@ -253,7 +253,7 @@ fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) {
icon = Icons.Default.LocationOn, icon = Icons.Default.LocationOn,
enabled = state.hasPositionLogs() enabled = state.hasPositionLogs()
) { ) {
onNavigate("PositionLog") onNavigate(Route.PositionLog)
} }
NavCard( NavCard(
@ -261,7 +261,7 @@ fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) {
icon = Icons.Default.Thermostat, icon = Icons.Default.Thermostat,
enabled = state.hasEnvironmentMetrics() enabled = state.hasEnvironmentMetrics()
) { ) {
onNavigate("EnvironmentMetrics") onNavigate(Route.EnvironmentMetrics)
} }
NavCard( NavCard(
@ -269,7 +269,7 @@ fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) {
icon = Icons.Default.SignalCellularAlt, icon = Icons.Default.SignalCellularAlt,
enabled = state.hasSignalMetrics() enabled = state.hasSignalMetrics()
) { ) {
onNavigate("SignalMetrics") onNavigate(Route.SignalMetrics)
} }
NavCard( NavCard(
@ -277,7 +277,7 @@ fun LogNavigationList(state: MetricsState, onNavigate: (String) -> Unit) {
icon = Icons.Default.Route, icon = Icons.Default.Route,
enabled = state.hasTracerouteLogs() enabled = state.hasTracerouteLogs()
) { ) {
onNavigate("TracerouteList") onNavigate(Route.TracerouteLog)
} }
} }

Wyświetl plik

@ -54,13 +54,18 @@ import com.geeksville.mesh.ui.components.PreferenceCategory
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
import com.geeksville.mesh.ui.components.config.PacketResponseStateDialog import com.geeksville.mesh.ui.components.config.PacketResponseStateDialog
private fun getNavRouteFrom(routeName: String): Any? {
return ConfigRoute.entries.find { it.name == routeName }?.route
?: ModuleRoute.entries.find { it.name == routeName }?.route
}
@Suppress("LongMethod", "CyclomaticComplexMethod") @Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable @Composable
fun RadioConfigScreen( fun RadioConfigScreen(
node: NodeEntity?, node: NodeEntity?,
viewModel: RadioConfigViewModel = hiltViewModel(), viewModel: RadioConfigViewModel = hiltViewModel(),
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onNavigate: (String) -> Unit = {} onNavigate: (Any) -> Unit = {}
) { ) {
val isLocal = node?.num == viewModel.myNodeNum val isLocal = node?.num == viewModel.myNodeNum
val state by viewModel.radioConfigState.collectAsStateWithLifecycle() val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
@ -74,9 +79,7 @@ fun RadioConfigScreen(
viewModel.clearPacketResponse() viewModel.clearPacketResponse()
}, },
onComplete = { onComplete = {
val route = state.route getNavRouteFrom(state.route)?.let { route ->
if (ConfigRoute.entries.any { it.name == route } ||
ModuleRoute.entries.any { it.name == route }) {
isWaiting = false isWaiting = false
viewModel.clearPacketResponse() viewModel.clearPacketResponse()
onNavigate(route) onNavigate(route)