Decouple settings screens from NavHostController (#3450)

This commit is contained in:
Phil Oliver
2025-10-13 13:52:44 -04:00
committed by GitHub
parent d4a6c8b7cb
commit 5c745bdd90
29 changed files with 383 additions and 519 deletions

View File

@@ -53,8 +53,8 @@ private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
composable(configRoute.route::class) { backStackEntry ->
val parentEntry = remember(backStackEntry) { navController.getBackStackEntry(ChannelsRoutes.ChannelsGraph) }
when (configRoute) {
ConfigRoute.CHANNELS -> ChannelConfigScreen(navController, hiltViewModel(parentEntry))
ConfigRoute.LORA -> LoRaConfigScreen(navController, hiltViewModel(parentEntry))
ConfigRoute.CHANNELS -> ChannelConfigScreen(hiltViewModel(parentEntry), navController::popBackStack)
ConfigRoute.LORA -> LoRaConfigScreen(hiltViewModel(parentEntry), navController::popBackStack)
else -> Unit // Should not happen if ConfigRoute enum is exhaustive for this context
}
}

View File

@@ -63,6 +63,6 @@ private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
composable<SettingsRoutes.LoRa> { backStackEntry ->
val parentEntry =
remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
LoRaConfigScreen(navController = navController, viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry))
LoRaConfigScreen(viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry), navController::popBackStack)
}
}

View File

@@ -9,26 +9,27 @@
<ID>ComposableParamOrder:MapReportingPreference.kt$MapReportingPreference</ID>
<ID>ComposableParamOrder:NodeActionButton.kt$NodeActionButton</ID>
<ID>ComposableParamOrder:WarningDialog.kt$WarningDialog</ID>
<ID>CyclomaticComplexMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>CyclomaticComplexMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>CyclomaticComplexMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>CyclomaticComplexMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>CyclomaticComplexMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
<ID>CyclomaticComplexMethod:SettingsNavigation.kt$@Suppress("LongMethod") fun NavGraphBuilder.settingsGraph(navController: NavHostController)</ID>
<ID>LambdaParameterEventTrailing:NodeActionButton.kt$onClick</ID>
<ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:DetectionSensorConfigItemList.kt$@Composable fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:DisplayConfigItemList.kt$@Composable fun DisplayConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:LoRaConfigItemList.kt$@Composable fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel)</ID>
<ID>LongMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:PowerConfigItemList.kt$@Composable fun PowerConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:DetectionSensorConfigItemList.kt$@Composable fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:DisplayConfigItemList.kt$@Composable fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:LoRaConfigItemList.kt$@Composable fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -&gt; Unit)</ID>
<ID>LongMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:PowerConfigItemList.kt$@Composable fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
<ID>LongMethod:SecurityConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SecurityConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:SerialConfigItemList.kt$@Composable fun SerialConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:StoreForwardConfigItemList.kt$@Composable fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:TelemetryConfigItemList.kt$@Composable fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:UserConfigItemList.kt$@Composable fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel())</ID>
<ID>LongMethod:SecurityConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:SerialConfigItemList.kt$@Composable fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:StoreForwardConfigItemList.kt$@Composable fun StoreForwardConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:TelemetryConfigItemList.kt$@Composable fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:UserConfigItemList.kt$@Composable fun UserConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>MagicNumber:Debug.kt$3</ID>
<ID>MagicNumber:EditChannelDialog.kt$16</ID>
<ID>MagicNumber:EditChannelDialog.kt$32</ID>

View File

@@ -0,0 +1,107 @@
/*
* 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 org.meshtastic.feature.settings.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.CellTower
import androidx.compose.material.icons.filled.DisplaySettings
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Power
import androidx.compose.material.icons.filled.Router
import androidx.compose.material.icons.filled.Security
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.ui.graphics.vector.ImageVector
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.strings.R
import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.MeshProtos.DeviceMetadata
enum class ConfigRoute(@StringRes val title: Int, val route: Route, val icon: ImageVector?, val type: Int = 0) {
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0),
CHANNELS(R.string.channels, SettingsRoutes.ChannelConfig, Icons.AutoMirrored.Default.List, 0),
DEVICE(
R.string.device,
SettingsRoutes.Device,
Icons.Default.Router,
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
),
POSITION(
R.string.position,
SettingsRoutes.Position,
Icons.Default.LocationOn,
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
),
POWER(
R.string.power,
SettingsRoutes.Power,
Icons.Default.Power,
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
),
NETWORK(
R.string.network,
SettingsRoutes.Network,
Icons.Default.Wifi,
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
),
DISPLAY(
R.string.display,
SettingsRoutes.Display,
Icons.Default.DisplaySettings,
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
),
LORA(
R.string.lora,
SettingsRoutes.LoRa,
Icons.Default.CellTower,
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
),
BLUETOOTH(
R.string.bluetooth,
SettingsRoutes.Bluetooth,
Icons.Default.Bluetooth,
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
),
SECURITY(
R.string.security,
SettingsRoutes.Security,
Icons.Default.Security,
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
),
;
companion object {
private fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
when {
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
}
}
val radioConfigRoutes = listOf(LORA, CHANNELS, SECURITY)
fun deviceConfigRoutes(metadata: DeviceMetadata?): List<ConfigRoute> =
filterExcludedFrom(metadata) - radioConfigRoutes
}
}

View File

@@ -0,0 +1,134 @@
/*
* 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 org.meshtastic.feature.settings.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Forward
import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.DataUsage
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.filled.PermScanWifi
import androidx.compose.material.icons.filled.Sensors
import androidx.compose.material.icons.filled.SettingsRemote
import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.Usb
import androidx.compose.ui.graphics.vector.ImageVector
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.strings.R
import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.MeshProtos.DeviceMetadata
enum class ModuleRoute(@StringRes val title: Int, val route: Route, val icon: ImageVector?, val type: Int = 0) {
MQTT(
R.string.mqtt,
SettingsRoutes.MQTT,
Icons.Default.Cloud,
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
),
SERIAL(
R.string.serial,
SettingsRoutes.Serial,
Icons.Default.Usb,
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
),
EXT_NOTIFICATION(
R.string.external_notification,
SettingsRoutes.ExtNotification,
Icons.Default.Notifications,
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
),
STORE_FORWARD(
R.string.store_forward,
SettingsRoutes.StoreForward,
Icons.AutoMirrored.Default.Forward,
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
),
RANGE_TEST(
R.string.range_test,
SettingsRoutes.RangeTest,
Icons.Default.Speed,
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
),
TELEMETRY(
R.string.telemetry,
SettingsRoutes.Telemetry,
Icons.Default.DataUsage,
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
),
CANNED_MESSAGE(
R.string.canned_message,
SettingsRoutes.CannedMessage,
Icons.AutoMirrored.Default.Message,
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
),
AUDIO(
R.string.audio,
SettingsRoutes.Audio,
Icons.AutoMirrored.Default.VolumeUp,
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
),
REMOTE_HARDWARE(
R.string.remote_hardware,
SettingsRoutes.RemoteHardware,
Icons.Default.SettingsRemote,
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
),
NEIGHBOR_INFO(
R.string.neighbor_info,
SettingsRoutes.NeighborInfo,
Icons.Default.People,
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
),
AMBIENT_LIGHTING(
R.string.ambient_lighting,
SettingsRoutes.AmbientLighting,
Icons.Default.LightMode,
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
),
DETECTION_SENSOR(
R.string.detection_sensor,
SettingsRoutes.DetectionSensor,
Icons.Default.Sensors,
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
),
PAXCOUNTER(
R.string.paxcounter,
SettingsRoutes.Paxcounter,
Icons.Default.PermScanWifi,
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
),
;
val bitfield: Int
get() = 1 shl ordinal
companion object {
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ModuleRoute> = entries.filter {
when (metadata) {
null -> true // Include all routes if metadata is null
else -> metadata.excludedModules and it.bitfield == 0
}
}
}
}

View File

@@ -17,39 +17,8 @@
package org.meshtastic.feature.settings.navigation
import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Forward
import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.CellTower
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.DataUsage
import androidx.compose.material.icons.filled.DisplaySettings
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.People
import androidx.compose.material.icons.filled.PermScanWifi
import androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Power
import androidx.compose.material.icons.filled.Router
import androidx.compose.material.icons.filled.Security
import androidx.compose.material.icons.filled.Sensors
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.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
@@ -59,7 +28,6 @@ import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.strings.R
import org.meshtastic.feature.settings.SettingsScreen
import org.meshtastic.feature.settings.debugging.DebugScreen
import org.meshtastic.feature.settings.radio.CleanNodeDatabaseScreen
@@ -87,8 +55,6 @@ import org.meshtastic.feature.settings.radio.component.SerialConfigScreen
import org.meshtastic.feature.settings.radio.component.StoreForwardConfigScreen
import org.meshtastic.feature.settings.radio.component.TelemetryConfigScreen
import org.meshtastic.feature.settings.radio.component.UserConfigScreen
import org.meshtastic.proto.AdminProtos
import org.meshtastic.proto.MeshProtos.DeviceMetadata
fun getNavRouteFrom(routeName: String): Route? =
ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route
@@ -113,6 +79,7 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
navController.navigate(it) { popUpTo(SettingsRoutes.Settings()) { inclusive = false } }
}
}
composable<SettingsRoutes.CleanNodeDb>(
deepLinks =
listOf(
@@ -123,8 +90,80 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
) {
CleanNodeDatabaseScreen()
}
configRoutesScreens(navController)
moduleRoutesScreens(navController)
ConfigRoute.entries.forEach { entry ->
composable(entry.route::class) { backStackEntry ->
val parentEntry =
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
when (entry) {
ConfigRoute.USER -> UserConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.CHANNELS -> ChannelConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.DEVICE -> DeviceConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.POSITION -> PositionConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.POWER -> PowerConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.NETWORK -> NetworkConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.DISPLAY -> DisplayConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.LORA -> LoRaConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.BLUETOOTH -> BluetoothConfigScreen(viewModel, onBack = navController::popBackStack)
ConfigRoute.SECURITY -> SecurityConfigScreen(viewModel, onBack = navController::popBackStack)
}
}
}
ModuleRoute.entries.forEach { entry ->
composable(entry.route::class) { backStackEntry ->
val parentEntry =
remember(backStackEntry) { navController.getBackStackEntry<SettingsRoutes.SettingsGraph>() }
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
when (entry) {
ModuleRoute.MQTT -> MQTTConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.SERIAL -> SerialConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.EXT_NOTIFICATION ->
ExternalNotificationConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.STORE_FORWARD ->
StoreForwardConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.RANGE_TEST -> RangeTestConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.TELEMETRY -> TelemetryConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.CANNED_MESSAGE ->
CannedMessageConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.AUDIO -> AudioConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.REMOTE_HARDWARE ->
RemoteHardwareConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.NEIGHBOR_INFO ->
NeighborInfoConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.AMBIENT_LIGHTING ->
AmbientLightingConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.DETECTION_SENSOR ->
DetectionSensorConfigScreen(viewModel, onBack = navController::popBackStack)
ModuleRoute.PAXCOUNTER -> PaxcounterConfigScreen(viewModel, onBack = navController::popBackStack)
}
}
}
composable<SettingsRoutes.DebugPanel>(
deepLinks =
listOf(navDeepLink<SettingsRoutes.DebugPanel>(basePath = "$DEEP_LINK_BASE_URI/settings/debug_panel")),
@@ -133,397 +172,3 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController) {
}
}
}
fun NavDestination.isConfigRoute(): Boolean =
ConfigRoute.entries.any { hasRoute(it.route::class) } || ModuleRoute.entries.any { hasRoute(it.route::class) }
/**
* 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 [SettingsRoutes.SettingsGraph].
* - 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 (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
) {
composable<R>(
deepLinks =
listOf(
navDeepLink<R>(
basePath = "$DEEP_LINK_BASE_URI/settings/radio/{destNum}/${routeNameString.lowercase()}",
),
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/settings/radio/${routeNameString.lowercase()}"),
),
) { backStackEntry ->
val parentEntry =
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
screenContent(navController, viewModel)
}
}
@Suppress("LongMethod")
private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController) {
ConfigRoute.entries.forEach { entry ->
when (entry.route) {
is SettingsRoutes.User ->
addRadioConfigScreenComposable<SettingsRoutes.User>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.ChannelConfig ->
addRadioConfigScreenComposable<SettingsRoutes.ChannelConfig>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Device ->
addRadioConfigScreenComposable<SettingsRoutes.Device>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Position ->
addRadioConfigScreenComposable<SettingsRoutes.Position>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Power ->
addRadioConfigScreenComposable<SettingsRoutes.Power>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Network ->
addRadioConfigScreenComposable<SettingsRoutes.Network>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Display ->
addRadioConfigScreenComposable<SettingsRoutes.Display>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.LoRa ->
addRadioConfigScreenComposable<SettingsRoutes.LoRa>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Bluetooth ->
addRadioConfigScreenComposable<SettingsRoutes.Bluetooth>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Security ->
addRadioConfigScreenComposable<SettingsRoutes.Security>(
navController,
entry.name,
entry.screenComposable,
)
else -> Unit // Should not happen if ConfigRoute enum is exhaustive for this context
}
}
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun NavGraphBuilder.moduleRoutesScreens(navController: NavHostController) {
ModuleRoute.entries.forEach { entry ->
when (entry.route) {
is SettingsRoutes.MQTT ->
addRadioConfigScreenComposable<SettingsRoutes.MQTT>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Serial ->
addRadioConfigScreenComposable<SettingsRoutes.Serial>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.ExtNotification ->
addRadioConfigScreenComposable<SettingsRoutes.ExtNotification>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.StoreForward ->
addRadioConfigScreenComposable<SettingsRoutes.StoreForward>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.RangeTest ->
addRadioConfigScreenComposable<SettingsRoutes.RangeTest>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Telemetry ->
addRadioConfigScreenComposable<SettingsRoutes.Telemetry>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.CannedMessage ->
addRadioConfigScreenComposable<SettingsRoutes.CannedMessage>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Audio ->
addRadioConfigScreenComposable<SettingsRoutes.Audio>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.RemoteHardware ->
addRadioConfigScreenComposable<SettingsRoutes.RemoteHardware>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.NeighborInfo ->
addRadioConfigScreenComposable<SettingsRoutes.NeighborInfo>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.AmbientLighting ->
addRadioConfigScreenComposable<SettingsRoutes.AmbientLighting>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.DetectionSensor ->
addRadioConfigScreenComposable<SettingsRoutes.DetectionSensor>(
navController,
entry.name,
entry.screenComposable,
)
is SettingsRoutes.Paxcounter ->
addRadioConfigScreenComposable<SettingsRoutes.Paxcounter>(
navController,
entry.name,
entry.screenComposable,
)
else -> Unit // Should not happen if ModuleRoute enum is exhaustive for this context
}
}
}
@Suppress("MagicNumber")
enum class ConfigRoute(
@StringRes val title: Int,
val route: Route,
val icon: ImageVector?,
val type: Int = 0,
val screenComposable: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
) {
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { nc, vm -> UserConfigScreen(nc, vm) }),
CHANNELS(
R.string.channels,
SettingsRoutes.ChannelConfig,
Icons.AutoMirrored.Default.List,
0,
{ nc, vm -> ChannelConfigScreen(nc, vm) },
),
DEVICE(
R.string.device,
SettingsRoutes.Device,
Icons.Default.Router,
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
{ nc, vm -> DeviceConfigScreen(nc, vm) },
),
POSITION(
R.string.position,
SettingsRoutes.Position,
Icons.Default.LocationOn,
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
{ nc, vm -> PositionConfigScreen(nc, vm) },
),
POWER(
R.string.power,
SettingsRoutes.Power,
Icons.Default.Power,
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
{ nc, vm -> PowerConfigScreen(nc, vm) },
),
NETWORK(
R.string.network,
SettingsRoutes.Network,
Icons.Default.Wifi,
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
{ nc, vm -> NetworkConfigScreen(nc, vm) },
),
DISPLAY(
R.string.display,
SettingsRoutes.Display,
Icons.Default.DisplaySettings,
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
{ nc, vm -> DisplayConfigScreen(nc, vm) },
),
LORA(
R.string.lora,
SettingsRoutes.LoRa,
Icons.Default.CellTower,
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
{ nc, vm -> LoRaConfigScreen(nc, vm) },
),
BLUETOOTH(
R.string.bluetooth,
SettingsRoutes.Bluetooth,
Icons.Default.Bluetooth,
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
{ nc, vm -> BluetoothConfigScreen(nc, vm) },
),
SECURITY(
R.string.security,
SettingsRoutes.Security,
Icons.Default.Security,
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
{ nc, vm -> SecurityConfigScreen(nc, vm) },
),
;
companion object {
private fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
when {
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
}
}
val radioConfigRoutes = listOf(LORA, CHANNELS, SECURITY)
fun deviceConfigRoutes(metadata: DeviceMetadata?): List<ConfigRoute> =
filterExcludedFrom(metadata) - radioConfigRoutes
}
}
@Suppress("MagicNumber")
enum class ModuleRoute(
@StringRes val title: Int,
val route: Route,
val icon: ImageVector?,
val type: Int = 0,
val screenComposable: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
) {
MQTT(
R.string.mqtt,
SettingsRoutes.MQTT,
Icons.Default.Cloud,
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
{ nc, vm -> MQTTConfigScreen(nc, vm) },
),
SERIAL(
R.string.serial,
SettingsRoutes.Serial,
Icons.Default.Usb,
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
{ nc, vm -> SerialConfigScreen(nc, vm) },
),
EXT_NOTIFICATION(
R.string.external_notification,
SettingsRoutes.ExtNotification,
Icons.Default.Notifications,
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
{ nc, vm -> ExternalNotificationConfigScreen(nc, vm) },
),
STORE_FORWARD(
R.string.store_forward,
SettingsRoutes.StoreForward,
Icons.AutoMirrored.Default.Forward,
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
{ nc, vm -> StoreForwardConfigScreen(nc, vm) },
),
RANGE_TEST(
R.string.range_test,
SettingsRoutes.RangeTest,
Icons.Default.Speed,
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
{ nc, vm -> RangeTestConfigScreen(nc, vm) },
),
TELEMETRY(
R.string.telemetry,
SettingsRoutes.Telemetry,
Icons.Default.DataUsage,
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
{ nc, vm -> TelemetryConfigScreen(nc, vm) },
),
CANNED_MESSAGE(
R.string.canned_message,
SettingsRoutes.CannedMessage,
Icons.AutoMirrored.Default.Message,
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
{ nc, vm -> CannedMessageConfigScreen(nc, vm) },
),
AUDIO(
R.string.audio,
SettingsRoutes.Audio,
Icons.AutoMirrored.Default.VolumeUp,
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
{ nc, vm -> AudioConfigScreen(nc, vm) },
),
REMOTE_HARDWARE(
R.string.remote_hardware,
SettingsRoutes.RemoteHardware,
Icons.Default.SettingsRemote,
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
{ nc, vm -> RemoteHardwareConfigScreen(nc, vm) },
),
NEIGHBOR_INFO(
R.string.neighbor_info,
SettingsRoutes.NeighborInfo,
Icons.Default.People,
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
{ nc, vm -> NeighborInfoConfigScreen(nc, vm) },
),
AMBIENT_LIGHTING(
R.string.ambient_lighting,
SettingsRoutes.AmbientLighting,
Icons.Default.LightMode,
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
{ nc, vm -> AmbientLightingConfigScreen(nc, vm) },
),
DETECTION_SENSOR(
R.string.detection_sensor,
SettingsRoutes.DetectionSensor,
Icons.Default.Sensors,
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
{ nc, vm -> DetectionSensorConfigScreen(nc, vm) },
),
PAXCOUNTER(
R.string.paxcounter,
SettingsRoutes.Paxcounter,
Icons.Default.PermScanWifi,
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
{ nc, vm -> PaxcounterConfigScreen(nc, vm) },
),
;
val bitfield: Int
get() = 1 shl ordinal
companion object {
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ModuleRoute> = entries.filter {
when (metadata) {
null -> true // Include all routes if metadata is null
else -> metadata.excludedModules and it.bitfield == 0
}
}
}
}

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.EditTextPreference
import org.meshtastic.core.ui.component.SwitchPreference
@@ -36,7 +35,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun AmbientLightingConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val ambientLightingConfig = state.moduleConfig.ambientLighting
val formState = rememberConfigState(initialValue = ambientLightingConfig)
@@ -44,7 +43,7 @@ fun AmbientLightingConfigScreen(navController: NavController, viewModel: RadioCo
RadioConfigScreenList(
title = stringResource(id = R.string.ambient_lighting),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -38,7 +37,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val audioConfig = state.moduleConfig.audio
val formState = rememberConfigState(initialValue = audioConfig)
@@ -46,7 +45,7 @@ fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewMo
RadioConfigScreenList(
title = stringResource(id = R.string.audio),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -38,7 +37,7 @@ import org.meshtastic.proto.config
import org.meshtastic.proto.copy
@Composable
fun BluetoothConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun BluetoothConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val bluetoothConfig = state.radioConfig.bluetooth
val formState = rememberConfigState(initialValue = bluetoothConfig)
@@ -46,7 +45,7 @@ fun BluetoothConfigScreen(navController: NavController, viewModel: RadioConfigVi
RadioConfigScreenList(
title = stringResource(id = R.string.bluetooth),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -32,7 +32,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -44,7 +43,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val cannedMessageConfig = state.moduleConfig.cannedMessage
val messages = state.cannedMessageMessages
@@ -54,7 +53,7 @@ fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConf
RadioConfigScreenList(
title = stringResource(id = R.string.canned_message),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -60,7 +60,6 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
@@ -120,7 +119,7 @@ private fun ChannelCard(
}
@Composable
fun ChannelConfigScreen(navController: NavController, viewModel: RadioConfigViewModel) {
fun ChannelConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
@@ -129,7 +128,7 @@ fun ChannelConfigScreen(navController: NavController, viewModel: RadioConfigView
ChannelSettingsItemList(
title = stringResource(id = R.string.channels),
onBack = { navController.popBackStack() },
onBack = onBack,
settingsList = state.channelList,
loraConfig = state.radioConfig.lora,
maxChannels = viewModel.maxChannels,

View File

@@ -30,7 +30,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -45,7 +44,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val detectionSensorConfig = state.moduleConfig.detectionSensor
val formState = rememberConfigState(initialValue = detectionSensorConfig)
@@ -53,7 +52,7 @@ fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioCo
RadioConfigScreenList(
title = stringResource(id = R.string.detection_sensor),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -48,7 +48,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -92,7 +91,7 @@ private val DeviceConfig.RebroadcastMode.description: Int
}
@Composable
fun DeviceConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val deviceConfig = state.radioConfig.device
val formState = rememberConfigState(initialValue = deviceConfig)
@@ -111,7 +110,7 @@ fun DeviceConfigScreen(navController: NavController, viewModel: RadioConfigViewM
val focusManager = LocalFocusManager.current
RadioConfigScreenList(
title = stringResource(id = R.string.device),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -25,7 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.SwitchPreference
@@ -38,14 +37,14 @@ import org.meshtastic.proto.config
import org.meshtastic.proto.copy
@Composable
fun DisplayConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val displayConfig = state.radioConfig.display
val formState = rememberConfigState(initialValue = displayConfig)
RadioConfigScreenList(
title = stringResource(id = R.string.display),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -33,7 +33,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -47,7 +46,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun ExternalNotificationConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val extNotificationConfig = state.moduleConfig.externalNotification
val ringtone = state.ringtone
@@ -57,7 +56,7 @@ fun ExternalNotificationConfigScreen(navController: NavController, viewModel: Ra
RadioConfigScreenList(
title = stringResource(id = R.string.external_notification),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -28,7 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.ChannelOption
import org.meshtastic.core.model.RegionInfo
@@ -45,7 +44,7 @@ import org.meshtastic.proto.config
import org.meshtastic.proto.copy
@Composable
fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel) {
fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val loraConfig = state.radioConfig.lora
val primarySettings = state.channelList.getOrNull(0) ?: return
@@ -56,7 +55,7 @@ fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewMod
RadioConfigScreenList(
title = stringResource(id = R.string.lora),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -31,7 +31,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.EditPasswordPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -42,7 +41,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun MQTTConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun MQTTConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val destNode by viewModel.destNode.collectAsStateWithLifecycle()
val destNum = destNode?.num
@@ -68,7 +67,7 @@ fun MQTTConfigScreen(navController: NavController, viewModel: RadioConfigViewMod
RadioConfigScreenList(
title = stringResource(id = R.string.mqtt),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected && consentValid,
responseState = state.responseState,

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.EditTextPreference
import org.meshtastic.core.ui.component.SwitchPreference
@@ -36,7 +35,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun NeighborInfoConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun NeighborInfoConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val neighborInfoConfig = state.moduleConfig.neighborInfo
val formState = rememberConfigState(initialValue = neighborInfoConfig)
@@ -44,7 +43,7 @@ fun NeighborInfoConfigScreen(navController: NavController, viewModel: RadioConfi
RadioConfigScreenList(
title = stringResource(id = R.string.neighbor_info),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -40,7 +40,6 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import org.meshtastic.core.strings.R
@@ -61,7 +60,7 @@ private fun ScanErrorDialog(onDismiss: () -> Unit = {}) =
SimpleAlertDialog(title = R.string.error, text = R.string.wifi_qr_code_error, onDismiss = onDismiss)
@Composable
fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val networkConfig = state.radioConfig.network
val formState = rememberConfigState(initialValue = networkConfig)
@@ -101,7 +100,7 @@ fun NetworkConfigScreen(navController: NavController, viewModel: RadioConfigView
RadioConfigScreenList(
title = stringResource(id = R.string.network),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -27,7 +27,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.SignedIntegerEditTextPreference
@@ -40,7 +39,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun PaxcounterConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val paxcounterConfig = state.moduleConfig.paxcounter
val formState = rememberConfigState(initialValue = paxcounterConfig)
@@ -48,7 +47,7 @@ fun PaxcounterConfigScreen(navController: NavController, viewModel: RadioConfigV
RadioConfigScreenList(
title = stringResource(id = R.string.paxcounter),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -39,7 +39,6 @@ import androidx.compose.ui.res.stringResource
import androidx.core.location.LocationCompat
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.launch
@@ -61,7 +60,7 @@ import org.meshtastic.proto.copy
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val coroutineScope = rememberCoroutineScope()
var phoneLocation: Location? by remember { mutableStateOf(null) }
@@ -122,7 +121,7 @@ fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigVie
RadioConfigScreenList(
title = stringResource(id = R.string.position),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -27,7 +27,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -40,7 +39,7 @@ import org.meshtastic.proto.config
import org.meshtastic.proto.copy
@Composable
fun PowerConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val powerConfig = state.radioConfig.power
val formState = rememberConfigState(initialValue = powerConfig)
@@ -48,7 +47,7 @@ fun PowerConfigScreen(navController: NavController, viewModel: RadioConfigViewMo
RadioConfigScreenList(
title = stringResource(id = R.string.power),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -25,7 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.SwitchPreference
@@ -37,14 +36,14 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun RangeTestConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val rangeTestConfig = state.moduleConfig.rangeTest
val formState = rememberConfigState(initialValue = rangeTestConfig)
RadioConfigScreenList(
title = stringResource(id = R.string.range_test),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.EditListPreference
import org.meshtastic.core.ui.component.SwitchPreference
@@ -36,7 +35,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun RemoteHardwareConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun RemoteHardwareConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val remoteHardwareConfig = state.moduleConfig.remoteHardware
val formState = rememberConfigState(initialValue = remoteHardwareConfig)
@@ -44,7 +43,7 @@ fun RemoteHardwareConfigScreen(navController: NavController, viewModel: RadioCon
RadioConfigScreenList(
title = stringResource(id = R.string.remote_hardware),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -43,7 +43,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.google.protobuf.ByteString
import org.meshtastic.core.model.util.encodeToString
import org.meshtastic.core.model.util.toByteString
@@ -61,7 +60,7 @@ import java.security.SecureRandom
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SecurityConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val node by viewModel.destNode.collectAsStateWithLifecycle()
val securityConfig = state.radioConfig.security
@@ -125,7 +124,7 @@ fun SecurityConfigScreen(navController: NavController, viewModel: RadioConfigVie
val focusManager = LocalFocusManager.current
RadioConfigScreenList(
title = stringResource(id = R.string.security),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.EditTextPreference
@@ -38,7 +37,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun SerialConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val serialConfig = state.moduleConfig.serial
val formState = rememberConfigState(initialValue = serialConfig)
@@ -46,7 +45,7 @@ fun SerialConfigScreen(navController: NavController, viewModel: RadioConfigViewM
RadioConfigScreenList(
title = stringResource(id = R.string.serial),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -26,7 +26,6 @@ import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.EditTextPreference
import org.meshtastic.core.ui.component.SwitchPreference
@@ -36,7 +35,7 @@ import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
@Composable
fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun StoreForwardConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val storeForwardConfig = state.moduleConfig.storeForward
val formState = rememberConfigState(initialValue = storeForwardConfig)
@@ -44,7 +43,7 @@ fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfi
RadioConfigScreenList(
title = stringResource(id = R.string.store_forward),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -25,7 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.DropDownPreference
@@ -40,7 +39,7 @@ import org.meshtastic.proto.moduleConfig
private const val MIN_FW_FOR_TELEMETRY_TOGGLE = "2.7.12"
@Composable
fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val telemetryConfig = state.moduleConfig.telemetry
val formState = rememberConfigState(initialValue = telemetryConfig)
@@ -49,7 +48,7 @@ fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigVi
RadioConfigScreenList(
title = stringResource(id = R.string.telemetry),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected,
responseState = state.responseState,

View File

@@ -29,7 +29,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.meshtastic.core.database.model.isUnmessageableRole
import org.meshtastic.core.model.DeviceVersion
import org.meshtastic.core.strings.R
@@ -41,7 +40,7 @@ import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.proto.copy
@Composable
fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
fun UserConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val userConfig = state.userConfig
val formState = rememberConfigState(initialValue = userConfig)
@@ -54,7 +53,7 @@ fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewMod
RadioConfigScreenList(
title = stringResource(id = R.string.user),
onBack = { navController.popBackStack() },
onBack = onBack,
configState = formState,
enabled = state.connected && validNames,
responseState = state.responseState,