Relocate radio config to new top-level Settings screen (#2834)

This commit is contained in:
Phil Oliver 2025-08-25 15:44:32 -04:00 committed by GitHub
parent af6066d788
commit f1bb020203
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 1077 additions and 1189 deletions

View file

@ -27,7 +27,7 @@ import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
import com.geeksville.mesh.R
import com.geeksville.mesh.deviceProfile
import com.geeksville.mesh.position
import com.geeksville.mesh.ui.radioconfig.components.EditDeviceProfileDialog
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
@ -36,11 +36,9 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class EditDeviceProfileDialogTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule val composeTestRule = createComposeRule()
private fun getString(id: Int): String =
InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
private fun getString(id: Int): String = InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
private val title = "Export configuration"
private val deviceProfile = deviceProfile {
@ -54,17 +52,15 @@ class EditDeviceProfileDialogTest {
}
}
private fun testEditDeviceProfileDialog(
onDismiss: () -> Unit = {},
onConfirm: (DeviceProfile) -> Unit = {},
) = composeTestRule.setContent {
EditDeviceProfileDialog(
title = title,
deviceProfile = deviceProfile,
onConfirm = onConfirm,
onDismiss = onDismiss,
)
}
private fun testEditDeviceProfileDialog(onDismiss: () -> Unit = {}, onConfirm: (DeviceProfile) -> Unit = {}) =
composeTestRule.setContent {
EditDeviceProfileDialog(
title = title,
deviceProfile = deviceProfile,
onConfirm = onConfirm,
onDismiss = onDismiss,
)
}
@Test
fun testEditDeviceProfileDialog_showsDialogTitle() {
@ -113,4 +109,4 @@ class EditDeviceProfileDialogTest {
// Verify onConfirm is called with the correct DeviceProfile
Assert.assertEquals(deviceProfile, actualDeviceProfile)
}
}
}

View file

@ -27,7 +27,7 @@ import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.geeksville.mesh.R
import com.geeksville.mesh.ui.radioconfig.components.MapReportingPreference
import com.geeksville.mesh.ui.settings.radio.components.MapReportingPreference
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
@ -36,30 +36,19 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MapReportingPreferenceTest {
@get:Rule
val composeTestRule = createComposeRule()
@get:Rule val composeTestRule = createComposeRule()
private fun getString(id: Int): String =
InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
private fun getString(id: Int): String = InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
var mapReportingEnabled = false
var shouldReportLocation = false
var positionPrecision = 5
var positionReportingInterval = 60
var mapReportingEnabledChanged = { enabled: Boolean ->
mapReportingEnabled = enabled
}
var shouldReportLocationChanged = { enabled: Boolean ->
shouldReportLocation = enabled
}
var positionPrecisionChanged = { precision: Int ->
positionPrecision = precision
}
var positionReportingIntervalChanged = { interval: Int ->
positionReportingInterval = interval
}
var mapReportingEnabledChanged = { enabled: Boolean -> mapReportingEnabled = enabled }
var shouldReportLocationChanged = { enabled: Boolean -> shouldReportLocation = enabled }
var positionPrecisionChanged = { precision: Int -> positionPrecision = precision }
var positionReportingIntervalChanged = { interval: Int -> positionReportingInterval = interval }
private fun testMapReportingPreference() = composeTestRule.setContent {
Column {

View file

@ -25,8 +25,8 @@ 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
import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.ChannelConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
import com.geeksville.mesh.ui.sharing.ChannelScreen
import kotlinx.serialization.Serializable

View file

@ -27,7 +27,7 @@ import androidx.navigation.navigation
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.connections.ConnectionsScreen
import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
import kotlinx.serialization.Serializable
@Serializable
@ -56,7 +56,7 @@ fun NavGraphBuilder.connectionsGraph(
uiViewModel = uiViewModel,
bluetoothViewModel = bluetoothViewModel,
radioConfigViewModel = hiltViewModel(parentEntry),
onNavigateToRadioConfig = { navController.navigate(RadioConfigRoutes.RadioConfig()) },
onNavigateToSettings = { navController.navigate(SettingsRoutes.Settings()) },
onNavigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) },
onConfigNavigate = { route -> navController.navigate(route) },
)
@ -66,7 +66,7 @@ fun NavGraphBuilder.connectionsGraph(
}
private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
composable<RadioConfigRoutes.LoRa> { backStackEntry ->
composable<SettingsRoutes.LoRa> { backStackEntry ->
val parentEntry =
remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
LoRaConfigScreen(hiltViewModel(parentEntry))

View file

@ -59,7 +59,7 @@ fun NavDestination.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any {
fun NavDestination.showLongNameTitle(): Boolean = !this.isTopLevel() &&
(
this.hasRoute<RadioConfigRoutes.RadioConfig>() ||
this.hasRoute<SettingsRoutes.Settings>() ||
this.hasRoute<NodesRoutes.NodeDetail>() ||
this.isConfigRoute() ||
this.isNodeDetailRoute()
@ -85,6 +85,6 @@ fun NavGraph(
) {
DebugScreen()
}
radioConfigGraph(navController, uIViewModel)
settingsGraph(navController, uIViewModel)
}
}

View file

@ -56,38 +56,40 @@ 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
import com.geeksville.mesh.ui.radioconfig.components.CannedMessageConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.ChannelConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.DetectionSensorConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.DeviceConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.DisplayConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.ExternalNotificationConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.MQTTConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.NeighborInfoConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.NetworkConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.PaxcounterConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.PositionConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.PowerConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.RangeTestConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.RemoteHardwareConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.SecurityConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.SerialConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.StoreForwardConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.TelemetryConfigScreen
import com.geeksville.mesh.ui.radioconfig.components.UserConfigScreen
import com.geeksville.mesh.ui.settings.radio.CleanNodeDatabaseScreen
import com.geeksville.mesh.ui.settings.radio.RadioConfigScreen
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.components.AmbientLightingConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.AudioConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.BluetoothConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.CannedMessageConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.ChannelConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.DetectionSensorConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.DeviceConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.DisplayConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.ExternalNotificationConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.MQTTConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.NeighborInfoConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.NetworkConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.PaxcounterConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.PositionConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.PowerConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.RangeTestConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.RemoteHardwareConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.SecurityConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.SerialConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.StoreForwardConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.TelemetryConfigScreen
import com.geeksville.mesh.ui.settings.radio.components.UserConfigScreen
import kotlinx.serialization.Serializable
sealed class RadioConfigRoutes {
@Serializable data class RadioConfigGraph(val destNum: Int? = null) : Graph
sealed class SettingsRoutes {
@Serializable data class SettingsGraph(val destNum: Int? = null) : Graph
@Serializable data class RadioConfig(val destNum: Int? = null) : Route
@Serializable data class Settings(val destNum: Int? = null) : Route
// region radio Config Routes
@Serializable data object User : Route
@ -109,6 +111,10 @@ sealed class RadioConfigRoutes {
@Serializable data object Security : Route
// endregion
// region module config routes
@Serializable data object MQTT : Route
@Serializable data object Serial : Route
@ -135,30 +141,35 @@ sealed class RadioConfigRoutes {
@Serializable data object Paxcounter : Route
// endregion
// region advanced config routes
@Serializable data object CleanNodeDb : Route
// endregion
}
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>(
deepLinks =
listOf(navDeepLink<RadioConfigRoutes.RadioConfig>(basePath = "$DEEP_LINK_BASE_URI/radio_config")),
fun NavGraphBuilder.settingsGraph(navController: NavHostController, uiViewModel: UIViewModel) {
navigation<SettingsRoutes.SettingsGraph>(startDestination = SettingsRoutes.Settings()) {
composable<SettingsRoutes.Settings>(
deepLinks = listOf(navDeepLink<SettingsRoutes.Settings>(basePath = "$DEEP_LINK_BASE_URI/settings")),
) { backStackEntry ->
val parentEntry =
remember(backStackEntry) { navController.getBackStackEntry(RadioConfigRoutes.RadioConfigGraph::class) }
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
RadioConfigScreen(uiViewModel = uiViewModel, viewModel = hiltViewModel(parentEntry)) {
navController.navigate(it) { popUpTo(RadioConfigRoutes.RadioConfig()) { inclusive = false } }
navController.navigate(it) { popUpTo(SettingsRoutes.Settings()) { inclusive = false } }
}
}
composable<RadioConfigRoutes.CleanNodeDb>(
composable<SettingsRoutes.CleanNodeDb>(
deepLinks =
listOf(
navDeepLink<RadioConfigRoutes.CleanNodeDb>(
basePath = "$DEEP_LINK_BASE_URI/radio_config/clean_node_db",
navDeepLink<SettingsRoutes.CleanNodeDb>(
basePath = "$DEEP_LINK_BASE_URI/settings/radio/clean_node_db",
),
),
) {
@ -174,7 +185,7 @@ fun NavGraphBuilder.radioConfigGraph(navController: NavHostController, uiViewMod
*
* 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].
* - 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.
@ -191,12 +202,14 @@ private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenCompo
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()}"),
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(RadioConfigRoutes.RadioConfigGraph::class) }
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
screenContent(viewModel)
}
@ -206,62 +219,46 @@ private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenCompo
private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController) {
ConfigRoute.entries.forEach { entry ->
when (entry.route) {
is RadioConfigRoutes.User ->
addRadioConfigScreenComposable<RadioConfigRoutes.User>(
is SettingsRoutes.User ->
addRadioConfigScreenComposable<SettingsRoutes.User>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.ChannelConfig ->
addRadioConfigScreenComposable<SettingsRoutes.ChannelConfig>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.ChannelConfig ->
addRadioConfigScreenComposable<RadioConfigRoutes.ChannelConfig>(
is SettingsRoutes.Device ->
addRadioConfigScreenComposable<SettingsRoutes.Device>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Position ->
addRadioConfigScreenComposable<SettingsRoutes.Position>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.Device ->
addRadioConfigScreenComposable<RadioConfigRoutes.Device>(
is SettingsRoutes.Power ->
addRadioConfigScreenComposable<SettingsRoutes.Power>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Network ->
addRadioConfigScreenComposable<SettingsRoutes.Network>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.Position ->
addRadioConfigScreenComposable<RadioConfigRoutes.Position>(
is SettingsRoutes.Display ->
addRadioConfigScreenComposable<SettingsRoutes.Display>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.Power ->
addRadioConfigScreenComposable<RadioConfigRoutes.Power>(
is SettingsRoutes.LoRa ->
addRadioConfigScreenComposable<SettingsRoutes.LoRa>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.Bluetooth ->
addRadioConfigScreenComposable<SettingsRoutes.Bluetooth>(
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>(
is SettingsRoutes.Security ->
addRadioConfigScreenComposable<SettingsRoutes.Security>(
navController,
entry.name,
entry.screenComposable,
@ -275,80 +272,68 @@ private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController
private fun NavGraphBuilder.moduleRoutesScreens(navController: NavHostController) {
ModuleRoute.entries.forEach { entry ->
when (entry.route) {
is RadioConfigRoutes.MQTT ->
addRadioConfigScreenComposable<RadioConfigRoutes.MQTT>(
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 RadioConfigRoutes.Serial ->
addRadioConfigScreenComposable<RadioConfigRoutes.Serial>(
is SettingsRoutes.StoreForward ->
addRadioConfigScreenComposable<SettingsRoutes.StoreForward>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.ExtNotification ->
addRadioConfigScreenComposable<RadioConfigRoutes.ExtNotification>(
is SettingsRoutes.RangeTest ->
addRadioConfigScreenComposable<SettingsRoutes.RangeTest>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.StoreForward ->
addRadioConfigScreenComposable<RadioConfigRoutes.StoreForward>(
is SettingsRoutes.Telemetry ->
addRadioConfigScreenComposable<SettingsRoutes.Telemetry>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.RangeTest ->
addRadioConfigScreenComposable<RadioConfigRoutes.RangeTest>(
is SettingsRoutes.CannedMessage ->
addRadioConfigScreenComposable<SettingsRoutes.CannedMessage>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.Telemetry ->
addRadioConfigScreenComposable<RadioConfigRoutes.Telemetry>(
is SettingsRoutes.Audio ->
addRadioConfigScreenComposable<SettingsRoutes.Audio>(navController, entry.name, entry.screenComposable)
is SettingsRoutes.RemoteHardware ->
addRadioConfigScreenComposable<SettingsRoutes.RemoteHardware>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.CannedMessage ->
addRadioConfigScreenComposable<RadioConfigRoutes.CannedMessage>(
is SettingsRoutes.NeighborInfo ->
addRadioConfigScreenComposable<SettingsRoutes.NeighborInfo>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.Audio ->
addRadioConfigScreenComposable<RadioConfigRoutes.Audio>(
is SettingsRoutes.AmbientLighting ->
addRadioConfigScreenComposable<SettingsRoutes.AmbientLighting>(
navController,
entry.name,
entry.screenComposable,
)
is RadioConfigRoutes.RemoteHardware ->
addRadioConfigScreenComposable<RadioConfigRoutes.RemoteHardware>(
is SettingsRoutes.DetectionSensor ->
addRadioConfigScreenComposable<SettingsRoutes.DetectionSensor>(
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>(
is SettingsRoutes.Paxcounter ->
addRadioConfigScreenComposable<SettingsRoutes.Paxcounter>(
navController,
entry.name,
entry.screenComposable,
@ -366,66 +351,66 @@ enum class ConfigRoute(
val type: Int = 0,
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
) {
USER(R.string.user, RadioConfigRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
CHANNELS(
R.string.channels,
RadioConfigRoutes.ChannelConfig,
SettingsRoutes.ChannelConfig,
Icons.AutoMirrored.Default.List,
0,
{ vm -> ChannelConfigScreen(vm) },
),
DEVICE(
R.string.device,
RadioConfigRoutes.Device,
SettingsRoutes.Device,
Icons.Default.Router,
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
{ vm -> DeviceConfigScreen(vm) },
),
POSITION(
R.string.position,
RadioConfigRoutes.Position,
SettingsRoutes.Position,
Icons.Default.LocationOn,
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
{ vm -> PositionConfigScreen(vm) },
),
POWER(
R.string.power,
RadioConfigRoutes.Power,
SettingsRoutes.Power,
Icons.Default.Power,
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
{ vm -> PowerConfigScreen(vm) },
),
NETWORK(
R.string.network,
RadioConfigRoutes.Network,
SettingsRoutes.Network,
Icons.Default.Wifi,
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
{ vm -> NetworkConfigScreen(vm) },
),
DISPLAY(
R.string.display,
RadioConfigRoutes.Display,
SettingsRoutes.Display,
Icons.Default.DisplaySettings,
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
{ vm -> DisplayConfigScreen(vm) },
),
LORA(
R.string.lora,
RadioConfigRoutes.LoRa,
SettingsRoutes.LoRa,
Icons.Default.CellTower,
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
{ vm -> LoRaConfigScreen(vm) },
),
BLUETOOTH(
R.string.bluetooth,
RadioConfigRoutes.Bluetooth,
SettingsRoutes.Bluetooth,
Icons.Default.Bluetooth,
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
{ vm -> BluetoothConfigScreen(vm) },
),
SECURITY(
R.string.security,
RadioConfigRoutes.Security,
SettingsRoutes.Security,
Icons.Default.Security,
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
{ vm -> SecurityConfigScreen(vm) },
@ -454,91 +439,91 @@ enum class ModuleRoute(
) {
MQTT(
R.string.mqtt,
RadioConfigRoutes.MQTT,
SettingsRoutes.MQTT,
Icons.Default.Cloud,
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
{ vm -> MQTTConfigScreen(vm) },
),
SERIAL(
R.string.serial,
RadioConfigRoutes.Serial,
SettingsRoutes.Serial,
Icons.Default.Usb,
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
{ vm -> SerialConfigScreen(vm) },
),
EXT_NOTIFICATION(
R.string.external_notification,
RadioConfigRoutes.ExtNotification,
SettingsRoutes.ExtNotification,
Icons.Default.Notifications,
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
{ vm -> ExternalNotificationConfigScreen(vm) },
),
STORE_FORWARD(
R.string.store_forward,
RadioConfigRoutes.StoreForward,
SettingsRoutes.StoreForward,
Icons.AutoMirrored.Default.Forward,
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
{ vm -> StoreForwardConfigScreen(vm) },
),
RANGE_TEST(
R.string.range_test,
RadioConfigRoutes.RangeTest,
SettingsRoutes.RangeTest,
Icons.Default.Speed,
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
{ vm -> RangeTestConfigScreen(vm) },
),
TELEMETRY(
R.string.telemetry,
RadioConfigRoutes.Telemetry,
SettingsRoutes.Telemetry,
Icons.Default.DataUsage,
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
{ vm -> TelemetryConfigScreen(vm) },
),
CANNED_MESSAGE(
R.string.canned_message,
RadioConfigRoutes.CannedMessage,
SettingsRoutes.CannedMessage,
Icons.AutoMirrored.Default.Message,
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
{ vm -> CannedMessageConfigScreen(vm) },
),
AUDIO(
R.string.audio,
RadioConfigRoutes.Audio,
SettingsRoutes.Audio,
Icons.AutoMirrored.Default.VolumeUp,
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
{ vm -> AudioConfigScreen(vm) },
),
REMOTE_HARDWARE(
R.string.remote_hardware,
RadioConfigRoutes.RemoteHardware,
SettingsRoutes.RemoteHardware,
Icons.Default.SettingsRemote,
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
{ vm -> RemoteHardwareConfigScreen(vm) },
),
NEIGHBOR_INFO(
R.string.neighbor_info,
RadioConfigRoutes.NeighborInfo,
SettingsRoutes.NeighborInfo,
Icons.Default.People,
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
{ vm -> NeighborInfoConfigScreen(vm) },
),
AMBIENT_LIGHTING(
R.string.ambient_lighting,
RadioConfigRoutes.AmbientLighting,
SettingsRoutes.AmbientLighting,
Icons.Default.LightMode,
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
{ vm -> AmbientLightingConfigScreen(vm) },
),
DETECTION_SENSOR(
R.string.detection_sensor,
RadioConfigRoutes.DetectionSensor,
SettingsRoutes.DetectionSensor,
Icons.Default.Sensors,
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
{ vm -> DetectionSensorConfigScreen(vm) },
),
PAXCOUNTER(
R.string.paxcounter,
RadioConfigRoutes.Paxcounter,
SettingsRoutes.Paxcounter,
Icons.Default.PermScanWifi,
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
{ vm -> PaxcounterConfigScreen(vm) },

View file

@ -34,7 +34,6 @@ import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.QrCode2
import androidx.compose.material.icons.rounded.Wifi
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme.colorScheme
@ -90,8 +89,8 @@ import com.geeksville.mesh.navigation.ContactsRoutes
import com.geeksville.mesh.navigation.MapRoutes
import com.geeksville.mesh.navigation.NavGraph
import com.geeksville.mesh.navigation.NodesRoutes
import com.geeksville.mesh.navigation.RadioConfigRoutes
import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.repository.radio.MeshActivity
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.service.MeshService
@ -104,6 +103,7 @@ import com.geeksville.mesh.ui.common.icons.Conversations
import com.geeksville.mesh.ui.common.icons.Map
import com.geeksville.mesh.ui.common.icons.MeshtasticIcons
import com.geeksville.mesh.ui.common.icons.Nodes
import com.geeksville.mesh.ui.common.icons.Settings
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusBlue
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
import com.geeksville.mesh.ui.connections.DeviceType
@ -120,7 +120,7 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector,
Conversations(R.string.conversations, MeshtasticIcons.Conversations, ContactsRoutes.ContactsGraph),
Nodes(R.string.nodes, MeshtasticIcons.Nodes, NodesRoutes.NodesGraph),
Map(R.string.map, MeshtasticIcons.Map, MapRoutes.Map),
Share(R.string.bottom_nav_share, Icons.Rounded.QrCode2, ChannelsRoutes.ChannelsGraph),
Settings(R.string.bottom_nav_settings, MeshtasticIcons.Settings, SettingsRoutes.SettingsGraph()),
Connections(R.string.connections, Icons.Rounded.Wifi, ConnectionsRoutes.ConnectionsGraph),
;
@ -150,7 +150,6 @@ fun MainScreen(
) {
val navController = rememberNavController()
val connectionState by uIViewModel.connectionState.collectAsStateWithLifecycle()
val localConfig by uIViewModel.localConfig.collectAsStateWithLifecycle()
val requestChannelSet by uIViewModel.requestChannelSet.collectAsStateWithLifecycle()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@ -205,7 +204,7 @@ fun MainScreen(
text = { Text(text = message) },
onConfirm = {
if (compromisedKeys) {
navController.navigate(RadioConfigRoutes.Security)
navController.navigate(SettingsRoutes.Security)
}
uIViewModel.clearClientNotification(notification)
},
@ -348,13 +347,11 @@ fun MainScreen(
}
MainAppBar(
viewModel = uIViewModel,
isManaged = localConfig.security.isManaged,
navController = navController,
onAction = { action ->
if (action is MainMenuAction) {
when (action) {
MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel)
MainMenuAction.RADIO_CONFIG -> navController.navigate(RadioConfigRoutes.RadioConfig())
MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat)
MainMenuAction.SHOW_INTRO -> uIViewModel.onMainMenuAction(action)
else -> onAction(action)

View file

@ -55,14 +55,14 @@ import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.ContactsRoutes
import com.geeksville.mesh.navigation.NodesRoutes
import com.geeksville.mesh.navigation.RadioConfigRoutes
import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.navigation.showLongNameTitle
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.debug.DebugMenuActions
import com.geeksville.mesh.ui.node.components.NodeChip
import com.geeksville.mesh.ui.radioconfig.RadioConfigMenuActions
import com.geeksville.mesh.ui.settings.radio.RadioConfigMenuActions
@Suppress("CyclomaticComplexMethod")
@Composable
@ -70,7 +70,6 @@ fun MainAppBar(
modifier: Modifier = Modifier,
viewModel: UIViewModel = hiltViewModel(),
navController: NavHostController,
isManaged: Boolean,
onAction: (Any?) -> Unit,
) {
val backStackEntry by navController.currentBackStackEntryAsState()
@ -119,11 +118,11 @@ fun MainAppBar(
actions = {
currentDestination?.let {
when {
it.isTopLevel() -> MainMenuActions(isManaged, onAction)
it.isTopLevel() -> MainMenuActions(onAction)
currentDestination.hasRoute<Route.DebugPanel>() -> DebugMenuActions()
currentDestination.hasRoute<RadioConfigRoutes.RadioConfig>() ->
currentDestination.hasRoute<SettingsRoutes.Settings>() ->
RadioConfigMenuActions(viewModel = viewModel)
else -> {}
@ -208,7 +207,6 @@ private fun TopBarActions(
enum class MainMenuAction(@StringRes val stringRes: Int) {
DEBUG(R.string.debug_panel),
RADIO_CONFIG(R.string.radio_configuration),
EXPORT_RANGETEST(R.string.save_rangetest),
THEME(R.string.theme),
LANGUAGE(R.string.preferences_language),
@ -217,7 +215,7 @@ enum class MainMenuAction(@StringRes val stringRes: Int) {
}
@Composable
private fun MainMenuActions(isManaged: Boolean, onAction: (MainMenuAction) -> Unit) {
private fun MainMenuActions(onAction: (MainMenuAction) -> Unit) {
var showMenu by remember { mutableStateOf(false) }
IconButton(onClick = { showMenu = true }) {
Icon(imageVector = Icons.Default.MoreVert, contentDescription = stringResource(R.string.overflow_menu))
@ -235,11 +233,7 @@ private fun MainMenuActions(isManaged: Boolean, onAction: (MainMenuAction) -> Un
onAction(action)
showMenu = false
},
enabled =
when (action) {
MainMenuAction.RADIO_CONFIG -> !isManaged
else -> true
},
enabled = true,
)
}
}
@ -257,7 +251,7 @@ private fun MainAppBarPreview(@PreviewParameter(BooleanProvider::class) canNavig
showNodeChip = true,
canNavigateUp = canNavigateUp,
onNavigateUp = {},
actions = { MainMenuActions(isManaged = false, onAction = {}) },
actions = { MainMenuActions(onAction = {}) },
) {}
}
}

View file

@ -57,7 +57,7 @@ import com.geeksville.mesh.channelSet
import com.geeksville.mesh.copy
import com.geeksville.mesh.model.Channel
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.radioconfig.components.ChannelSelection
import com.geeksville.mesh.ui.settings.radio.components.ChannelSelection
@Composable
fun ScannedQrCodeDialog(viewModel: UIViewModel, incoming: ChannelSet) {

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.connections.components
package com.geeksville.mesh.ui.common.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -35,8 +35,8 @@ import androidx.compose.ui.unit.dp
import com.geeksville.mesh.ui.common.theme.AppTheme
@Composable
fun TitledCard(title: String, content: @Composable ColumnScope.() -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
fun TitledCard(title: String, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
title,
modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(),
@ -49,6 +49,6 @@ fun TitledCard(title: String, content: @Composable ColumnScope.() -> Unit) {
@PreviewLightDark
@Composable
fun TitledCardPreview() {
private fun TitledCardPreview() {
AppTheme { Surface { TitledCard(title = "Title") { Box(modifier = Modifier.fillMaxWidth().height(100.dp)) {} } } }
}

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.common.icons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
/**
* This is from Material Symbols.
*
* @see
* [settings](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:settings:FILL@0;wght@400;GRAD@0;opsz@24&icon.style=Rounded&icon.query=settings&icon.set=Material+Symbols&icon.size=24&icon.color=%23e3e3e3&icon.platform=android)
*/
val MeshtasticIcons.Settings: ImageVector
get() {
if (settings != null) {
return settings!!
}
settings =
ImageVector.Builder(
name = "Settings",
defaultWidth = 24.dp,
defaultHeight = 24.dp,
viewportWidth = 960f,
viewportHeight = 960f,
)
.apply {
path(fill = SolidColor(Color(0xFFE3E3E3))) {
moveTo(433f, 880f)
quadToRelative(-27f, 0f, -46.5f, -18f)
reflectiveQuadTo(363f, 818f)
lineToRelative(-9f, -66f)
quadToRelative(-13f, -5f, -24.5f, -12f)
reflectiveQuadTo(307f, 725f)
lineToRelative(-62f, 26f)
quadToRelative(-25f, 11f, -50f, 2f)
reflectiveQuadToRelative(-39f, -32f)
lineToRelative(-47f, -82f)
quadToRelative(-14f, -23f, -8f, -49f)
reflectiveQuadToRelative(27f, -43f)
lineToRelative(53f, -40f)
quadToRelative(-1f, -7f, -1f, -13.5f)
verticalLineToRelative(-27f)
quadToRelative(0f, -6.5f, 1f, -13.5f)
lineToRelative(-53f, -40f)
quadToRelative(-21f, -17f, -27f, -43f)
reflectiveQuadToRelative(8f, -49f)
lineToRelative(47f, -82f)
quadToRelative(14f, -23f, 39f, -32f)
reflectiveQuadToRelative(50f, 2f)
lineToRelative(62f, 26f)
quadToRelative(11f, -8f, 23f, -15f)
reflectiveQuadToRelative(24f, -12f)
lineToRelative(9f, -66f)
quadToRelative(4f, -26f, 23.5f, -44f)
reflectiveQuadToRelative(46.5f, -18f)
horizontalLineToRelative(94f)
quadToRelative(27f, 0f, 46.5f, 18f)
reflectiveQuadToRelative(23.5f, 44f)
lineToRelative(9f, 66f)
quadToRelative(13f, 5f, 24.5f, 12f)
reflectiveQuadToRelative(22.5f, 15f)
lineToRelative(62f, -26f)
quadToRelative(25f, -11f, 50f, -2f)
reflectiveQuadToRelative(39f, 32f)
lineToRelative(47f, 82f)
quadToRelative(14f, 23f, 8f, 49f)
reflectiveQuadToRelative(-27f, 43f)
lineToRelative(-53f, 40f)
quadToRelative(1f, 7f, 1f, 13.5f)
verticalLineToRelative(27f)
quadToRelative(0f, 6.5f, -2f, 13.5f)
lineToRelative(53f, 40f)
quadToRelative(21f, 17f, 27f, 43f)
reflectiveQuadToRelative(-8f, 49f)
lineToRelative(-48f, 82f)
quadToRelative(-14f, 23f, -39f, 32f)
reflectiveQuadToRelative(-50f, -2f)
lineToRelative(-60f, -26f)
quadToRelative(-11f, 8f, -23f, 15f)
reflectiveQuadToRelative(-24f, 12f)
lineToRelative(-9f, 66f)
quadToRelative(-4f, 26f, -23.5f, 44f)
reflectiveQuadTo(527f, 880f)
horizontalLineToRelative(-94f)
close()
moveTo(440f, 800f)
horizontalLineToRelative(79f)
lineToRelative(14f, -106f)
quadToRelative(31f, -8f, 57.5f, -23.5f)
reflectiveQuadTo(639f, 633f)
lineToRelative(99f, 41f)
lineToRelative(39f, -68f)
lineToRelative(-86f, -65f)
quadToRelative(5f, -14f, 7f, -29.5f)
reflectiveQuadToRelative(2f, -31.5f)
quadToRelative(0f, -16f, -2f, -31.5f)
reflectiveQuadToRelative(-7f, -29.5f)
lineToRelative(86f, -65f)
lineToRelative(-39f, -68f)
lineToRelative(-99f, 42f)
quadToRelative(-22f, -23f, -48.5f, -38.5f)
reflectiveQuadTo(533f, 266f)
lineToRelative(-13f, -106f)
horizontalLineToRelative(-79f)
lineToRelative(-14f, 106f)
quadToRelative(-31f, 8f, -57.5f, 23.5f)
reflectiveQuadTo(321f, 327f)
lineToRelative(-99f, -41f)
lineToRelative(-39f, 68f)
lineToRelative(86f, 64f)
quadToRelative(-5f, 15f, -7f, 30f)
reflectiveQuadToRelative(-2f, 32f)
quadToRelative(0f, 16f, 2f, 31f)
reflectiveQuadToRelative(7f, 30f)
lineToRelative(-86f, 65f)
lineToRelative(39f, 68f)
lineToRelative(99f, -42f)
quadToRelative(22f, 23f, 48.5f, 38.5f)
reflectiveQuadTo(427f, 694f)
lineToRelative(13f, 106f)
close()
moveTo(482f, 620f)
quadToRelative(58f, 0f, 99f, -41f)
reflectiveQuadToRelative(41f, -99f)
quadToRelative(0f, -58f, -41f, -99f)
reflectiveQuadToRelative(-99f, -41f)
quadToRelative(-59f, 0f, -99.5f, 41f)
reflectiveQuadTo(342f, 480f)
quadToRelative(0f, 58f, 40.5f, 99f)
reflectiveQuadToRelative(99.5f, 41f)
close()
moveTo(480f, 480f)
close()
}
}
.build()
return settings!!
}
private var settings: ImageVector? = null

View file

@ -94,8 +94,8 @@ import com.geeksville.mesh.model.NO_DEVICE_SELECTED
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.ConfigRoute
import com.geeksville.mesh.navigation.RadioConfigRoutes
import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.navigation.getNavRouteFrom
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.ui.common.components.SwitchPreference
@ -103,8 +103,8 @@ import com.geeksville.mesh.ui.connections.components.BLEDevices
import com.geeksville.mesh.ui.connections.components.CurrentlyConnectedCard
import com.geeksville.mesh.ui.connections.components.NetworkDevices
import com.geeksville.mesh.ui.connections.components.UsbDevices
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.radioconfig.components.PacketResponseStateDialog
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
import com.geeksville.mesh.ui.sharing.SharedContactDialog
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
@ -129,7 +129,7 @@ fun ConnectionsScreen(
scanModel: BTScanModel = hiltViewModel(),
bluetoothViewModel: BluetoothViewModel = hiltViewModel(),
radioConfigViewModel: RadioConfigViewModel = hiltViewModel(),
onNavigateToRadioConfig: () -> Unit,
onNavigateToSettings: () -> Unit,
onNavigateToNodeDetails: (Int) -> Unit,
onConfigNavigate: (Route) -> Unit,
) {
@ -167,8 +167,8 @@ fun ConnectionsScreen(
getNavRouteFrom(radioConfigState.route)?.let { route ->
isWaiting = false
radioConfigViewModel.clearPacketResponse()
if (route == RadioConfigRoutes.LoRa) {
onConfigNavigate(RadioConfigRoutes.LoRa)
if (route == SettingsRoutes.LoRa) {
onConfigNavigate(SettingsRoutes.LoRa)
}
}
},
@ -260,7 +260,7 @@ fun ConnectionsScreen(
node = node,
onNavigateToNodeDetails = onNavigateToNodeDetails,
onSetShowSharedContact = { showSharedContact = it },
onNavigateToRadioConfig = onNavigateToRadioConfig,
onNavigateToSettings = onNavigateToSettings,
onClickDisconnect = { scanModel.disconnect() },
)
}

View file

@ -50,6 +50,7 @@ import com.geeksville.mesh.R
import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.DeviceListEntry
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.ui.common.components.TitledCard
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.rememberMultiplePermissionsState

View file

@ -58,7 +58,7 @@ fun CurrentlyConnectedCard(
modifier: Modifier = Modifier,
onNavigateToNodeDetails: (Int) -> Unit,
onSetShowSharedContact: (Node) -> Unit,
onNavigateToRadioConfig: () -> Unit,
onNavigateToSettings: () -> Unit,
onClickDisconnect: () -> Unit,
) {
Card(modifier = modifier) {
@ -98,7 +98,7 @@ fun CurrentlyConnectedCard(
}
}
IconButton(enabled = true, onClick = onNavigateToRadioConfig) {
IconButton(enabled = true, onClick = onNavigateToSettings) {
Icon(
imageVector = Icons.Default.Settings,
contentDescription = stringResource(id = R.string.radio_configuration),
@ -142,7 +142,7 @@ private fun CurrentlyConnectedCardPreview() {
),
onNavigateToNodeDetails = {},
onSetShowSharedContact = {},
onNavigateToRadioConfig = {},
onNavigateToSettings = {},
onClickDisconnect = {},
)
}

View file

@ -56,6 +56,7 @@ import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.DeviceListEntry
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.ui.common.components.TitledCard
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.connections.isIPAddress
import kotlinx.coroutines.launch

View file

@ -27,6 +27,7 @@ import com.geeksville.mesh.R
import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.DeviceListEntry
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.ui.common.components.TitledCard
import com.geeksville.mesh.ui.common.theme.AppTheme
@Composable

View file

@ -141,8 +141,8 @@ import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.model.isUnmessageableRole
import com.geeksville.mesh.navigation.NodeDetailRoutes
import com.geeksville.mesh.navigation.RadioConfigRoutes
import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.service.ServiceAction
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
@ -153,7 +153,7 @@ import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
import com.geeksville.mesh.ui.node.components.NodeActionDialogs
import com.geeksville.mesh.ui.node.components.NodeMenuAction
import com.geeksville.mesh.ui.radioconfig.NavCard
import com.geeksville.mesh.ui.settings.radio.NavCard
import com.geeksville.mesh.ui.sharing.SharedContactDialog
import com.geeksville.mesh.util.UnitConversions
import com.geeksville.mesh.util.UnitConversions.toTempString
@ -440,7 +440,7 @@ private fun AdministrationSection(
icon = Icons.Default.Settings,
enabled = metricsState.isLocal || node.metadata != null,
) {
onAction(NodeDetailAction.Navigate(RadioConfigRoutes.RadioConfig(node.num)))
onAction(NodeDetailAction.Navigate(SettingsRoutes.Settings(node.num)))
}
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.settings.components
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material.icons.rounded.Android
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.ui.common.theme.AppTheme
@Composable
fun SettingsItem(
text: String,
enabled: Boolean,
leadingIcon: ImageVector? = null,
trailingIcon: ImageVector? = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
onClick: () -> Unit,
) {
Card(
onClick = onClick,
enabled = enabled,
colors = CardDefaults.cardColors(
containerColor = Color.Transparent,
disabledContainerColor = Color.Transparent,
),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 16.dp),
) {
leadingIcon?.let {
Icon(imageVector = it, contentDescription = text, modifier = Modifier.size(24.dp))
Spacer(modifier = Modifier.width(16.dp))
}
Text(text = text, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
trailingIcon?.let {
Icon(imageVector = it, contentDescription = null, modifier = Modifier.wrapContentSize())
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun SettingsItemPreview() {
AppTheme { SettingsItem(text = "Text", leadingIcon = Icons.Rounded.Android, enabled = true) {} }
}
@Preview(showBackground = true)
@Composable
private fun SettingsItemDisabledPreview() {
AppTheme { SettingsItem(text = "Text", leadingIcon = Icons.Rounded.Android, enabled = false) {} }
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig
package com.geeksville.mesh.ui.settings.radio
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig
package com.geeksville.mesh.ui.settings.radio
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig
package com.geeksville.mesh.ui.settings.radio
import android.app.Activity
import android.content.Intent
@ -72,13 +72,15 @@ import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.navigation.AdminRoute
import com.geeksville.mesh.navigation.ConfigRoute
import com.geeksville.mesh.navigation.ModuleRoute
import com.geeksville.mesh.navigation.RadioConfigRoutes
import com.geeksville.mesh.navigation.Route
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.navigation.getNavRouteFrom
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.TitledCard
import com.geeksville.mesh.ui.common.theme.AppTheme
import com.geeksville.mesh.ui.radioconfig.components.EditDeviceProfileDialog
import com.geeksville.mesh.ui.radioconfig.components.PacketResponseStateDialog
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
import com.geeksville.mesh.ui.settings.components.SettingsItem
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
@ -105,6 +107,7 @@ fun RadioConfigScreen(
nodeName?.let { uiViewModel.setTitle(it) }
val excludedModulesUnlocked by uiViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle()
val localConfig by uiViewModel.localConfig.collectAsStateWithLifecycle()
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
var isWaiting by remember { mutableStateOf(false) }
@ -177,6 +180,7 @@ fun RadioConfigScreen(
RadioConfigItemList(
modifier = modifier,
state = state,
isManaged = localConfig.security.isManaged,
excludedModulesUnlocked = excludedModulesUnlocked,
onRouteClick = { route ->
isWaiting = true
@ -272,9 +276,11 @@ private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Un
}
}
@Suppress("LongMethod")
@Composable
private fun RadioConfigItemList(
state: RadioConfigState,
isManaged: Boolean,
excludedModulesUnlocked: Boolean = false,
modifier: Modifier = Modifier,
onRouteClick: (Enum<*>) -> Unit = {},
@ -282,7 +288,7 @@ private fun RadioConfigItemList(
onExport: () -> Unit = {},
onNavigate: (Route) -> Unit,
) {
val enabled = state.connected && !state.responseState.isWaiting()
val enabled = state.connected && !state.responseState.isWaiting() && !isManaged
var modules by remember { mutableStateOf(ModuleRoute.filterExcludedFrom(state.metadata)) }
LaunchedEffect(excludedModulesUnlocked) {
if (excludedModulesUnlocked) {
@ -291,44 +297,72 @@ private fun RadioConfigItemList(
modules = ModuleRoute.filterExcludedFrom(state.metadata)
}
}
LazyColumn(modifier = modifier, contentPadding = PaddingValues(horizontal = 16.dp)) {
item { PreferenceCategory(stringResource(R.string.radio_configuration)) }
items(ConfigRoute.filterExcludedFrom(state.metadata)) {
NavCard(title = stringResource(it.title), icon = it.icon, enabled = enabled) { onRouteClick(it) }
LazyColumn(modifier = modifier, contentPadding = PaddingValues(16.dp)) {
item {
TitledCard(title = stringResource(R.string.radio_configuration)) {
if (isManaged) {
ManagedMessage()
}
ConfigRoute.filterExcludedFrom(state.metadata).forEach {
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
onRouteClick(it)
}
}
}
}
item { PreferenceCategory(stringResource(R.string.module_settings)) }
items(modules) {
NavCard(title = stringResource(it.title), icon = it.icon, enabled = enabled) { onRouteClick(it) }
item {
TitledCard(title = stringResource(R.string.module_settings), modifier = Modifier.padding(top = 16.dp)) {
if (isManaged) {
ManagedMessage()
}
modules.forEach {
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
onRouteClick(it)
}
}
}
}
if (state.isLocal) {
item {
PreferenceCategory(stringResource(R.string.backup_restore))
NavCard(
title = stringResource(R.string.import_configuration),
icon = Icons.Default.Download,
enabled = enabled,
onClick = onImport,
)
NavCard(
title = stringResource(R.string.export_configuration),
icon = Icons.Default.Upload,
enabled = enabled,
onClick = onExport,
)
TitledCard(title = stringResource(R.string.backup_restore), modifier = Modifier.padding(top = 16.dp)) {
if (isManaged) {
ManagedMessage()
}
SettingsItem(
text = stringResource(R.string.import_configuration),
leadingIcon = Icons.Default.Download,
enabled = enabled,
onClick = onImport,
)
SettingsItem(
text = stringResource(R.string.export_configuration),
leadingIcon = Icons.Default.Upload,
enabled = enabled,
onClick = onExport,
)
}
}
}
items(AdminRoute.entries) { NavButton(it.title, enabled) { onRouteClick(it) } }
item {
PreferenceCategory("Advanced")
NavCard(
title = stringResource(R.string.clean_node_database_title),
enabled = enabled,
onClick = { onNavigate(RadioConfigRoutes.CleanNodeDb) },
)
TitledCard(title = "Advanced", modifier = Modifier.padding(top = 16.dp)) {
if (isManaged) {
ManagedMessage()
}
SettingsItem(
text = stringResource(R.string.clean_node_database_title),
enabled = enabled,
onClick = { onNavigate(SettingsRoutes.CleanNodeDb) },
)
}
}
}
}
@ -362,5 +396,28 @@ fun RadioConfigMenuActions(modifier: Modifier = Modifier, viewModel: UIViewModel
@Preview(showBackground = true)
@Composable
private fun RadioSettingsScreenPreview() = AppTheme {
RadioConfigItemList(state = RadioConfigState(isLocal = true, connected = true), onNavigate = { _ -> })
RadioConfigItemList(
state = RadioConfigState(isLocal = true, connected = true),
isManaged = false,
onNavigate = { _ -> },
)
}
@Composable
private fun ManagedMessage() {
Text(
text = stringResource(R.string.message_device_managed),
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
color = MaterialTheme.colorScheme.StatusRed,
)
}
@Preview(showBackground = true)
@Composable
private fun RadioSettingsScreenManagedPreview() = AppTheme {
RadioConfigItemList(
state = RadioConfigState(isLocal = true, connected = true),
isManaged = true,
onNavigate = { _ -> },
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig
package com.geeksville.mesh.ui.settings.radio
import android.Manifest
import android.app.Application
@ -56,7 +56,7 @@ import com.geeksville.mesh.moduleConfig
import com.geeksville.mesh.navigation.AdminRoute
import com.geeksville.mesh.navigation.ConfigRoute
import com.geeksville.mesh.navigation.ModuleRoute
import com.geeksville.mesh.navigation.RadioConfigRoutes
import com.geeksville.mesh.navigation.SettingsRoutes
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
import com.geeksville.mesh.repository.location.LocationRepository
import com.geeksville.mesh.service.ConnectionState
@ -108,7 +108,7 @@ constructor(
private val meshService: IMeshService?
get() = radioConfigRepository.meshService
private val destNum = savedStateHandle.toRoute<RadioConfigRoutes.RadioConfig>().destNum
private val destNum = savedStateHandle.toRoute<SettingsRoutes.Settings>().destNum
private val _destNode = MutableStateFlow<Node?>(null)
val destNode: StateFlow<Node?>
get() = _destNode

View file

@ -15,17 +15,18 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig
package com.geeksville.mesh.ui.settings.radio
import com.geeksville.mesh.util.UiText
/**
* Generic sealed class defines each possible state of a response.
*/
/** Generic sealed class defines each possible state of a response. */
sealed class ResponseState<out T> {
data object Empty : ResponseState<Nothing>()
data class Loading(var total: Int = 1, var completed: Int = 0) : ResponseState<Nothing>()
data class Success<T>(val result: T) : ResponseState<T>()
data class Error(val error: UiText) : ResponseState<Nothing>()
fun isWaiting() = this !is Empty

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun AmbientLightingConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
AmbientLightingConfigItemList(
@ -61,7 +56,7 @@ fun AmbientLightingConfigScreen(
onSaveClicked = { ambientLightingInput ->
val config = moduleConfig { ambientLighting = ambientLightingInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -74,9 +69,7 @@ fun AmbientLightingConfigItemList(
val focusManager = LocalFocusManager.current
var ambientLightingInput by rememberSaveable { mutableStateOf(ambientLightingConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.ambient_lighting_config)) }
item {
@ -84,9 +77,7 @@ fun AmbientLightingConfigItemList(
title = stringResource(R.string.led_state),
checked = ambientLightingInput.ledState,
enabled = enabled,
onCheckedChange = {
ambientLightingInput = ambientLightingInput.copy { ledState = it }
}
onCheckedChange = { ambientLightingInput = ambientLightingInput.copy { ledState = it } },
)
}
item { HorizontalDivider() }
@ -97,9 +88,7 @@ fun AmbientLightingConfigItemList(
value = ambientLightingInput.current,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
ambientLightingInput = ambientLightingInput.copy { current = it }
}
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { current = it } },
)
}
@ -109,7 +98,7 @@ fun AmbientLightingConfigItemList(
value = ambientLightingInput.red,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { red = it } }
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { red = it } },
)
}
@ -119,7 +108,7 @@ fun AmbientLightingConfigItemList(
value = ambientLightingInput.green,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { green = it } }
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { green = it } },
)
}
@ -129,7 +118,7 @@ fun AmbientLightingConfigItemList(
value = ambientLightingInput.blue,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { blue = it } }
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { blue = it } },
)
}
@ -143,7 +132,7 @@ fun AmbientLightingConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(ambientLightingInput)
}
},
)
}
}
@ -155,6 +144,6 @@ private fun AmbientLightingConfigPreview() {
AmbientLightingConfigItemList(
ambientLightingConfig = ModuleConfigProtos.ModuleConfig.AmbientLightingConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -42,19 +42,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun AudioConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
AudioConfigItemList(
@ -63,22 +58,17 @@ fun AudioConfigScreen(
onSaveClicked = { audioInput ->
val config = moduleConfig { audio = audioInput }
viewModel.setModuleConfig(config)
}
},
)
}
@Suppress("LongMethod")
@Composable
fun AudioConfigItemList(
audioConfig: AudioConfig,
enabled: Boolean,
onSaveClicked: (AudioConfig) -> Unit,
) {
fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -> Unit) {
val focusManager = LocalFocusManager.current
var audioInput by rememberSaveable { mutableStateOf(audioConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.audio_config)) }
item {
@ -86,7 +76,7 @@ fun AudioConfigItemList(
title = stringResource(R.string.codec_2_enabled),
checked = audioInput.codec2Enabled,
enabled = enabled,
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } }
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } },
)
}
item { HorizontalDivider() }
@ -97,7 +87,7 @@ fun AudioConfigItemList(
value = audioInput.pttPin,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { audioInput = audioInput.copy { pttPin = it } }
onValueChanged = { audioInput = audioInput.copy { pttPin = it } },
)
}
@ -105,11 +95,12 @@ fun AudioConfigItemList(
DropDownPreference(
title = stringResource(R.string.codec2_sample_rate),
enabled = enabled,
items = AudioConfig.Audio_Baud.entries
items =
AudioConfig.Audio_Baud.entries
.filter { it != AudioConfig.Audio_Baud.UNRECOGNIZED }
.map { it to it.name },
selectedItem = audioInput.bitrate,
onItemSelected = { audioInput = audioInput.copy { bitrate = it } }
onItemSelected = { audioInput = audioInput.copy { bitrate = it } },
)
}
item { Divider() }
@ -120,7 +111,7 @@ fun AudioConfigItemList(
value = audioInput.i2SWs,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { audioInput = audioInput.copy { i2SWs = it } }
onValueChanged = { audioInput = audioInput.copy { i2SWs = it } },
)
}
@ -130,7 +121,7 @@ fun AudioConfigItemList(
value = audioInput.i2SSd,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { audioInput = audioInput.copy { i2SSd = it } }
onValueChanged = { audioInput = audioInput.copy { i2SSd = it } },
)
}
@ -140,7 +131,7 @@ fun AudioConfigItemList(
value = audioInput.i2SDin,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { audioInput = audioInput.copy { i2SDin = it } }
onValueChanged = { audioInput = audioInput.copy { i2SDin = it } },
)
}
@ -150,7 +141,7 @@ fun AudioConfigItemList(
value = audioInput.i2SSck,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { audioInput = audioInput.copy { i2SSck = it } }
onValueChanged = { audioInput = audioInput.copy { i2SSck = it } },
)
}
@ -164,7 +155,7 @@ fun AudioConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(audioInput)
}
},
)
}
}
@ -173,9 +164,5 @@ fun AudioConfigItemList(
@Preview(showBackground = true)
@Composable
private fun AudioConfigPreview() {
AudioConfigItemList(
audioConfig = AudioConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
)
AudioConfigItemList(audioConfig = AudioConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -41,19 +41,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun BluetoothConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun BluetoothConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
BluetoothConfigItemList(
@ -62,7 +57,7 @@ fun BluetoothConfigScreen(
onSaveClicked = { bluetoothInput ->
val config = config { bluetooth = bluetoothInput }
viewModel.setConfig(config)
}
},
)
}
@ -75,9 +70,7 @@ fun BluetoothConfigItemList(
val focusManager = LocalFocusManager.current
var bluetoothInput by rememberSaveable { mutableStateOf(bluetoothConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.bluetooth_config)) }
item {
@ -85,7 +78,7 @@ fun BluetoothConfigItemList(
title = stringResource(R.string.bluetooth_enabled),
checked = bluetoothInput.enabled,
enabled = enabled,
onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } }
onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -94,11 +87,12 @@ fun BluetoothConfigItemList(
DropDownPreference(
title = stringResource(R.string.pairing_mode),
enabled = enabled,
items = BluetoothConfig.PairingMode.entries
items =
BluetoothConfig.PairingMode.entries
.filter { it != BluetoothConfig.PairingMode.UNRECOGNIZED }
.map { it to it.name },
selectedItem = bluetoothInput.mode,
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } }
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } },
)
}
item { HorizontalDivider() }
@ -113,7 +107,7 @@ fun BluetoothConfigItemList(
if (it.toString().length == 6) { // ensure 6 digits
bluetoothInput = bluetoothInput.copy { fixedPin = it }
}
}
},
)
}
@ -127,7 +121,7 @@ fun BluetoothConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(bluetoothInput)
}
},
)
}
}
@ -136,9 +130,5 @@ fun BluetoothConfigItemList(
@Preview(showBackground = true)
@Composable
private fun BluetoothConfigPreview() {
BluetoothConfigItemList(
bluetoothConfig = BluetoothConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
)
BluetoothConfigItemList(bluetoothConfig = BluetoothConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -44,19 +44,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun CannedMessageConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
CannedMessageConfigItemList(
@ -71,7 +66,7 @@ fun CannedMessageConfigScreen(
val config = moduleConfig { cannedMessage = cannedMessageInput }
viewModel.setModuleConfig(config)
}
}
},
)
}
@ -86,9 +81,7 @@ fun CannedMessageConfigItemList(
var messagesInput by rememberSaveable { mutableStateOf(messages) }
var cannedMessageInput by rememberSaveable { mutableStateOf(cannedMessageConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.canned_message_config)) }
item {
@ -96,9 +89,7 @@ fun CannedMessageConfigItemList(
title = stringResource(R.string.canned_message_enabled),
checked = cannedMessageInput.enabled,
enabled = enabled,
onCheckedChange = {
cannedMessageInput = cannedMessageInput.copy { this.enabled = it }
}
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -108,9 +99,7 @@ fun CannedMessageConfigItemList(
title = stringResource(R.string.rotary_encoder_1_enabled),
checked = cannedMessageInput.rotary1Enabled,
enabled = enabled,
onCheckedChange = {
cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it }
}
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it } },
)
}
item { HorizontalDivider() }
@ -121,9 +110,7 @@ fun CannedMessageConfigItemList(
value = cannedMessageInput.inputbrokerPinA,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it }
}
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it } },
)
}
@ -133,9 +120,7 @@ fun CannedMessageConfigItemList(
value = cannedMessageInput.inputbrokerPinB,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it }
}
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it } },
)
}
@ -145,9 +130,7 @@ fun CannedMessageConfigItemList(
value = cannedMessageInput.inputbrokerPinPress,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it }
}
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it } },
)
}
@ -155,13 +138,12 @@ fun CannedMessageConfigItemList(
DropDownPreference(
title = stringResource(R.string.generate_input_event_on_press),
enabled = enabled,
items = CannedMessageConfig.InputEventChar.entries
items =
CannedMessageConfig.InputEventChar.entries
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
.map { it to it.name },
selectedItem = cannedMessageInput.inputbrokerEventPress,
onItemSelected = {
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it }
}
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it } },
)
}
item { HorizontalDivider() }
@ -170,13 +152,12 @@ fun CannedMessageConfigItemList(
DropDownPreference(
title = stringResource(R.string.generate_input_event_on_cw),
enabled = enabled,
items = CannedMessageConfig.InputEventChar.entries
items =
CannedMessageConfig.InputEventChar.entries
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
.map { it to it.name },
selectedItem = cannedMessageInput.inputbrokerEventCw,
onItemSelected = {
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it }
}
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it } },
)
}
item { HorizontalDivider() }
@ -185,13 +166,12 @@ fun CannedMessageConfigItemList(
DropDownPreference(
title = stringResource(R.string.generate_input_event_on_ccw),
enabled = enabled,
items = CannedMessageConfig.InputEventChar.entries
items =
CannedMessageConfig.InputEventChar.entries
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
.map { it to it.name },
selectedItem = cannedMessageInput.inputbrokerEventCcw,
onItemSelected = {
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it }
}
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it } },
)
}
item { HorizontalDivider() }
@ -201,9 +181,7 @@ fun CannedMessageConfigItemList(
title = stringResource(R.string.up_down_select_input_enabled),
checked = cannedMessageInput.updown1Enabled,
enabled = enabled,
onCheckedChange = {
cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it }
}
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it } },
)
}
item { HorizontalDivider() }
@ -215,13 +193,10 @@ fun CannedMessageConfigItemList(
maxSize = 63, // allow_input_source max_size:16
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
cannedMessageInput = cannedMessageInput.copy { allowInputSource = it }
}
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { allowInputSource = it } },
)
}
@ -230,9 +205,7 @@ fun CannedMessageConfigItemList(
title = stringResource(R.string.send_bell),
checked = cannedMessageInput.sendBell,
enabled = enabled,
onCheckedChange = {
cannedMessageInput = cannedMessageInput.copy { sendBell = it }
}
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { sendBell = it } },
)
}
item { HorizontalDivider() }
@ -244,11 +217,10 @@ fun CannedMessageConfigItemList(
maxSize = 200, // messages max_size:201
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { messagesInput = it }
onValueChanged = { messagesInput = it },
)
}
@ -263,7 +235,7 @@ fun CannedMessageConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(messagesInput, cannedMessageInput)
}
},
)
}
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
@ -79,7 +79,7 @@ import com.geeksville.mesh.ui.common.components.SecurityIcon
import com.geeksville.mesh.ui.common.components.dragContainer
import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
import com.geeksville.mesh.ui.common.components.rememberDragDropState
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
private fun ChannelItem(

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -44,19 +44,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun DetectionSensorConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
DetectionSensorConfigItemList(
@ -65,7 +60,7 @@ fun DetectionSensorConfigScreen(
onSaveClicked = { detectionSensorInput ->
val config = moduleConfig { detectionSensor = detectionSensorInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -79,9 +74,7 @@ fun DetectionSensorConfigItemList(
val focusManager = LocalFocusManager.current
var detectionSensorInput by rememberSaveable { mutableStateOf(detectionSensorConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.detection_sensor_config)) }
item {
@ -89,9 +82,7 @@ fun DetectionSensorConfigItemList(
title = stringResource(R.string.detection_sensor_enabled),
checked = detectionSensorInput.enabled,
enabled = enabled,
onCheckedChange = {
detectionSensorInput = detectionSensorInput.copy { this.enabled = it }
}
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -102,9 +93,7 @@ fun DetectionSensorConfigItemList(
value = detectionSensorInput.minimumBroadcastSecs,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
detectionSensorInput = detectionSensorInput.copy { minimumBroadcastSecs = it }
}
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { minimumBroadcastSecs = it } },
)
}
@ -114,9 +103,7 @@ fun DetectionSensorConfigItemList(
value = detectionSensorInput.stateBroadcastSecs,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
detectionSensorInput = detectionSensorInput.copy { stateBroadcastSecs = it }
}
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { stateBroadcastSecs = it } },
)
}
@ -125,9 +112,7 @@ fun DetectionSensorConfigItemList(
title = stringResource(R.string.send_bell_with_alert_message),
checked = detectionSensorInput.sendBell,
enabled = enabled,
onCheckedChange = {
detectionSensorInput = detectionSensorInput.copy { sendBell = it }
}
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { sendBell = it } },
)
}
item { HorizontalDivider() }
@ -139,13 +124,10 @@ fun DetectionSensorConfigItemList(
maxSize = 19, // name max_size:20
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
detectionSensorInput = detectionSensorInput.copy { name = it }
}
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { name = it } },
)
}
@ -155,9 +137,7 @@ fun DetectionSensorConfigItemList(
value = detectionSensorInput.monitorPin,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
detectionSensorInput = detectionSensorInput.copy { monitorPin = it }
}
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { monitorPin = it } },
)
}
@ -165,13 +145,12 @@ fun DetectionSensorConfigItemList(
DropDownPreference(
title = stringResource(R.string.detection_trigger_type),
enabled = enabled,
items = ModuleConfig.DetectionSensorConfig.TriggerType.entries
items =
ModuleConfig.DetectionSensorConfig.TriggerType.entries
.filter { it != ModuleConfig.DetectionSensorConfig.TriggerType.UNRECOGNIZED }
.map { it to it.name },
selectedItem = detectionSensorInput.detectionTriggerType,
onItemSelected = {
detectionSensorInput = detectionSensorInput.copy { detectionTriggerType = it }
}
onItemSelected = { detectionSensorInput = detectionSensorInput.copy { detectionTriggerType = it } },
)
}
item { HorizontalDivider() }
@ -181,9 +160,7 @@ fun DetectionSensorConfigItemList(
title = stringResource(R.string.use_input_pullup_mode),
checked = detectionSensorInput.usePullup,
enabled = enabled,
onCheckedChange = {
detectionSensorInput = detectionSensorInput.copy { usePullup = it }
}
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { usePullup = it } },
)
}
item { HorizontalDivider() }
@ -198,7 +175,7 @@ fun DetectionSensorConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(detectionSensorInput)
}
},
)
}
}
@ -210,6 +187,6 @@ private fun DetectionSensorConfigPreview() {
DetectionSensorConfigItemList(
detectionSensorConfig = ModuleConfig.DetectionSensorConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
@ -58,47 +58,44 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
private val DeviceConfig.Role.stringRes: Int
get() = when (this) {
DeviceConfig.Role.CLIENT -> R.string.role_client
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute
DeviceConfig.Role.ROUTER -> R.string.role_router
DeviceConfig.Role.ROUTER_CLIENT -> R.string.role_router_client
DeviceConfig.Role.REPEATER -> R.string.role_repeater
DeviceConfig.Role.TRACKER -> R.string.role_tracker
DeviceConfig.Role.SENSOR -> R.string.role_sensor
DeviceConfig.Role.TAK -> R.string.role_tak
DeviceConfig.Role.CLIENT_HIDDEN -> R.string.role_client_hidden
DeviceConfig.Role.LOST_AND_FOUND -> R.string.role_lost_and_found
DeviceConfig.Role.TAK_TRACKER -> R.string.role_tak_tracker
DeviceConfig.Role.ROUTER_LATE -> R.string.role_router_late
else -> R.string.unrecognized
}
get() =
when (this) {
DeviceConfig.Role.CLIENT -> R.string.role_client
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute
DeviceConfig.Role.ROUTER -> R.string.role_router
DeviceConfig.Role.ROUTER_CLIENT -> R.string.role_router_client
DeviceConfig.Role.REPEATER -> R.string.role_repeater
DeviceConfig.Role.TRACKER -> R.string.role_tracker
DeviceConfig.Role.SENSOR -> R.string.role_sensor
DeviceConfig.Role.TAK -> R.string.role_tak
DeviceConfig.Role.CLIENT_HIDDEN -> R.string.role_client_hidden
DeviceConfig.Role.LOST_AND_FOUND -> R.string.role_lost_and_found
DeviceConfig.Role.TAK_TRACKER -> R.string.role_tak_tracker
DeviceConfig.Role.ROUTER_LATE -> R.string.role_router_late
else -> R.string.unrecognized
}
private val DeviceConfig.RebroadcastMode.stringRes: Int
get() = when (this) {
DeviceConfig.RebroadcastMode.ALL -> R.string.rebroadcast_mode_all
DeviceConfig.RebroadcastMode.ALL_SKIP_DECODING -> R.string.rebroadcast_mode_all_skip_decoding
DeviceConfig.RebroadcastMode.LOCAL_ONLY -> R.string.rebroadcast_mode_local_only
DeviceConfig.RebroadcastMode.KNOWN_ONLY -> R.string.rebroadcast_mode_known_only
DeviceConfig.RebroadcastMode.NONE -> R.string.rebroadcast_mode_none
DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY -> R.string.rebroadcast_mode_core_portnums_only
else -> R.string.unrecognized
}
get() =
when (this) {
DeviceConfig.RebroadcastMode.ALL -> R.string.rebroadcast_mode_all
DeviceConfig.RebroadcastMode.ALL_SKIP_DECODING -> R.string.rebroadcast_mode_all_skip_decoding
DeviceConfig.RebroadcastMode.LOCAL_ONLY -> R.string.rebroadcast_mode_local_only
DeviceConfig.RebroadcastMode.KNOWN_ONLY -> R.string.rebroadcast_mode_known_only
DeviceConfig.RebroadcastMode.NONE -> R.string.rebroadcast_mode_none
DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY -> R.string.rebroadcast_mode_core_portnums_only
else -> R.string.unrecognized
}
@Composable
fun DeviceConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
DeviceConfigItemList(
@ -107,94 +104,62 @@ fun DeviceConfigScreen(
onSaveClicked = { deviceInput ->
val config = config { device = deviceInput }
viewModel.setConfig(config)
}
},
)
}
@Suppress("LongMethod")
@Composable
fun RouterRoleConfirmationDialog(
onDismiss: () -> Unit,
onConfirm: () -> Unit,
) {
fun RouterRoleConfirmationDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
val dialogTitle = stringResource(R.string.are_you_sure)
val annotatedDialogText = AnnotatedString.fromHtml(
htmlString = stringResource(R.string.router_role_confirmation_text),
linkStyles = TextLinkStyles(style = SpanStyle(color = Color.Blue))
)
val annotatedDialogText =
AnnotatedString.fromHtml(
htmlString = stringResource(R.string.router_role_confirmation_text),
linkStyles = TextLinkStyles(style = SpanStyle(color = Color.Blue)),
)
var confirmed by rememberSaveable { mutableStateOf(false) }
AlertDialog(
title = {
Text(text = dialogTitle)
},
title = { Text(text = dialogTitle) },
text = {
Column {
Text(text = annotatedDialogText)
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(true) {
confirmed = !confirmed
},
verticalAlignment = Alignment.CenterVertically
modifier = Modifier.fillMaxWidth().clickable(true) { confirmed = !confirmed },
verticalAlignment = Alignment.CenterVertically,
) {
Checkbox(
checked = confirmed,
onCheckedChange = { confirmed = it }
)
Checkbox(checked = confirmed, onCheckedChange = { confirmed = it })
Text(stringResource(R.string.i_know_what_i_m_doing))
}
}
},
onDismissRequest = onDismiss,
confirmButton = {
TextButton(
onClick = onConfirm,
enabled = confirmed
) {
Text(stringResource(R.string.accept))
}
TextButton(onClick = onConfirm, enabled = confirmed) { Text(stringResource(R.string.accept)) }
},
dismissButton = {
TextButton(
onClick = onDismiss
) {
Text(stringResource(R.string.cancel))
}
}
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
)
}
@Suppress("LongMethod")
@Composable
fun DeviceConfigItemList(
deviceConfig: DeviceConfig,
enabled: Boolean,
onSaveClicked: (DeviceConfig) -> Unit,
) {
fun DeviceConfigItemList(deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -> Unit) {
val focusManager = LocalFocusManager.current
var deviceInput by rememberSaveable { mutableStateOf(deviceConfig) }
var selectedRole by rememberSaveable { mutableStateOf(deviceInput.role) }
val infrastructureRoles = listOf(
DeviceConfig.Role.ROUTER,
DeviceConfig.Role.REPEATER,
)
val infrastructureRoles = listOf(DeviceConfig.Role.ROUTER, DeviceConfig.Role.REPEATER)
if (selectedRole != deviceInput.role) {
if (selectedRole in infrastructureRoles) {
RouterRoleConfirmationDialog(
onDismiss = { selectedRole = deviceInput.role },
onConfirm = {
deviceInput = deviceInput.copy { role = selectedRole }
}
onConfirm = { deviceInput = deviceInput.copy { role = selectedRole } },
)
} else {
deviceInput = deviceInput.copy { role = selectedRole }
}
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.device_config)) }
item {
@ -202,9 +167,7 @@ fun DeviceConfigItemList(
title = stringResource(R.string.role),
enabled = enabled,
selectedItem = deviceInput.role,
onItemSelected = {
selectedRole = it
},
onItemSelected = { selectedRole = it },
summary = stringResource(id = deviceInput.role.stringRes),
)
HorizontalDivider()
@ -216,9 +179,7 @@ fun DeviceConfigItemList(
value = deviceInput.buttonGpio,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
deviceInput = deviceInput.copy { buttonGpio = it }
}
onValueChanged = { deviceInput = deviceInput.copy { buttonGpio = it } },
)
}
@ -228,9 +189,7 @@ fun DeviceConfigItemList(
value = deviceInput.buzzerGpio,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
deviceInput = deviceInput.copy { buzzerGpio = it }
}
onValueChanged = { deviceInput = deviceInput.copy { buzzerGpio = it } },
)
}
@ -251,9 +210,7 @@ fun DeviceConfigItemList(
value = deviceInput.nodeInfoBroadcastSecs,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it }
}
onValueChanged = { deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } },
)
}
@ -263,7 +220,7 @@ fun DeviceConfigItemList(
summary = stringResource(id = R.string.config_device_doubleTapAsButtonPress_summary),
checked = deviceInput.doubleTapAsButtonPress,
enabled = enabled,
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } }
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } },
)
HorizontalDivider()
}
@ -274,7 +231,7 @@ fun DeviceConfigItemList(
summary = stringResource(id = R.string.config_device_disableTripleClick_summary),
checked = deviceInput.disableTripleClick,
enabled = enabled,
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = it } }
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = it } },
)
HorizontalDivider()
}
@ -286,13 +243,10 @@ fun DeviceConfigItemList(
maxSize = 64, // tzdef max_size:65
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
deviceInput = deviceInput.copy { tzdef = it }
},
onValueChanged = { deviceInput = deviceInput.copy { tzdef = it } },
)
}
@ -302,7 +256,7 @@ fun DeviceConfigItemList(
summary = stringResource(id = R.string.config_device_ledHeartbeatDisabled_summary),
checked = deviceInput.ledHeartbeatDisabled,
enabled = enabled,
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = it } }
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = it } },
)
HorizontalDivider()
}
@ -317,7 +271,7 @@ fun DeviceConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(deviceInput)
}
},
)
}
}
@ -326,9 +280,5 @@ fun DeviceConfigItemList(
@Preview(showBackground = true)
@Composable
private fun DeviceConfigPreview() {
DeviceConfigItemList(
deviceConfig = DeviceConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
)
DeviceConfigItemList(deviceConfig = DeviceConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -41,7 +41,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -45,7 +45,7 @@ import com.geeksville.mesh.R
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.google.protobuf.Descriptors
private const val SupportedFields = 7
private const val SUPPORTED_FIELDS = 7
@Suppress("LongMethod")
@OptIn(ExperimentalLayoutApi::class)
@ -58,10 +58,13 @@ fun EditDeviceProfileDialog(
modifier: Modifier = Modifier,
) {
val state = remember {
val fields = deviceProfile.descriptorForType.fields
.filter { it.number < SupportedFields } // TODO add ringtone & canned messages
mutableStateMapOf<Descriptors.FieldDescriptor, Boolean>()
.apply { putAll(fields.associateWith(deviceProfile::hasField)) }
val fields =
deviceProfile.descriptorForType.fields.filter {
it.number < SUPPORTED_FIELDS
} // TODO add ringtone & canned messages
mutableStateMapOf<Descriptors.FieldDescriptor, Boolean>().apply {
putAll(fields.associateWith(deviceProfile::hasField))
}
}
AlertDialog(
@ -71,38 +74,36 @@ fun EditDeviceProfileDialog(
Column(modifier.fillMaxWidth()) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge.copy(
style =
MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
)
HorizontalDivider()
state.keys.sortedBy { it.number }.forEach { field ->
SwitchPreference(
title = field.name,
checked = state[field] == true,
enabled = deviceProfile.hasField(field),
onCheckedChange = { state[field] = it },
padding = PaddingValues(0.dp)
)
}
state.keys
.sortedBy { it.number }
.forEach { field ->
SwitchPreference(
title = field.name,
checked = state[field] == true,
enabled = deviceProfile.hasField(field),
onCheckedChange = { state[field] = it },
padding = PaddingValues(0.dp),
)
}
HorizontalDivider()
}
},
confirmButton = {
FlowRow(
modifier = modifier
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
modifier = modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
TextButton(
modifier = modifier.weight(1f),
onClick = onDismiss
) { Text(stringResource(R.string.cancel)) }
TextButton(modifier = modifier.weight(1f), onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
Button(
modifier = modifier.weight(1f),
onClick = {
@ -115,9 +116,11 @@ fun EditDeviceProfileDialog(
onConfirm(builder.build())
},
enabled = state.values.any { it },
) { Text(stringResource(R.string.save)) }
) {
Text(stringResource(R.string.save))
}
}
}
},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -44,19 +44,14 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.common.components.TextDividerPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun ExternalNotificationConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
ExternalNotificationConfigItemList(
@ -71,7 +66,7 @@ fun ExternalNotificationConfigScreen(
val config = moduleConfig { externalNotification = extNotificationInput }
viewModel.setModuleConfig(config)
}
}
},
)
}
@ -86,9 +81,7 @@ fun ExternalNotificationConfigItemList(
var ringtoneInput by rememberSaveable { mutableStateOf(ringtone) }
var externalNotificationInput by rememberSaveable { mutableStateOf(extNotificationConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.external_notification_config)) }
item {
@ -96,9 +89,7 @@ fun ExternalNotificationConfigItemList(
title = stringResource(R.string.external_notification_enabled),
checked = externalNotificationInput.enabled,
enabled = enabled,
onCheckedChange = {
externalNotificationInput = externalNotificationInput.copy { this.enabled = it }
}
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { this.enabled = it } },
)
}
@ -109,9 +100,7 @@ fun ExternalNotificationConfigItemList(
title = stringResource(R.string.alert_message_led),
checked = externalNotificationInput.alertMessage,
enabled = enabled,
onCheckedChange = {
externalNotificationInput = externalNotificationInput.copy { alertMessage = it }
}
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertMessage = it } },
)
}
item { HorizontalDivider() }
@ -122,9 +111,8 @@ fun ExternalNotificationConfigItemList(
checked = externalNotificationInput.alertMessageBuzzer,
enabled = enabled,
onCheckedChange = {
externalNotificationInput =
externalNotificationInput.copy { alertMessageBuzzer = it }
}
externalNotificationInput = externalNotificationInput.copy { alertMessageBuzzer = it }
},
)
}
item { HorizontalDivider() }
@ -135,9 +123,8 @@ fun ExternalNotificationConfigItemList(
checked = externalNotificationInput.alertMessageVibra,
enabled = enabled,
onCheckedChange = {
externalNotificationInput =
externalNotificationInput.copy { alertMessageVibra = it }
}
externalNotificationInput = externalNotificationInput.copy { alertMessageVibra = it }
},
)
}
@ -148,9 +135,7 @@ fun ExternalNotificationConfigItemList(
title = stringResource(R.string.alert_bell_led),
checked = externalNotificationInput.alertBell,
enabled = enabled,
onCheckedChange = {
externalNotificationInput = externalNotificationInput.copy { alertBell = it }
}
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertBell = it } },
)
}
item { HorizontalDivider() }
@ -161,9 +146,8 @@ fun ExternalNotificationConfigItemList(
checked = externalNotificationInput.alertBellBuzzer,
enabled = enabled,
onCheckedChange = {
externalNotificationInput =
externalNotificationInput.copy { alertBellBuzzer = it }
}
externalNotificationInput = externalNotificationInput.copy { alertBellBuzzer = it }
},
)
}
item { HorizontalDivider() }
@ -174,9 +158,8 @@ fun ExternalNotificationConfigItemList(
checked = externalNotificationInput.alertBellVibra,
enabled = enabled,
onCheckedChange = {
externalNotificationInput =
externalNotificationInput.copy { alertBellVibra = it }
}
externalNotificationInput = externalNotificationInput.copy { alertBellVibra = it }
},
)
}
item { HorizontalDivider() }
@ -187,23 +170,19 @@ fun ExternalNotificationConfigItemList(
value = externalNotificationInput.output,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
externalNotificationInput = externalNotificationInput.copy { output = it }
}
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { output = it } },
)
}
if (externalNotificationInput.output != 0) {
item {
SwitchPreference(
title = stringResource(R.string.output_led_active_high),
checked = externalNotificationInput.active,
enabled = enabled,
onCheckedChange = {
externalNotificationInput = externalNotificationInput.copy { active = it }
}
)
}
SwitchPreference(
title = stringResource(R.string.output_led_active_high),
checked = externalNotificationInput.active,
enabled = enabled,
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { active = it } },
)
}
}
item { HorizontalDivider() }
@ -213,23 +192,19 @@ fun ExternalNotificationConfigItemList(
value = externalNotificationInput.outputBuzzer,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it }
}
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it } },
)
}
if (externalNotificationInput.outputBuzzer != 0) {
item {
SwitchPreference(
title = stringResource(R.string.use_pwm_buzzer),
checked = externalNotificationInput.usePwm,
enabled = enabled,
onCheckedChange = {
externalNotificationInput = externalNotificationInput.copy { usePwm = it }
}
)
}
SwitchPreference(
title = stringResource(R.string.use_pwm_buzzer),
checked = externalNotificationInput.usePwm,
enabled = enabled,
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { usePwm = it } },
)
}
}
item { HorizontalDivider() }
@ -239,9 +214,7 @@ fun ExternalNotificationConfigItemList(
value = externalNotificationInput.outputVibra,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
externalNotificationInput = externalNotificationInput.copy { outputVibra = it }
}
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputVibra = it } },
)
}
@ -251,9 +224,7 @@ fun ExternalNotificationConfigItemList(
value = externalNotificationInput.outputMs,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
externalNotificationInput = externalNotificationInput.copy { outputMs = it }
}
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputMs = it } },
)
}
@ -263,9 +234,7 @@ fun ExternalNotificationConfigItemList(
value = externalNotificationInput.nagTimeout,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
externalNotificationInput = externalNotificationInput.copy { nagTimeout = it }
}
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { nagTimeout = it } },
)
}
@ -276,11 +245,10 @@ fun ExternalNotificationConfigItemList(
maxSize = 230, // ringtone max_size:231
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { ringtoneInput = it }
onValueChanged = { ringtoneInput = it },
)
}
@ -291,7 +259,7 @@ fun ExternalNotificationConfigItemList(
enabled = enabled,
onCheckedChange = {
externalNotificationInput = externalNotificationInput.copy { useI2SAsBuzzer = it }
}
},
)
}
item { HorizontalDivider() }
@ -307,7 +275,7 @@ fun ExternalNotificationConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(ringtoneInput, externalNotificationInput)
}
},
)
}
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -48,19 +48,14 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SignedIntegerEditTextPreference
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun LoRaConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun LoRaConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
LoRaConfigItemList(
@ -86,13 +81,9 @@ fun LoRaConfigItemList(
) {
val focusManager = LocalFocusManager.current
var loraInput by rememberSaveable { mutableStateOf(loraConfig) }
val primaryChannel by remember(loraInput) {
mutableStateOf(Channel(primarySettings, loraInput))
}
val primaryChannel by remember(loraInput) { mutableStateOf(Channel(primarySettings, loraInput)) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.lora_config)) }
item {
@ -100,7 +91,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.use_modem_preset),
checked = loraInput.usePreset,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } }
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } },
)
}
item { HorizontalDivider() }
@ -110,11 +101,12 @@ fun LoRaConfigItemList(
DropDownPreference(
title = stringResource(R.string.modem_preset),
enabled = enabled && loraInput.usePreset,
items = LoRaConfig.ModemPreset.entries
items =
LoRaConfig.ModemPreset.entries
.filter { it != LoRaConfig.ModemPreset.UNRECOGNIZED }
.map { it to it.name },
selectedItem = loraInput.modemPreset,
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } }
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } },
)
}
item { HorizontalDivider() }
@ -125,7 +117,7 @@ fun LoRaConfigItemList(
value = loraInput.bandwidth,
enabled = enabled && !loraInput.usePreset,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { loraInput = loraInput.copy { bandwidth = it } }
onValueChanged = { loraInput = loraInput.copy { bandwidth = it } },
)
}
@ -135,7 +127,7 @@ fun LoRaConfigItemList(
value = loraInput.spreadFactor,
enabled = enabled && !loraInput.usePreset,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } }
onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } },
)
}
@ -145,7 +137,7 @@ fun LoRaConfigItemList(
value = loraInput.codingRate,
enabled = enabled && !loraInput.usePreset,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { loraInput = loraInput.copy { codingRate = it } }
onValueChanged = { loraInput = loraInput.copy { codingRate = it } },
)
}
}
@ -156,7 +148,7 @@ fun LoRaConfigItemList(
value = loraInput.frequencyOffset,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } }
onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } },
)
}
@ -166,7 +158,7 @@ fun LoRaConfigItemList(
enabled = enabled,
items = RegionInfo.entries.map { it.regionCode to it.description },
selectedItem = loraInput.region,
onItemSelected = { loraInput = loraInput.copy { region = it } }
onItemSelected = { loraInput = loraInput.copy { region = it } },
)
}
item { HorizontalDivider() }
@ -177,7 +169,7 @@ fun LoRaConfigItemList(
value = loraInput.hopLimit,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } }
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } },
)
}
@ -186,7 +178,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.tx_enabled),
checked = loraInput.txEnabled,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } }
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } },
)
}
item { HorizontalDivider() }
@ -213,7 +205,7 @@ fun LoRaConfigItemList(
if (it <= loraInput.numChannels) { // total num of LoRa channels
loraInput = loraInput.copy { channelNum = it }
}
}
},
)
}
@ -222,7 +214,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.override_duty_cycle),
checked = loraInput.overrideDutyCycle,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } }
onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } },
)
}
item { HorizontalDivider() }
@ -235,11 +227,12 @@ fun LoRaConfigItemList(
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValuesChanged = { list ->
loraInput = loraInput.copy {
ignoreIncoming.clear()
ignoreIncoming.addAll(list.filter { it != 0 })
}
}
loraInput =
loraInput.copy {
ignoreIncoming.clear()
ignoreIncoming.addAll(list.filter { it != 0 })
}
},
)
}
@ -248,7 +241,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.sx126x_rx_boosted_gain),
checked = loraInput.sx126XRxBoostedGain,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } }
onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } },
)
}
item { HorizontalDivider() }
@ -257,11 +250,16 @@ fun LoRaConfigItemList(
var isFocused by remember { mutableStateOf(false) }
EditTextPreference(
title = stringResource(R.string.override_frequency_mhz),
value = if (isFocused || loraInput.overrideFrequency != 0f) loraInput.overrideFrequency else primaryChannel.radioFreq,
value =
if (isFocused || loraInput.overrideFrequency != 0f) {
loraInput.overrideFrequency
} else {
primaryChannel.radioFreq
},
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onFocusChanged = { isFocused = it.isFocused },
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } }
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } },
)
}
@ -271,7 +269,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.pa_fan_disabled),
checked = loraInput.paFanDisabled,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { paFanDisabled = it } }
onCheckedChange = { loraInput = loraInput.copy { paFanDisabled = it } },
)
}
item { HorizontalDivider() }
@ -282,7 +280,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.ignore_mqtt),
checked = loraInput.ignoreMqtt,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } }
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } },
)
}
item { HorizontalDivider() }
@ -292,7 +290,7 @@ fun LoRaConfigItemList(
title = stringResource(R.string.ok_to_mqtt),
checked = loraInput.configOkToMqtt,
enabled = enabled,
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } }
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } },
)
}
item { HorizontalDivider() }
@ -307,7 +305,7 @@ fun LoRaConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(loraInput)
}
},
)
}
}
@ -320,6 +318,6 @@ private fun LoRaConfigPreview() {
loraConfig = Channel.default.loraConfig,
primarySettings = Channel.default.settings,
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -17,7 +17,7 @@
@file:Suppress("LongMethod")
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -46,7 +46,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun MQTTConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Column
@ -48,8 +48,9 @@ import com.geeksville.mesh.util.DistanceUnit
import com.geeksville.mesh.util.toDistanceString
import kotlin.math.roundToInt
private const val PositionPrecisionMin = 12
private const val PositionPrecisionMax = 15
private const val POSITION_PRECISION_MIN = 12
private const val POSITION_PRECISION_MAX = 15
@Suppress("LongMethod")
@Composable
fun MapReportingPreference(
@ -62,13 +63,11 @@ fun MapReportingPreference(
publishIntervalSecs: Int = 3600,
onPublishIntervalSecsChanged: (Int) -> Unit = {},
enabled: Boolean,
focusManager: FocusManager
focusManager: FocusManager,
) {
Column {
var showMapReportingWarning by rememberSaveable { mutableStateOf(mapReportingEnabled) }
LaunchedEffect(mapReportingEnabled) {
showMapReportingWarning = mapReportingEnabled
}
LaunchedEffect(mapReportingEnabled) { showMapReportingWarning = mapReportingEnabled }
SwitchPreference(
title = stringResource(R.string.map_reporting),
summary = stringResource(R.string.map_reporting_summary),
@ -81,21 +80,13 @@ fun MapReportingPreference(
} else if (!checked) {
onMapReportingEnabledChanged(false)
}
}
},
)
AnimatedVisibility(showMapReportingWarning) {
Card(
modifier = Modifier.padding(16.dp),
) {
Text(
text = stringResource(R.string.map_reporting_consent_header),
modifier = Modifier.padding(16.dp),
)
Card(modifier = Modifier.padding(16.dp)) {
Text(text = stringResource(R.string.map_reporting_consent_header), modifier = Modifier.padding(16.dp))
HorizontalDivider()
Text(
stringResource(R.string.map_reporting_consent_text),
modifier = Modifier.padding(16.dp)
)
Text(stringResource(R.string.map_reporting_consent_text), modifier = Modifier.padding(16.dp))
SwitchPreference(
title = stringResource(R.string.i_agree),
@ -118,18 +109,14 @@ fun MapReportingPreference(
value = positionPrecision.toFloat(),
onValueChange = { onPositionPrecisionChanged(it.roundToInt()) },
enabled = enabled,
valueRange = PositionPrecisionMin.toFloat()..PositionPrecisionMax.toFloat(),
steps = PositionPrecisionMax - PositionPrecisionMin - 1,
valueRange = POSITION_PRECISION_MIN.toFloat()..POSITION_PRECISION_MAX.toFloat(),
steps = POSITION_PRECISION_MAX - POSITION_PRECISION_MIN - 1,
)
val precisionMeters = precisionBitsToMeters(positionPrecision).toInt()
val unit = DistanceUnit.Companion.getFromLocale()
Text(
text = precisionMeters.toDistanceString(unit),
modifier = Modifier.Companion.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp
),
modifier = Modifier.Companion.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
overflow = TextOverflow.Companion.Ellipsis,
maxLines = 1,
@ -161,6 +148,6 @@ fun MapReportingPreview() {
positionPrecision = 5,
onPositionPrecisionChanged = {},
enabled = true,
focusManager = focusManager
focusManager = focusManager,
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun NeighborInfoConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun NeighborInfoConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
NeighborInfoConfigItemList(
@ -61,7 +56,7 @@ fun NeighborInfoConfigScreen(
onSaveClicked = { neighborInfoInput ->
val config = moduleConfig { neighborInfo = neighborInfoInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -74,9 +69,7 @@ fun NeighborInfoConfigItemList(
val focusManager = LocalFocusManager.current
var neighborInfoInput by rememberSaveable { mutableStateOf(neighborInfoConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.neighbor_info_config)) }
item {
@ -84,9 +77,7 @@ fun NeighborInfoConfigItemList(
title = stringResource(R.string.neighbor_info_enabled),
checked = neighborInfoInput.enabled,
enabled = enabled,
onCheckedChange = {
neighborInfoInput = neighborInfoInput.copy { this.enabled = it }
}
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -97,9 +88,7 @@ fun NeighborInfoConfigItemList(
value = neighborInfoInput.updateInterval,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
neighborInfoInput = neighborInfoInput.copy { updateInterval = it }
}
onValueChanged = { neighborInfoInput = neighborInfoInput.copy { updateInterval = it } },
)
}
@ -109,9 +98,7 @@ fun NeighborInfoConfigItemList(
summary = stringResource(id = R.string.config_device_transmitOverLora_summary),
checked = neighborInfoInput.transmitOverLora,
enabled = enabled,
onCheckedChange = {
neighborInfoInput = neighborInfoInput.copy { transmitOverLora = it }
}
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { transmitOverLora = it } },
)
HorizontalDivider()
}
@ -126,7 +113,7 @@ fun NeighborInfoConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(neighborInfoInput)
}
},
)
}
}
@ -138,6 +125,6 @@ private fun NeighborInfoConfigPreview() {
NeighborInfoConfigItemList(
neighborInfoConfig = ModuleConfigProtos.ModuleConfig.NeighborInfoConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.layout.fillMaxSize
@ -54,30 +54,20 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
@Composable
private fun ScanErrorDialog(
onDismiss: () -> Unit = {}
) = SimpleAlertDialog(
title = R.string.error,
text = R.string.wifi_qr_code_error,
onDismiss = onDismiss,
)
private fun ScanErrorDialog(onDismiss: () -> Unit = {}) =
SimpleAlertDialog(title = R.string.error, text = R.string.wifi_qr_code_error, onDismiss = onDismiss)
@Composable
fun NetworkConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
NetworkConfigItemList(
@ -88,13 +78,13 @@ fun NetworkConfigScreen(
onSaveClicked = { networkInput ->
val config = config { network = networkInput }
viewModel.setConfig(config)
}
},
)
}
private fun extractWifiCredentials(qrCode: String) = Regex("""WIFI:S:(.*?);.*?P:(.*?);""")
.find(qrCode)?.destructured
?.let { (ssid, password) -> ssid to password } ?: (null to null)
private fun extractWifiCredentials(qrCode: String) =
Regex("""WIFI:S:(.*?);.*?P:(.*?);""").find(qrCode)?.destructured?.let { (ssid, password) -> ssid to password }
?: (null to null)
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
@ -113,33 +103,34 @@ fun NetworkConfigItemList(
ScanErrorDialog { showScanErrorDialog = false }
}
val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
val (ssid, psk) = extractWifiCredentials(result.contents)
if (ssid != null && psk != null) {
networkInput = networkInput.copy {
wifiSsid = ssid
wifiPsk = psk
val barcodeLauncher =
rememberLauncherForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
val (ssid, psk) = extractWifiCredentials(result.contents)
if (ssid != null && psk != null) {
networkInput =
networkInput.copy {
wifiSsid = ssid
wifiPsk = psk
}
} else {
showScanErrorDialog = true
}
} else {
showScanErrorDialog = true
}
}
}
fun zxingScan() {
val zxingScan = ScanOptions().apply {
setCameraId(0)
setPrompt("")
setBeepEnabled(false)
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
}
val zxingScan =
ScanOptions().apply {
setCameraId(0)
setPrompt("")
setBeepEnabled(false)
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
}
barcodeLauncher.launch(zxingScan)
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.network_config)) }
item {
@ -147,7 +138,7 @@ fun NetworkConfigItemList(
title = stringResource(R.string.wifi_enabled),
checked = networkInput.wifiEnabled,
enabled = enabled && hasWifi,
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } }
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } },
)
HorizontalDivider()
}
@ -159,13 +150,10 @@ fun NetworkConfigItemList(
maxSize = 32, // wifi_ssid max_size:33
enabled = enabled && hasWifi,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
networkInput = networkInput.copy { wifiSsid = it }
}
onValueChanged = { networkInput = networkInput.copy { wifiSsid = it } },
)
}
@ -176,17 +164,14 @@ fun NetworkConfigItemList(
maxSize = 64, // wifi_psk max_size:65
enabled = enabled && hasWifi,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } }
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } },
)
}
item {
Button(
onClick = { zxingScan() },
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.height(48.dp),
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).height(48.dp),
enabled = enabled && hasWifi,
) {
Text(text = stringResource(R.string.wifi_qr_code_scan))
@ -198,7 +183,7 @@ fun NetworkConfigItemList(
title = stringResource(R.string.ethernet_enabled),
checked = networkInput.ethEnabled,
enabled = enabled && hasEthernet,
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } }
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } },
)
HorizontalDivider()
}
@ -210,13 +195,10 @@ fun NetworkConfigItemList(
maxSize = 32, // ntp_server max_size:33
enabled = enabled,
isError = networkInput.ntpServer.isEmpty(),
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
networkInput = networkInput.copy { ntpServer = it }
}
onValueChanged = { networkInput = networkInput.copy { ntpServer = it } },
)
}
@ -227,13 +209,10 @@ fun NetworkConfigItemList(
maxSize = 32, // rsyslog_server max_size:33
enabled = enabled,
isError = false,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
networkInput = networkInput.copy { rsyslogServer = it }
}
onValueChanged = { networkInput = networkInput.copy { rsyslogServer = it } },
)
}
@ -241,11 +220,12 @@ fun NetworkConfigItemList(
DropDownPreference(
title = stringResource(R.string.ipv4_mode),
enabled = enabled,
items = NetworkConfig.AddressMode.entries
items =
NetworkConfig.AddressMode.entries
.filter { it != NetworkConfig.AddressMode.UNRECOGNIZED }
.map { it to it.name },
selectedItem = networkInput.addressMode,
onItemSelected = { networkInput = networkInput.copy { addressMode = it } }
onItemSelected = { networkInput = networkInput.copy { addressMode = it } },
)
HorizontalDivider()
}
@ -259,7 +239,7 @@ fun NetworkConfigItemList(
onValueChanged = {
val ipv4 = networkInput.ipv4Config.copy { ip = it }
networkInput = networkInput.copy { ipv4Config = ipv4 }
}
},
)
}
@ -272,7 +252,7 @@ fun NetworkConfigItemList(
onValueChanged = {
val ipv4 = networkInput.ipv4Config.copy { gateway = it }
networkInput = networkInput.copy { ipv4Config = ipv4 }
}
},
)
}
@ -285,7 +265,7 @@ fun NetworkConfigItemList(
onValueChanged = {
val ipv4 = networkInput.ipv4Config.copy { subnet = it }
networkInput = networkInput.copy { ipv4Config = ipv4 }
}
},
)
}
@ -298,14 +278,12 @@ fun NetworkConfigItemList(
onValueChanged = {
val ipv4 = networkInput.ipv4Config.copy { dns = it }
networkInput = networkInput.copy { ipv4Config = ipv4 }
}
},
)
}
item { HorizontalDivider() }
if (hasEthernet || hasWifi) {
item {
PreferenceCategory(text = stringResource(R.string.udp_config))
}
item { PreferenceCategory(text = stringResource(R.string.udp_config)) }
item {
SwitchPreference(
@ -313,11 +291,8 @@ fun NetworkConfigItemList(
checked = networkInput.enabledProtocols == 1,
enabled = enabled,
onCheckedChange = {
networkInput =
networkInput.copy {
if (it) enabledProtocols = 1 else enabledProtocols = 0
}
}
networkInput = networkInput.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
},
)
}
@ -333,7 +308,7 @@ fun NetworkConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(networkInput)
}
},
)
}
}
@ -347,7 +322,7 @@ private fun NetworkConfigPreview() {
hasEthernet = true,
networkConfig = NetworkConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.animation.core.animateFloatAsState
@ -37,35 +37,24 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.ui.radioconfig.ResponseState
import com.geeksville.mesh.ui.settings.radio.ResponseState
@Composable
fun <T> PacketResponseStateDialog(
state: ResponseState<T>,
onDismiss: () -> Unit = {},
onComplete: () -> Unit = {},
) {
fun <T> PacketResponseStateDialog(state: ResponseState<T>, onDismiss: () -> Unit = {}, onComplete: () -> Unit = {}) {
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
AlertDialog(
onDismissRequest = {},
shape = RoundedCornerShape(16.dp),
title = {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
if (state is ResponseState.Loading) {
val progress by animateFloatAsState(
targetValue = state.completed.toFloat() / state.total.toFloat(),
label = "progress",
)
val progress by
animateFloatAsState(
targetValue = state.completed.toFloat() / state.total.toFloat(),
label = "progress",
)
Text("%.0f%%".format(progress * 100))
LinearProgressIndicator(
progress = progress,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
)
LinearProgressIndicator(progress = progress, modifier = Modifier.fillMaxWidth().padding(top = 8.dp))
if (state.total == state.completed) onComplete()
}
if (state is ResponseState.Success) {
@ -79,10 +68,8 @@ fun <T> PacketResponseStateDialog(
},
confirmButton = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.Center
modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.Center,
) {
Button(
onClick = {
@ -91,20 +78,17 @@ fun <T> PacketResponseStateDialog(
backDispatcher?.onBackPressed()
}
},
modifier = Modifier.padding(top = 16.dp)
) { Text(stringResource(R.string.close)) }
modifier = Modifier.padding(top = 16.dp),
) {
Text(stringResource(R.string.close))
}
}
}
},
)
}
@Preview(showBackground = true)
@Composable
private fun PacketResponseStateDialogPreview() {
PacketResponseStateDialog(
state = ResponseState.Loading(
total = 17,
completed = 5,
),
)
PacketResponseStateDialog(state = ResponseState.Loading(total = 17, completed = 5))
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun PaxcounterConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
PaxcounterConfigItemList(
@ -61,7 +56,7 @@ fun PaxcounterConfigScreen(
onSaveClicked = { paxcounterConfigInput ->
val config = moduleConfig { paxcounter = paxcounterConfigInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -75,9 +70,7 @@ fun PaxcounterConfigItemList(
val focusManager = LocalFocusManager.current
var paxcounterInput by rememberSaveable { mutableStateOf(paxcounterConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.paxcounter_config)) }
item {
@ -85,9 +78,7 @@ fun PaxcounterConfigItemList(
title = stringResource(R.string.paxcounter_enabled),
checked = paxcounterInput.enabled,
enabled = enabled,
onCheckedChange = {
paxcounterInput = paxcounterInput.copy { this.enabled = it }
}
onCheckedChange = { paxcounterInput = paxcounterInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -98,9 +89,7 @@ fun PaxcounterConfigItemList(
value = paxcounterInput.paxcounterUpdateInterval,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
paxcounterInput = paxcounterInput.copy { paxcounterUpdateInterval = it }
}
onValueChanged = { paxcounterInput = paxcounterInput.copy { paxcounterUpdateInterval = it } },
)
}
@ -110,9 +99,7 @@ fun PaxcounterConfigItemList(
value = paxcounterInput.wifiThreshold,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
paxcounterInput = paxcounterInput.copy { wifiThreshold = it }
}
onValueChanged = { paxcounterInput = paxcounterInput.copy { wifiThreshold = it } },
)
}
@ -122,9 +109,7 @@ fun PaxcounterConfigItemList(
value = paxcounterInput.bleThreshold,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
paxcounterInput = paxcounterInput.copy { bleThreshold = it }
}
onValueChanged = { paxcounterInput = paxcounterInput.copy { bleThreshold = it } },
)
}
@ -138,7 +123,7 @@ fun PaxcounterConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(paxcounterInput)
}
},
)
}
}
@ -150,6 +135,6 @@ private fun PaxcounterConfigPreview() {
PaxcounterConfigItemList(
paxcounterConfig = ModuleConfigProtos.ModuleConfig.PaxcounterConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import android.Manifest
import android.annotation.SuppressLint
@ -54,7 +54,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import kotlinx.coroutines.launch

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,7 +40,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun RangeTestConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
RangeTestConfigItemList(
@ -61,7 +56,7 @@ fun RangeTestConfigScreen(
onSaveClicked = { rangeTestInput ->
val config = moduleConfig { rangeTest = rangeTestInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -74,9 +69,7 @@ fun RangeTestConfigItemList(
val focusManager = LocalFocusManager.current
var rangeTestInput by rememberSaveable { mutableStateOf(rangeTestConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.range_test_config)) }
item {
@ -84,7 +77,7 @@ fun RangeTestConfigItemList(
title = stringResource(R.string.range_test_enabled),
checked = rangeTestInput.enabled,
enabled = enabled,
onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } }
onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -95,7 +88,7 @@ fun RangeTestConfigItemList(
value = rangeTestInput.sender,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } }
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } },
)
}
@ -104,7 +97,7 @@ fun RangeTestConfigItemList(
title = stringResource(R.string.save_csv_in_storage_esp32_only),
checked = rangeTestInput.save,
enabled = enabled,
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } }
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } },
)
}
item { HorizontalDivider() }
@ -119,7 +112,7 @@ fun RangeTestConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(rangeTestInput)
}
},
)
}
}
@ -128,9 +121,5 @@ fun RangeTestConfigItemList(
@Preview(showBackground = true)
@Composable
private fun RangeTestConfig() {
RangeTestConfigItemList(
rangeTestConfig = RangeTestConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
)
RangeTestConfigItemList(rangeTestConfig = RangeTestConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditListPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun RemoteHardwareConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun RemoteHardwareConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
RemoteHardwareConfigItemList(
@ -61,7 +56,7 @@ fun RemoteHardwareConfigScreen(
onSaveClicked = { remoteHardwareInput ->
val config = moduleConfig { remoteHardware = remoteHardwareInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -74,9 +69,7 @@ fun RemoteHardwareConfigItemList(
val focusManager = LocalFocusManager.current
var remoteHardwareInput by rememberSaveable { mutableStateOf(remoteHardwareConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.remote_hardware_config)) }
item {
@ -84,9 +77,7 @@ fun RemoteHardwareConfigItemList(
title = stringResource(R.string.remote_hardware_enabled),
checked = remoteHardwareInput.enabled,
enabled = enabled,
onCheckedChange = {
remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it }
}
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -96,9 +87,7 @@ fun RemoteHardwareConfigItemList(
title = stringResource(R.string.allow_undefined_pin_access),
checked = remoteHardwareInput.allowUndefinedPinAccess,
enabled = enabled,
onCheckedChange = {
remoteHardwareInput = remoteHardwareInput.copy { allowUndefinedPinAccess = it }
}
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { allowUndefinedPinAccess = it } },
)
}
item { HorizontalDivider() }
@ -111,11 +100,12 @@ fun RemoteHardwareConfigItemList(
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValuesChanged = { list ->
remoteHardwareInput = remoteHardwareInput.copy {
availablePins.clear()
availablePins.addAll(list)
}
}
remoteHardwareInput =
remoteHardwareInput.copy {
availablePins.clear()
availablePins.addAll(list)
}
},
)
}
@ -129,7 +119,7 @@ fun RemoteHardwareConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(remoteHardwareInput)
}
},
)
}
}
@ -141,6 +131,6 @@ private fun RemoteHardwareConfigPreview() {
RemoteHardwareConfigItemList(
remoteHardwareConfig = RemoteHardwareConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import android.app.Activity
import android.content.Intent
@ -58,24 +58,19 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.node.NodeActionButton
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.geeksville.mesh.util.encodeToString
import com.geeksville.mesh.util.toByteString
import com.google.protobuf.ByteString
import java.security.SecureRandom
@Composable
fun SecurityConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val node by viewModel.destNode.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
SecurityConfigItemList(
@ -86,9 +81,7 @@ fun SecurityConfigScreen(
val config = config { security = securityInput }
viewModel.setConfig(config)
},
onExport = { uri, securityConfig ->
viewModel.exportSecurityConfig(uri, securityConfig)
},
onExport = { uri, securityConfig -> viewModel.exportSecurityConfig(uri, securityConfig) },
)
}
@ -114,13 +107,12 @@ fun SecurityConfigItemList(
}
}
val exportConfigLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri -> onExport(uri, securityConfig) }
val exportConfigLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri -> onExport(uri, securityConfig) }
}
}
}
var showKeyGenerationDialog by rememberSaveable { mutableStateOf(false) }
PrivateKeyRegenerateDialog(
@ -131,7 +123,7 @@ fun SecurityConfigItemList(
showKeyGenerationDialog = false
onConfirm(securityInput)
},
onDismiss = { showKeyGenerationDialog = false }
onDismiss = { showKeyGenerationDialog = false },
)
var showEditSecurityConfigDialog by rememberSaveable { mutableStateOf(false) }
if (showEditSecurityConfigDialog) {
@ -143,14 +135,15 @@ fun SecurityConfigItemList(
TextButton(
onClick = {
showEditSecurityConfigDialog = false
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
putExtra(
Intent.EXTRA_TITLE,
"${user?.shortName}_keys_${System.currentTimeMillis()}.json"
)
}
val intent =
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
putExtra(
Intent.EXTRA_TITLE,
"${user?.shortName}_keys_${System.currentTimeMillis()}.json",
)
}
exportConfigLauncher.launch(intent)
},
) {
@ -160,9 +153,7 @@ fun SecurityConfigItemList(
)
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.security_config)) }
item {
@ -177,11 +168,7 @@ fun SecurityConfigItemList(
securityInput = securityInput.copy { publicKey = it }
}
},
trailingIcon = {
CopyIconButton(
valueToCopy = securityInput.publicKey.encodeToString(),
)
}
trailingIcon = { CopyIconButton(valueToCopy = securityInput.publicKey.encodeToString()) },
)
}
@ -196,11 +183,7 @@ fun SecurityConfigItemList(
securityInput = securityInput.copy { privateKey = it }
}
},
trailingIcon = {
CopyIconButton(
valueToCopy = securityInput.privateKey.encodeToString(),
)
}
trailingIcon = { CopyIconButton(valueToCopy = securityInput.privateKey.encodeToString()) },
)
}
@ -210,9 +193,7 @@ fun SecurityConfigItemList(
title = stringResource(R.string.regenerate_private_key),
enabled = enabled,
icon = Icons.TwoTone.Warning,
onClick = {
showKeyGenerationDialog = true
}
onClick = { showKeyGenerationDialog = true },
)
}
@ -222,9 +203,7 @@ fun SecurityConfigItemList(
title = stringResource(R.string.export_keys),
enabled = enabled,
icon = Icons.TwoTone.Warning,
onClick = {
showEditSecurityConfigDialog = true
}
onClick = { showEditSecurityConfigDialog = true },
)
}
@ -236,10 +215,11 @@ fun SecurityConfigItemList(
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValuesChanged = {
securityInput = securityInput.copy {
adminKey.clear()
adminKey.addAll(it)
}
securityInput =
securityInput.copy {
adminKey.clear()
adminKey.addAll(it)
}
},
)
}
@ -249,9 +229,7 @@ fun SecurityConfigItemList(
title = stringResource(R.string.managed_mode),
checked = securityInput.isManaged,
enabled = enabled && securityInput.adminKeyCount > 0,
onCheckedChange = {
securityInput = securityInput.copy { isManaged = it }
}
onCheckedChange = { securityInput = securityInput.copy { isManaged = it } },
)
}
item { HorizontalDivider() }
@ -261,7 +239,7 @@ fun SecurityConfigItemList(
title = stringResource(R.string.serial_console),
checked = securityInput.serialEnabled,
enabled = enabled,
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } }
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } },
)
}
item { HorizontalDivider() }
@ -271,9 +249,7 @@ fun SecurityConfigItemList(
title = stringResource(R.string.debug_log_api_enabled),
checked = securityInput.debugLogApiEnabled,
enabled = enabled,
onCheckedChange = {
securityInput = securityInput.copy { debugLogApiEnabled = it }
}
onCheckedChange = { securityInput = securityInput.copy { debugLogApiEnabled = it } },
)
}
item { HorizontalDivider() }
@ -283,9 +259,7 @@ fun SecurityConfigItemList(
title = stringResource(R.string.legacy_admin_channel),
checked = securityInput.adminChannelEnabled,
enabled = enabled,
onCheckedChange = {
securityInput = securityInput.copy { adminChannelEnabled = it }
}
onCheckedChange = { securityInput = securityInput.copy { adminChannelEnabled = it } },
)
}
item { HorizontalDivider() }
@ -300,7 +274,7 @@ fun SecurityConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onConfirm(securityInput)
}
},
)
}
}
@ -323,34 +297,27 @@ fun PrivateKeyRegenerateDialog(
confirmButton = {
TextButton(
onClick = {
securityInput = securityInput.copy {
clearPrivateKey()
clearPublicKey()
// Generate a random "f" value
val f = ByteArray(32).apply {
SecureRandom().nextBytes(this)
securityInput =
securityInput.copy {
clearPrivateKey()
clearPublicKey()
// Generate a random "f" value
val f = ByteArray(32).apply { SecureRandom().nextBytes(this) }
// Adjust the value to make it valid as an "s" value for eval().
// According to the specification we need to mask off the 3
// right-most bits of f[0], mask off the left-most bit of f[31],
// and set the second to left-most bit of f[31].
f[0] = (f[0].toInt() and 0xF8).toByte()
f[31] = ((f[31].toInt() and 0x7F) or 0x40).toByte()
privateKey = ByteString.copyFrom(f)
}
// Adjust the value to make it valid as an "s" value for eval().
// According to the specification we need to mask off the 3
// right-most bits of f[0], mask off the left-most bit of f[31],
// and set the second to left-most bit of f[31].
f[0] = (f[0].toInt() and 0xF8).toByte()
f[31] = ((f[31].toInt() and 0x7F) or 0x40).toByte()
privateKey = ByteString.copyFrom(f)
}
onConfirm(securityInput)
},
) {
Text(stringResource(R.string.okay))
}
},
dismissButton = {
TextButton(
onClick = onDismiss,
) {
Text(stringResource(R.string.cancel))
}
}
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
)
}
}
@ -358,9 +325,5 @@ fun PrivateKeyRegenerateDialog(
@Preview(showBackground = true)
@Composable
private fun SecurityConfigPreview() {
SecurityConfigItemList(
securityConfig = SecurityConfig.getDefaultInstance(),
enabled = true,
onConfirm = {},
)
SecurityConfigItemList(securityConfig = SecurityConfig.getDefaultInstance(), enabled = true, onConfirm = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -41,19 +41,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun SerialConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
SerialConfigItemList(
@ -62,22 +57,17 @@ fun SerialConfigScreen(
onSaveClicked = { serialInput ->
val config = moduleConfig { serial = serialInput }
viewModel.setModuleConfig(config)
}
},
)
}
@Suppress("LongMethod")
@Composable
fun SerialConfigItemList(
serialConfig: SerialConfig,
enabled: Boolean,
onSaveClicked: (SerialConfig) -> Unit,
) {
fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveClicked: (SerialConfig) -> Unit) {
val focusManager = LocalFocusManager.current
var serialInput by rememberSaveable { mutableStateOf(serialConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.serial_config)) }
item {
@ -85,7 +75,7 @@ fun SerialConfigItemList(
title = stringResource(R.string.serial_enabled),
checked = serialInput.enabled,
enabled = enabled,
onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } }
onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -95,7 +85,7 @@ fun SerialConfigItemList(
title = stringResource(R.string.echo_enabled),
checked = serialInput.echo,
enabled = enabled,
onCheckedChange = { serialInput = serialInput.copy { echo = it } }
onCheckedChange = { serialInput = serialInput.copy { echo = it } },
)
}
item { HorizontalDivider() }
@ -106,7 +96,7 @@ fun SerialConfigItemList(
value = serialInput.rxd,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { serialInput = serialInput.copy { rxd = it } }
onValueChanged = { serialInput = serialInput.copy { rxd = it } },
)
}
@ -116,7 +106,7 @@ fun SerialConfigItemList(
value = serialInput.txd,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { serialInput = serialInput.copy { txd = it } }
onValueChanged = { serialInput = serialInput.copy { txd = it } },
)
}
@ -124,11 +114,12 @@ fun SerialConfigItemList(
DropDownPreference(
title = stringResource(R.string.serial_baud_rate),
enabled = enabled,
items = SerialConfig.Serial_Baud.entries
items =
SerialConfig.Serial_Baud.entries
.filter { it != SerialConfig.Serial_Baud.UNRECOGNIZED }
.map { it to it.name },
selectedItem = serialInput.baud,
onItemSelected = { serialInput = serialInput.copy { baud = it } }
onItemSelected = { serialInput = serialInput.copy { baud = it } },
)
}
item { HorizontalDivider() }
@ -139,7 +130,7 @@ fun SerialConfigItemList(
value = serialInput.timeout,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { serialInput = serialInput.copy { timeout = it } }
onValueChanged = { serialInput = serialInput.copy { timeout = it } },
)
}
@ -147,11 +138,12 @@ fun SerialConfigItemList(
DropDownPreference(
title = stringResource(R.string.serial_mode),
enabled = enabled,
items = SerialConfig.Serial_Mode.entries
items =
SerialConfig.Serial_Mode.entries
.filter { it != SerialConfig.Serial_Mode.UNRECOGNIZED }
.map { it to it.name },
selectedItem = serialInput.mode,
onItemSelected = { serialInput = serialInput.copy { mode = it } }
onItemSelected = { serialInput = serialInput.copy { mode = it } },
)
}
item { HorizontalDivider() }
@ -161,9 +153,7 @@ fun SerialConfigItemList(
title = stringResource(R.string.override_console_serial_port),
checked = serialInput.overrideConsoleSerialPort,
enabled = enabled,
onCheckedChange = {
serialInput = serialInput.copy { overrideConsoleSerialPort = it }
}
onCheckedChange = { serialInput = serialInput.copy { overrideConsoleSerialPort = it } },
)
}
item { HorizontalDivider() }
@ -178,7 +168,7 @@ fun SerialConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(serialInput)
}
},
)
}
}
@ -187,9 +177,5 @@ fun SerialConfigItemList(
@Preview(showBackground = true)
@Composable
private fun SerialConfigPreview() {
SerialConfigItemList(
serialConfig = SerialConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
)
SerialConfigItemList(serialConfig = SerialConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun StoreForwardConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun StoreForwardConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
StoreForwardConfigItemList(
@ -61,7 +56,7 @@ fun StoreForwardConfigScreen(
onSaveClicked = { storeForwardInput ->
val config = moduleConfig { storeForward = storeForwardInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -74,9 +69,7 @@ fun StoreForwardConfigItemList(
val focusManager = LocalFocusManager.current
var storeForwardInput by rememberSaveable { mutableStateOf(storeForwardConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.store_forward_config)) }
item {
@ -84,9 +77,7 @@ fun StoreForwardConfigItemList(
title = stringResource(R.string.store_forward_enabled),
checked = storeForwardInput.enabled,
enabled = enabled,
onCheckedChange = {
storeForwardInput = storeForwardInput.copy { this.enabled = it }
}
onCheckedChange = { storeForwardInput = storeForwardInput.copy { this.enabled = it } },
)
}
item { HorizontalDivider() }
@ -96,7 +87,7 @@ fun StoreForwardConfigItemList(
title = stringResource(R.string.heartbeat),
checked = storeForwardInput.heartbeat,
enabled = enabled,
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } }
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } },
)
}
item { HorizontalDivider() }
@ -107,7 +98,7 @@ fun StoreForwardConfigItemList(
value = storeForwardInput.records,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } }
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } },
)
}
@ -117,9 +108,7 @@ fun StoreForwardConfigItemList(
value = storeForwardInput.historyReturnMax,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
storeForwardInput = storeForwardInput.copy { historyReturnMax = it }
}
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnMax = it } },
)
}
@ -129,9 +118,7 @@ fun StoreForwardConfigItemList(
value = storeForwardInput.historyReturnWindow,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
storeForwardInput = storeForwardInput.copy { historyReturnWindow = it }
}
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnWindow = it } },
)
}
@ -140,7 +127,7 @@ fun StoreForwardConfigItemList(
title = stringResource(R.string.server),
checked = storeForwardInput.isServer,
enabled = enabled,
onCheckedChange = { storeForwardInput = storeForwardInput.copy { isServer = it } }
onCheckedChange = { storeForwardInput = storeForwardInput.copy { isServer = it } },
)
}
item { HorizontalDivider() }
@ -155,7 +142,7 @@ fun StoreForwardConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(storeForwardInput)
}
},
)
}
}
@ -167,6 +154,6 @@ private fun StoreForwardConfigPreview() {
StoreForwardConfigItemList(
storeForwardConfig = StoreForwardConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
onSaveClicked = {},
)
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@Composable
fun TelemetryConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
TelemetryConfigItemList(
@ -61,7 +56,7 @@ fun TelemetryConfigScreen(
onSaveClicked = { telemetryInput ->
val config = moduleConfig { telemetry = telemetryInput }
viewModel.setModuleConfig(config)
}
},
)
}
@ -74,9 +69,7 @@ fun TelemetryConfigItemList(
val focusManager = LocalFocusManager.current
var telemetryInput by rememberSaveable { mutableStateOf(telemetryConfig) }
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.telemetry_config)) }
item {
@ -85,9 +78,7 @@ fun TelemetryConfigItemList(
value = telemetryInput.deviceUpdateInterval,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
telemetryInput = telemetryInput.copy { deviceUpdateInterval = it }
}
onValueChanged = { telemetryInput = telemetryInput.copy { deviceUpdateInterval = it } },
)
}
@ -97,9 +88,7 @@ fun TelemetryConfigItemList(
value = telemetryInput.environmentUpdateInterval,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
telemetryInput = telemetryInput.copy { environmentUpdateInterval = it }
}
onValueChanged = { telemetryInput = telemetryInput.copy { environmentUpdateInterval = it } },
)
}
@ -108,9 +97,7 @@ fun TelemetryConfigItemList(
title = stringResource(R.string.environment_metrics_module_enabled),
checked = telemetryInput.environmentMeasurementEnabled,
enabled = enabled,
onCheckedChange = {
telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it }
}
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it } },
)
}
item { HorizontalDivider() }
@ -120,9 +107,7 @@ fun TelemetryConfigItemList(
title = stringResource(R.string.environment_metrics_on_screen_enabled),
checked = telemetryInput.environmentScreenEnabled,
enabled = enabled,
onCheckedChange = {
telemetryInput = telemetryInput.copy { environmentScreenEnabled = it }
}
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentScreenEnabled = it } },
)
}
item { HorizontalDivider() }
@ -132,9 +117,7 @@ fun TelemetryConfigItemList(
title = stringResource(R.string.environment_metrics_use_fahrenheit),
checked = telemetryInput.environmentDisplayFahrenheit,
enabled = enabled,
onCheckedChange = {
telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it }
}
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it } },
)
}
item { HorizontalDivider() }
@ -144,9 +127,7 @@ fun TelemetryConfigItemList(
title = stringResource(R.string.air_quality_metrics_module_enabled),
checked = telemetryInput.airQualityEnabled,
enabled = enabled,
onCheckedChange = {
telemetryInput = telemetryInput.copy { airQualityEnabled = it }
}
onCheckedChange = { telemetryInput = telemetryInput.copy { airQualityEnabled = it } },
)
}
item { HorizontalDivider() }
@ -157,9 +138,7 @@ fun TelemetryConfigItemList(
value = telemetryInput.airQualityInterval,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
telemetryInput = telemetryInput.copy { airQualityInterval = it }
}
onValueChanged = { telemetryInput = telemetryInput.copy { airQualityInterval = it } },
)
}
@ -168,9 +147,7 @@ fun TelemetryConfigItemList(
title = stringResource(R.string.power_metrics_module_enabled),
checked = telemetryInput.powerMeasurementEnabled,
enabled = enabled,
onCheckedChange = {
telemetryInput = telemetryInput.copy { powerMeasurementEnabled = it }
}
onCheckedChange = { telemetryInput = telemetryInput.copy { powerMeasurementEnabled = it } },
)
}
item { HorizontalDivider() }
@ -181,9 +158,7 @@ fun TelemetryConfigItemList(
value = telemetryInput.powerUpdateInterval,
enabled = enabled,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
telemetryInput = telemetryInput.copy { powerUpdateInterval = it }
}
onValueChanged = { telemetryInput = telemetryInput.copy { powerUpdateInterval = it } },
)
}
@ -192,9 +167,7 @@ fun TelemetryConfigItemList(
title = stringResource(R.string.power_metrics_on_screen_enabled),
checked = telemetryInput.powerScreenEnabled,
enabled = enabled,
onCheckedChange = {
telemetryInput = telemetryInput.copy { powerScreenEnabled = it }
}
onCheckedChange = { telemetryInput = telemetryInput.copy { powerScreenEnabled = it } },
)
}
item { HorizontalDivider() }
@ -209,7 +182,7 @@ fun TelemetryConfigItemList(
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(telemetryInput)
}
},
)
}
}
@ -218,9 +191,5 @@ fun TelemetryConfigItemList(
@Preview(showBackground = true)
@Composable
private fun TelemetryConfigPreview() {
TelemetryConfigItemList(
telemetryConfig = TelemetryConfig.getDefaultInstance(),
enabled = true,
onSaveClicked = { },
)
TelemetryConfigItemList(telemetryConfig = TelemetryConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
}

View file

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.ui.radioconfig.components
package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@ -46,20 +46,15 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.common.components.RegularPreference
import com.geeksville.mesh.ui.common.components.SwitchPreference
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.geeksville.mesh.user
@Composable
fun UserConfigScreen(
viewModel: RadioConfigViewModel = hiltViewModel(),
) {
fun UserConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
if (state.responseState.isWaiting()) {
PacketResponseStateDialog(
state = state.responseState,
onDismiss = viewModel::clearPacketResponse,
)
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
}
UserConfigItemList(
@ -85,18 +80,10 @@ fun UserConfigItemList(
val validLongName = userInput.longName.isNotBlank()
val validShortName = userInput.shortName.isNotBlank()
val validNames = validLongName && validShortName
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
LazyColumn(modifier = Modifier.fillMaxSize()) {
item { PreferenceCategory(text = stringResource(R.string.user_config)) }
item {
RegularPreference(
title = stringResource(R.string.node_id),
subtitle = userInput.id,
onClick = {}
)
}
item { RegularPreference(title = stringResource(R.string.node_id), subtitle = userInput.id, onClick = {}) }
item { HorizontalDivider() }
item {
@ -106,13 +93,10 @@ fun UserConfigItemList(
maxSize = 39, // long_name max_size:40
enabled = enabled,
isError = !validLongName,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = {
userInput = userInput.copy { longName = it }
}
onValueChanged = { userInput = userInput.copy { longName = it } },
)
}
@ -123,11 +107,10 @@ fun UserConfigItemList(
maxSize = 4, // short_name max_size:5
enabled = enabled,
isError = !validShortName,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
),
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
onValueChanged = { userInput = userInput.copy { shortName = it } }
onValueChanged = { userInput = userInput.copy { shortName = it } },
)
}
@ -135,7 +118,7 @@ fun UserConfigItemList(
RegularPreference(
title = stringResource(R.string.hardware_model),
subtitle = userInput.hwModel.name,
onClick = {}
onClick = {},
)
}
item { HorizontalDivider() }
@ -144,12 +127,11 @@ fun UserConfigItemList(
SwitchPreference(
title = stringResource(R.string.unmessageable),
summary = stringResource(R.string.unmonitored_or_infrastructure),
checked = userInput.isUnmessagable || (
firmwareVersion < DeviceVersion("2.6.9") &&
userInput.role.isUnmessageableRole()
),
checked =
userInput.isUnmessagable ||
(firmwareVersion < DeviceVersion("2.6.9") && userInput.role.isUnmessageableRole()),
enabled = userInput.hasIsUnmessagable() || firmwareVersion >= DeviceVersion("2.6.9"),
onCheckedChange = { userInput = userInput.copy { isUnmessagable = it } }
onCheckedChange = { userInput = userInput.copy { isUnmessagable = it } },
)
}
@ -161,7 +143,7 @@ fun UserConfigItemList(
summary = stringResource(R.string.licensed_amateur_radio_text),
checked = userInput.isLicensed,
enabled = enabled,
onCheckedChange = { userInput = userInput.copy { isLicensed = it } }
onCheckedChange = { userInput = userInput.copy { isLicensed = it } },
)
}
item { HorizontalDivider() }
@ -172,10 +154,11 @@ fun UserConfigItemList(
onCancelClicked = {
focusManager.clearFocus()
userInput = userConfig
}, onSaveClicked = {
},
onSaveClicked = {
focusManager.clearFocus()
onSaveClicked(userInput)
}
},
)
}
}
@ -185,7 +168,8 @@ fun UserConfigItemList(
@Composable
private fun UserConfigPreview() {
UserConfigItemList(
userConfig = user {
userConfig =
user {
id = "!a280d9c8"
longName = "Meshtastic d9c8"
shortName = "d9c8"
@ -193,9 +177,7 @@ private fun UserConfigPreview() {
isLicensed = false
},
enabled = true,
onSaveClicked = { },
metadata = deviceMetadata {
firmwareVersion = "2.8.0"
}
onSaveClicked = {},
metadata = deviceMetadata { firmwareVersion = "2.8.0" },
)
}

View file

@ -104,9 +104,9 @@ import com.geeksville.mesh.navigation.getNavRouteFrom
import com.geeksville.mesh.service.ConnectionState
import com.geeksville.mesh.ui.common.components.AdaptiveTwoPane
import com.geeksville.mesh.ui.common.components.PreferenceFooter
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
import com.geeksville.mesh.ui.radioconfig.components.ChannelSelection
import com.geeksville.mesh.ui.radioconfig.components.PacketResponseStateDialog
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
import com.geeksville.mesh.ui.settings.radio.components.ChannelSelection
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState

View file

@ -628,7 +628,7 @@
<string name="map">Mesh Map</string>
<string name="conversations">Conversations</string>
<string name="nodes">Nodes</string>
<string name="bottom_nav_share">Share</string>
<string name="bottom_nav_settings">Settings</string>
<string name="set_your_region">Set your region</string>
<string name="reply">Reply</string>
<string name="map_reporting_summary">Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, long and short name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name.</string>
@ -672,6 +672,7 @@
<string name="uv_lux">UV Lux</string>
<string name="unknown">Unknown</string>
<string name="message_device_managed">This radio is managed and can only be changed by a remote admin.</string>
<string name="clean_node_database_title">Clean Node Database</string>
<string name="clean_nodes_older_than">Clean up nodes last seen older than %1$d days</string>
<string name="clean_unknown_nodes">Clean up only unknown nodes</string>