mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor(settings)!: standardize radio config screens (#3167)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
d2db37e0d4
commit
ddb19b959f
35 changed files with 1480 additions and 2651 deletions
|
|
@ -47,6 +47,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.navigation.NavBackStackEntry
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hasRoute
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
|
|
@ -153,7 +154,7 @@ fun NavDestination.isConfigRoute(): Boolean =
|
|||
private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenComposable(
|
||||
navController: NavHostController,
|
||||
routeNameString: String,
|
||||
crossinline screenContent: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
crossinline screenContent: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
composable<R>(
|
||||
deepLinks =
|
||||
|
|
@ -167,7 +168,7 @@ private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenCompo
|
|||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
|
||||
screenContent(viewModel)
|
||||
screenContent(navController, viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -306,71 +307,71 @@ enum class ConfigRoute(
|
|||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
val screenComposable: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
|
||||
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { nc, vm -> UserConfigScreen(nc, vm) }),
|
||||
CHANNELS(
|
||||
R.string.channels,
|
||||
SettingsRoutes.ChannelConfig,
|
||||
Icons.AutoMirrored.Default.List,
|
||||
0,
|
||||
{ vm -> ChannelConfigScreen(vm) },
|
||||
{ nc, vm -> ChannelConfigScreen(nc, vm) },
|
||||
),
|
||||
DEVICE(
|
||||
R.string.device,
|
||||
SettingsRoutes.Device,
|
||||
Icons.Default.Router,
|
||||
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
|
||||
{ vm -> DeviceConfigScreen(vm) },
|
||||
{ nc, vm -> DeviceConfigScreen(nc, vm) },
|
||||
),
|
||||
POSITION(
|
||||
R.string.position,
|
||||
SettingsRoutes.Position,
|
||||
Icons.Default.LocationOn,
|
||||
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
|
||||
{ vm -> PositionConfigScreen(vm) },
|
||||
{ nc, vm -> PositionConfigScreen(nc, vm) },
|
||||
),
|
||||
POWER(
|
||||
R.string.power,
|
||||
SettingsRoutes.Power,
|
||||
Icons.Default.Power,
|
||||
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
|
||||
{ vm -> PowerConfigScreen(vm) },
|
||||
{ nc, vm -> PowerConfigScreen(nc, vm) },
|
||||
),
|
||||
NETWORK(
|
||||
R.string.network,
|
||||
SettingsRoutes.Network,
|
||||
Icons.Default.Wifi,
|
||||
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
|
||||
{ vm -> NetworkConfigScreen(vm) },
|
||||
{ nc, vm -> NetworkConfigScreen(nc, vm) },
|
||||
),
|
||||
DISPLAY(
|
||||
R.string.display,
|
||||
SettingsRoutes.Display,
|
||||
Icons.Default.DisplaySettings,
|
||||
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
|
||||
{ vm -> DisplayConfigScreen(vm) },
|
||||
{ nc, vm -> DisplayConfigScreen(nc, vm) },
|
||||
),
|
||||
LORA(
|
||||
R.string.lora,
|
||||
SettingsRoutes.LoRa,
|
||||
Icons.Default.CellTower,
|
||||
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
|
||||
{ vm -> LoRaConfigScreen(vm) },
|
||||
{ nc, vm -> LoRaConfigScreen(nc, vm) },
|
||||
),
|
||||
BLUETOOTH(
|
||||
R.string.bluetooth,
|
||||
SettingsRoutes.Bluetooth,
|
||||
Icons.Default.Bluetooth,
|
||||
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
|
||||
{ vm -> BluetoothConfigScreen(vm) },
|
||||
{ nc, vm -> BluetoothConfigScreen(nc, vm) },
|
||||
),
|
||||
SECURITY(
|
||||
R.string.security,
|
||||
SettingsRoutes.Security,
|
||||
Icons.Default.Security,
|
||||
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
|
||||
{ vm -> SecurityConfigScreen(vm) },
|
||||
{ nc, vm -> SecurityConfigScreen(nc, vm) },
|
||||
),
|
||||
;
|
||||
|
||||
|
|
@ -397,98 +398,98 @@ enum class ModuleRoute(
|
|||
val route: Route,
|
||||
val icon: ImageVector?,
|
||||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
val screenComposable: @Composable (navController: NavController, viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
MQTT(
|
||||
R.string.mqtt,
|
||||
SettingsRoutes.MQTT,
|
||||
Icons.Default.Cloud,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
|
||||
{ vm -> MQTTConfigScreen(vm) },
|
||||
{ nc, vm -> MQTTConfigScreen(nc, vm) },
|
||||
),
|
||||
SERIAL(
|
||||
R.string.serial,
|
||||
SettingsRoutes.Serial,
|
||||
Icons.Default.Usb,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
|
||||
{ vm -> SerialConfigScreen(vm) },
|
||||
{ nc, vm -> SerialConfigScreen(nc, vm) },
|
||||
),
|
||||
EXT_NOTIFICATION(
|
||||
R.string.external_notification,
|
||||
SettingsRoutes.ExtNotification,
|
||||
Icons.Default.Notifications,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
|
||||
{ vm -> ExternalNotificationConfigScreen(vm) },
|
||||
{ nc, vm -> ExternalNotificationConfigScreen(nc, vm) },
|
||||
),
|
||||
STORE_FORWARD(
|
||||
R.string.store_forward,
|
||||
SettingsRoutes.StoreForward,
|
||||
Icons.AutoMirrored.Default.Forward,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
|
||||
{ vm -> StoreForwardConfigScreen(vm) },
|
||||
{ nc, vm -> StoreForwardConfigScreen(nc, vm) },
|
||||
),
|
||||
RANGE_TEST(
|
||||
R.string.range_test,
|
||||
SettingsRoutes.RangeTest,
|
||||
Icons.Default.Speed,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
|
||||
{ vm -> RangeTestConfigScreen(vm) },
|
||||
{ nc, vm -> RangeTestConfigScreen(nc, vm) },
|
||||
),
|
||||
TELEMETRY(
|
||||
R.string.telemetry,
|
||||
SettingsRoutes.Telemetry,
|
||||
Icons.Default.DataUsage,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
|
||||
{ vm -> TelemetryConfigScreen(vm) },
|
||||
{ nc, vm -> TelemetryConfigScreen(nc, vm) },
|
||||
),
|
||||
CANNED_MESSAGE(
|
||||
R.string.canned_message,
|
||||
SettingsRoutes.CannedMessage,
|
||||
Icons.AutoMirrored.Default.Message,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
|
||||
{ vm -> CannedMessageConfigScreen(vm) },
|
||||
{ nc, vm -> CannedMessageConfigScreen(nc, vm) },
|
||||
),
|
||||
AUDIO(
|
||||
R.string.audio,
|
||||
SettingsRoutes.Audio,
|
||||
Icons.AutoMirrored.Default.VolumeUp,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
|
||||
{ vm -> AudioConfigScreen(vm) },
|
||||
{ nc, vm -> AudioConfigScreen(nc, vm) },
|
||||
),
|
||||
REMOTE_HARDWARE(
|
||||
R.string.remote_hardware,
|
||||
SettingsRoutes.RemoteHardware,
|
||||
Icons.Default.SettingsRemote,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
|
||||
{ vm -> RemoteHardwareConfigScreen(vm) },
|
||||
{ nc, vm -> RemoteHardwareConfigScreen(nc, vm) },
|
||||
),
|
||||
NEIGHBOR_INFO(
|
||||
R.string.neighbor_info,
|
||||
SettingsRoutes.NeighborInfo,
|
||||
Icons.Default.People,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
|
||||
{ vm -> NeighborInfoConfigScreen(vm) },
|
||||
{ nc, vm -> NeighborInfoConfigScreen(nc, vm) },
|
||||
),
|
||||
AMBIENT_LIGHTING(
|
||||
R.string.ambient_lighting,
|
||||
SettingsRoutes.AmbientLighting,
|
||||
Icons.Default.LightMode,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
|
||||
{ vm -> AmbientLightingConfigScreen(vm) },
|
||||
{ nc, vm -> AmbientLightingConfigScreen(nc, vm) },
|
||||
),
|
||||
DETECTION_SENSOR(
|
||||
R.string.detection_sensor,
|
||||
SettingsRoutes.DetectionSensor,
|
||||
Icons.Default.Sensors,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
|
||||
{ vm -> DetectionSensorConfigScreen(vm) },
|
||||
{ nc, vm -> DetectionSensorConfigScreen(nc, vm) },
|
||||
),
|
||||
PAXCOUNTER(
|
||||
R.string.paxcounter,
|
||||
SettingsRoutes.Paxcounter,
|
||||
Icons.Default.PermScanWifi,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
|
||||
{ vm -> PaxcounterConfigScreen(vm) },
|
||||
{ nc, vm -> PaxcounterConfigScreen(nc, vm) },
|
||||
),
|
||||
;
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,6 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector,
|
|||
companion object {
|
||||
fun NavDestination.isTopLevel(): Boolean = listOf<KClass<out Route>>(
|
||||
ContactsRoutes.Contacts::class,
|
||||
NodesRoutes.Nodes::class,
|
||||
MapRoutes.Map::class,
|
||||
ConnectionsRoutes.Connections::class,
|
||||
)
|
||||
|
|
@ -356,10 +355,34 @@ fun MainScreen(uIViewModel: UIViewModel = hiltViewModel(), scanModel: BTScanMode
|
|||
NodesRoutes.Nodes::class,
|
||||
NodesRoutes.NodeDetail::class,
|
||||
SettingsRoutes.Settings::class,
|
||||
SettingsRoutes.AmbientLighting::class,
|
||||
SettingsRoutes.LoRa::class,
|
||||
SettingsRoutes.Security::class,
|
||||
SettingsRoutes.Audio::class,
|
||||
SettingsRoutes.Bluetooth::class,
|
||||
SettingsRoutes.ChannelConfig::class,
|
||||
SettingsRoutes.DetectionSensor::class,
|
||||
SettingsRoutes.Display::class,
|
||||
SettingsRoutes.Telemetry::class,
|
||||
SettingsRoutes.Network::class,
|
||||
SettingsRoutes.Paxcounter::class,
|
||||
SettingsRoutes.Power::class,
|
||||
SettingsRoutes.Position::class,
|
||||
SettingsRoutes.User::class,
|
||||
SettingsRoutes.StoreForward::class,
|
||||
SettingsRoutes.MQTT::class,
|
||||
SettingsRoutes.Serial::class,
|
||||
SettingsRoutes.ExtNotification::class,
|
||||
SettingsRoutes.CleanNodeDb::class,
|
||||
SettingsRoutes.DebugPanel::class,
|
||||
SettingsRoutes.RangeTest::class,
|
||||
SettingsRoutes.CannedMessage::class,
|
||||
SettingsRoutes.RemoteHardware::class,
|
||||
SettingsRoutes.NeighborInfo::class,
|
||||
)
|
||||
.none { this.hasRoute(it) }
|
||||
|
||||
AnimatedVisibility(visible = currentDestination?.hasGlobalAppBar() ?: true) {
|
||||
AnimatedVisibility(visible = currentDestination?.hasGlobalAppBar() ?: false) {
|
||||
MainAppBar(
|
||||
viewModel = uIViewModel,
|
||||
navController = navController,
|
||||
|
|
|
|||
|
|
@ -186,9 +186,16 @@ fun SettingsScreen(
|
|||
topBar = {
|
||||
MainAppBar(
|
||||
title = stringResource(R.string.bottom_nav_settings),
|
||||
subtitle =
|
||||
if (state.isLocal) {
|
||||
ourNode?.user?.longName
|
||||
} else {
|
||||
val remoteName = viewModel.destNode.value?.user?.longName ?: ""
|
||||
stringResource(R.string.remotely_administrating, remoteName)
|
||||
},
|
||||
ourNode = ourNode,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = ourNode != null && isConnected,
|
||||
showNodeChip = ourNode != null && isConnected && state.isLocal,
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
|
|
|
|||
|
|
@ -17,67 +17,50 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun AmbientLightingConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val ambientLightingConfig = state.moduleConfig.ambientLighting
|
||||
val formState = rememberConfigState(initialValue = ambientLightingConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
AmbientLightingConfigItemList(
|
||||
ambientLightingConfig = state.moduleConfig.ambientLighting,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.ambient_lighting),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { ambientLightingInput ->
|
||||
val config = moduleConfig { ambientLighting = ambientLightingInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { ambientLighting = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AmbientLightingConfigItemList(
|
||||
ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var ambientLightingInput by rememberSaveable { mutableStateOf(ambientLightingConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.ambient_lighting_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_state),
|
||||
checked = ambientLightingInput.ledState,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { ambientLightingInput = ambientLightingInput.copy { ledState = it } },
|
||||
checked = formState.value.ledState,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ledState = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -85,65 +68,41 @@ fun AmbientLightingConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.current),
|
||||
value = ambientLightingInput.current,
|
||||
enabled = enabled,
|
||||
value = formState.value.current,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { current = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { current = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.red),
|
||||
value = ambientLightingInput.red,
|
||||
enabled = enabled,
|
||||
value = formState.value.red,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { red = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { red = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.green),
|
||||
value = ambientLightingInput.green,
|
||||
enabled = enabled,
|
||||
value = formState.value.green,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { green = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { green = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.blue),
|
||||
value = ambientLightingInput.blue,
|
||||
enabled = enabled,
|
||||
value = formState.value.blue,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { blue = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && ambientLightingInput != ambientLightingConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
ambientLightingInput = ambientLightingConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(ambientLightingInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { blue = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AmbientLightingConfigPreview() {
|
||||
AmbientLightingConfigItemList(
|
||||
ambientLightingConfig = ModuleConfigProtos.ModuleConfig.AmbientLightingConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,66 +17,52 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.AudioConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun AudioConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val audioConfig = state.moduleConfig.audio
|
||||
val formState = rememberConfigState(initialValue = audioConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
AudioConfigItemList(
|
||||
audioConfig = state.moduleConfig.audio,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.audio),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { audioInput ->
|
||||
val config = moduleConfig { audio = audioInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { audio = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var audioInput by rememberSaveable { mutableStateOf(audioConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.audio_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.codec_2_enabled),
|
||||
checked = audioInput.codec2Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } },
|
||||
checked = formState.value.codec2Enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { codec2Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -84,85 +70,65 @@ fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicke
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ptt_pin),
|
||||
value = audioInput.pttPin,
|
||||
enabled = enabled,
|
||||
value = formState.value.pttPin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { pttPin = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { pttPin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.codec2_sample_rate),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
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 } },
|
||||
selectedItem = formState.value.bitrate,
|
||||
onItemSelected = { formState.value = formState.value.copy { bitrate = it } },
|
||||
)
|
||||
}
|
||||
item { Divider() }
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_word_select),
|
||||
value = audioInput.i2SWs,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SWs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SWs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SWs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_data_in),
|
||||
value = audioInput.i2SSd,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SSd,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSd = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SSd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_data_out),
|
||||
value = audioInput.i2SDin,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SDin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SDin = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SDin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.i2s_clock),
|
||||
value = audioInput.i2SSck,
|
||||
enabled = enabled,
|
||||
value = formState.value.i2SSck,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSck = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && audioInput != audioConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
audioInput = audioConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(audioInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { i2SSck = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AudioConfigPreview() {
|
||||
AudioConfigItemList(audioConfig = AudioConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,68 +17,52 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.BluetoothConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun BluetoothConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun BluetoothConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val bluetoothConfig = state.radioConfig.bluetooth
|
||||
val formState = rememberConfigState(initialValue = bluetoothConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
BluetoothConfigItemList(
|
||||
bluetoothConfig = state.radioConfig.bluetooth,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.bluetooth),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { bluetoothInput ->
|
||||
val config = config { bluetooth = bluetoothInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { bluetooth = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BluetoothConfigItemList(
|
||||
bluetoothConfig: BluetoothConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (BluetoothConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var bluetoothInput by rememberSaveable { mutableStateOf(bluetoothConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.bluetooth_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.bluetooth_enabled),
|
||||
checked = bluetoothInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -86,13 +70,13 @@ fun BluetoothConfigItemList(
|
|||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.pairing_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
BluetoothConfig.PairingMode.entries
|
||||
.filter { it != BluetoothConfig.PairingMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = bluetoothInput.mode,
|
||||
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } },
|
||||
selectedItem = formState.value.mode,
|
||||
onItemSelected = { formState.value = formState.value.copy { mode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -100,35 +84,15 @@ fun BluetoothConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.fixed_pin),
|
||||
value = bluetoothInput.fixedPin,
|
||||
enabled = enabled,
|
||||
value = formState.value.fixedPin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
if (it.toString().length == 6) { // ensure 6 digits
|
||||
bluetoothInput = bluetoothInput.copy { fixedPin = it }
|
||||
formState.value = formState.value.copy { fixedPin = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && bluetoothInput != bluetoothConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
bluetoothInput = bluetoothConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(bluetoothInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BluetoothConfigPreview() {
|
||||
BluetoothConfigItemList(bluetoothConfig = BluetoothConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
|
@ -27,69 +25,57 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.CannedMessageConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun CannedMessageConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val cannedMessageConfig = state.moduleConfig.cannedMessage
|
||||
val messages = state.cannedMessageMessages
|
||||
val formState = rememberConfigState(initialValue = cannedMessageConfig)
|
||||
var messagesInput by rememberSaveable(messages) { mutableStateOf(messages) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
CannedMessageConfigItemList(
|
||||
messages = state.cannedMessageMessages,
|
||||
cannedMessageConfig = state.moduleConfig.cannedMessage,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.canned_message),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { messagesInput, cannedMessageInput ->
|
||||
if (messagesInput != state.cannedMessageMessages) {
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
if (messagesInput != messages) {
|
||||
viewModel.setCannedMessages(messagesInput)
|
||||
}
|
||||
if (cannedMessageInput != state.moduleConfig.cannedMessage) {
|
||||
val config = moduleConfig { cannedMessage = cannedMessageInput }
|
||||
if (formState.value != cannedMessageConfig) {
|
||||
val config = moduleConfig { cannedMessage = formState.value }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CannedMessageConfigItemList(
|
||||
messages: String,
|
||||
cannedMessageConfig: CannedMessageConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (messages: String, config: CannedMessageConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var messagesInput by rememberSaveable { mutableStateOf(messages) }
|
||||
var cannedMessageInput by rememberSaveable { mutableStateOf(cannedMessageConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.canned_message_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.canned_message_enabled),
|
||||
checked = cannedMessageInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -97,9 +83,9 @@ fun CannedMessageConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.rotary_encoder_1_enabled),
|
||||
checked = cannedMessageInput.rotary1Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it } },
|
||||
checked = formState.value.rotary1Enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { rotary1Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -107,43 +93,43 @@ fun CannedMessageConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_for_rotary_encoder_a_port),
|
||||
value = cannedMessageInput.inputbrokerPinA,
|
||||
enabled = enabled,
|
||||
value = formState.value.inputbrokerPinA,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { inputbrokerPinA = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_for_rotary_encoder_b_port),
|
||||
value = cannedMessageInput.inputbrokerPinB,
|
||||
enabled = enabled,
|
||||
value = formState.value.inputbrokerPinB,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { inputbrokerPinB = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_for_rotary_encoder_press_port),
|
||||
value = cannedMessageInput.inputbrokerPinPress,
|
||||
enabled = enabled,
|
||||
value = formState.value.inputbrokerPinPress,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { inputbrokerPinPress = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_press),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventPress,
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it } },
|
||||
selectedItem = formState.value.inputbrokerEventPress,
|
||||
onItemSelected = { formState.value = formState.value.copy { inputbrokerEventPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -151,13 +137,13 @@ fun CannedMessageConfigItemList(
|
|||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_cw),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCw,
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it } },
|
||||
selectedItem = formState.value.inputbrokerEventCw,
|
||||
onItemSelected = { formState.value = formState.value.copy { inputbrokerEventCw = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -165,13 +151,13 @@ fun CannedMessageConfigItemList(
|
|||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_ccw),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCcw,
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it } },
|
||||
selectedItem = formState.value.inputbrokerEventCcw,
|
||||
onItemSelected = { formState.value = formState.value.copy { inputbrokerEventCcw = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -179,9 +165,9 @@ fun CannedMessageConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.up_down_select_input_enabled),
|
||||
checked = cannedMessageInput.updown1Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it } },
|
||||
checked = formState.value.updown1Enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { updown1Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -189,23 +175,23 @@ fun CannedMessageConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.allow_input_source),
|
||||
value = cannedMessageInput.allowInputSource,
|
||||
value = formState.value.allowInputSource,
|
||||
maxSize = 63, // allow_input_source max_size:16
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { allowInputSource = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { allowInputSource = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.send_bell),
|
||||
checked = cannedMessageInput.sendBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { sendBell = it } },
|
||||
checked = formState.value.sendBell,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sendBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -215,7 +201,7 @@ fun CannedMessageConfigItemList(
|
|||
title = stringResource(R.string.messages),
|
||||
value = messagesInput,
|
||||
maxSize = 200, // messages max_size:201
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
|
|
@ -223,31 +209,5 @@ fun CannedMessageConfigItemList(
|
|||
onValueChanged = { messagesInput = it },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && cannedMessageInput != cannedMessageConfig || messagesInput != messages,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
messagesInput = messages
|
||||
cannedMessageInput = cannedMessageConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(messagesInput, cannedMessageInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun CannedMessageConfigPreview() {
|
||||
CannedMessageConfigItemList(
|
||||
messages = "",
|
||||
cannedMessageConfig = CannedMessageConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import androidx.compose.material3.FloatingActionButton
|
|||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -68,6 +69,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.channelSettings
|
||||
|
|
@ -168,7 +170,7 @@ fun ChannelSelection(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun ChannelConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
|
|
@ -176,6 +178,8 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
|||
}
|
||||
|
||||
ChannelSettingsItemList(
|
||||
title = stringResource(id = R.string.channels),
|
||||
onBack = { navController.popBackStack() },
|
||||
settingsList = state.channelList,
|
||||
loraConfig = state.radioConfig.lora,
|
||||
maxChannels = viewModel.maxChannels,
|
||||
|
|
@ -188,6 +192,8 @@ fun ChannelConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
|||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun ChannelSettingsItemList(
|
||||
title: String,
|
||||
onBack: () -> Unit,
|
||||
settingsList: List<ChannelSettings>,
|
||||
loraConfig: LoRaConfig,
|
||||
maxChannels: Int = 8,
|
||||
|
|
@ -243,104 +249,116 @@ private fun ChannelSettingsItemList(
|
|||
ChannelLegendDialog(fwVersion) { showChannelLegendDialog = false }
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize().clickable(onClick = {}, enabled = false)) {
|
||||
Column {
|
||||
ChannelsConfigHeader(
|
||||
frequency =
|
||||
if (loraConfig.overrideFrequency != 0f) {
|
||||
loraConfig.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
slot =
|
||||
if (loraConfig.channelNum != 0) {
|
||||
loraConfig.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.press_and_drag),
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
|
||||
ChannelLegend { showChannelLegendDialog = true }
|
||||
|
||||
val locationChannel = determineLocationSharingChannel(fwVersion, settingsListInput.toList())
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.dragContainer(dragDropState = dragDropState, haptics = LocalHapticFeedback.current),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
dragDropItemsIndexed(items = settingsListInput, dragDropState = dragDropState) {
|
||||
index,
|
||||
channel,
|
||||
isDragging,
|
||||
->
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
channelSettings = channel,
|
||||
loraConfig = loraConfig,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) },
|
||||
sharesLocation = locationChannel == index,
|
||||
)
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
negativeText = R.string.cancel,
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
},
|
||||
)
|
||||
Scaffold(
|
||||
floatingActionButton = {
|
||||
if (maxChannels > settingsListInput.size) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (maxChannels > settingsListInput.size) {
|
||||
settingsListInput.add(channelSettings { psk = Channel.default.settings.psk })
|
||||
showEditChannelDialog = settingsListInput.lastIndex
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Icon(Icons.TwoTone.Add, stringResource(R.string.add))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
|
||||
Column {
|
||||
ChannelsConfigHeader(
|
||||
frequency =
|
||||
if (loraConfig.overrideFrequency != 0f) {
|
||||
loraConfig.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
slot =
|
||||
if (loraConfig.channelNum != 0) {
|
||||
loraConfig.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
)
|
||||
Text(
|
||||
text = stringResource(R.string.press_and_drag),
|
||||
fontSize = 11.sp,
|
||||
modifier = Modifier.padding(start = 16.dp),
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = maxChannels > settingsListInput.size,
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
enter =
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
exit =
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (maxChannels > settingsListInput.size) {
|
||||
settingsListInput.add(channelSettings { psk = Channel.default.settings.psk })
|
||||
showEditChannelDialog = settingsListInput.lastIndex
|
||||
ChannelLegend { showChannelLegendDialog = true }
|
||||
|
||||
val locationChannel = determineLocationSharingChannel(fwVersion, settingsListInput.toList())
|
||||
|
||||
LazyColumn(
|
||||
modifier =
|
||||
Modifier.dragContainer(dragDropState = dragDropState, haptics = LocalHapticFeedback.current),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
dragDropItemsIndexed(items = settingsListInput, dragDropState = dragDropState) {
|
||||
index,
|
||||
channel,
|
||||
isDragging,
|
||||
->
|
||||
ChannelCard(
|
||||
index = index,
|
||||
title = channel.name.ifEmpty { modemPresetName },
|
||||
enabled = enabled,
|
||||
channelSettings = channel,
|
||||
loraConfig = loraConfig,
|
||||
onEditClick = { showEditChannelDialog = index },
|
||||
onDeleteClick = { settingsListInput.removeAt(index) },
|
||||
sharesLocation = locationChannel == index,
|
||||
)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Icon(Icons.TwoTone.Add, stringResource(R.string.add))
|
||||
item { Spacer(modifier = Modifier.weight(1f)) }
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && isEditing,
|
||||
negativeText = R.string.cancel,
|
||||
onNegativeClicked = {
|
||||
focusManager.clearFocus()
|
||||
settingsListInput.clear()
|
||||
settingsListInput.addAll(settingsList)
|
||||
},
|
||||
positiveText = R.string.send,
|
||||
onPositiveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onPositiveClicked(settingsListInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = maxChannels > settingsListInput.size,
|
||||
modifier = Modifier.align(Alignment.BottomEnd),
|
||||
enter =
|
||||
slideInHorizontally(
|
||||
initialOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
exit =
|
||||
slideOutHorizontally(
|
||||
targetOffsetX = { it },
|
||||
animationSpec = tween(durationMillis = 600, easing = FastOutSlowInEasing),
|
||||
),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChannelsConfigHeader(frequency: Float, slot: Int) {
|
||||
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PreferenceCategory(text = stringResource(R.string.channels))
|
||||
Column {
|
||||
Text(text = "${stringResource(R.string.freq)}: ${frequency}MHz", fontSize = 11.sp)
|
||||
|
|
@ -380,6 +398,8 @@ private fun determineLocationSharingChannel(firmwareVersion: DeviceVersion, sett
|
|||
@Composable
|
||||
private fun ChannelSettingsPreview() {
|
||||
ChannelSettingsItemList(
|
||||
title = "Channels",
|
||||
onBack = {},
|
||||
settingsList =
|
||||
listOf(
|
||||
channelSettings {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.radio.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.google.protobuf.MessageLite
|
||||
|
||||
/**
|
||||
* A state holder for managing config data within a Composable.
|
||||
*
|
||||
* This class encapsulates the common logic for handling editable state that is derived from an initial value. It tracks
|
||||
* whether the current value has been modified ("dirty"), and provides simple methods to save the changes or reset to
|
||||
* the initial state.
|
||||
*
|
||||
* @param T The type of the data being managed, typically a Protobuf message.
|
||||
* @property initialValue The original, unmodified value of the config data.
|
||||
*/
|
||||
class ConfigState<T : MessageLite>(private val initialValue: T) {
|
||||
var value by mutableStateOf(initialValue)
|
||||
|
||||
val isDirty: Boolean
|
||||
get() = value != initialValue
|
||||
|
||||
fun reset() {
|
||||
value = initialValue
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : MessageLite> saver(initialValue: T): Saver<ConfigState<T>, ByteArray> = Saver(
|
||||
save = { it.value.toByteArray() },
|
||||
restore = {
|
||||
ConfigState(initialValue).apply {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
value = initialValue.parserForType.parseFrom(it) as T
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and remembers a [ConfigState] instance, correctly handling process death and recomposition. When the
|
||||
* `initialValue` changes, the config state will be reset.
|
||||
*
|
||||
* @param initialValue The initial value to populate the config with. The config will be reset if this value changes
|
||||
* across recompositions.
|
||||
*/
|
||||
@Composable
|
||||
fun <T : MessageLite> rememberConfigState(initialValue: T): ConfigState<T> =
|
||||
rememberSaveable(initialValue, saver = ConfigState.saver(initialValue)) { ConfigState(initialValue) }
|
||||
|
|
@ -17,72 +17,55 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun DetectionSensorConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val detectionSensorConfig = state.moduleConfig.detectionSensor
|
||||
val formState = rememberConfigState(initialValue = detectionSensorConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
DetectionSensorConfigItemList(
|
||||
detectionSensorConfig = state.moduleConfig.detectionSensor,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.detection_sensor),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { detectionSensorInput ->
|
||||
val config = moduleConfig { detectionSensor = detectionSensorInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { detectionSensor = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun DetectionSensorConfigItemList(
|
||||
detectionSensorConfig: ModuleConfig.DetectionSensorConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfig.DetectionSensorConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var detectionSensorInput by rememberSaveable { mutableStateOf(detectionSensorConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.detection_sensor_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.detection_sensor_enabled),
|
||||
checked = detectionSensorInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -90,29 +73,29 @@ fun DetectionSensorConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_broadcast_seconds),
|
||||
value = detectionSensorInput.minimumBroadcastSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.minimumBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { minimumBroadcastSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { minimumBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.state_broadcast_seconds),
|
||||
value = detectionSensorInput.stateBroadcastSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.stateBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { stateBroadcastSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { stateBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.send_bell_with_alert_message),
|
||||
checked = detectionSensorInput.sendBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { sendBell = it } },
|
||||
checked = formState.value.sendBell,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sendBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -120,37 +103,37 @@ fun DetectionSensorConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.friendly_name),
|
||||
value = detectionSensorInput.name,
|
||||
value = formState.value.name,
|
||||
maxSize = 19, // name max_size:20
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { name = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { name = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin_to_monitor),
|
||||
value = detectionSensorInput.monitorPin,
|
||||
enabled = enabled,
|
||||
value = formState.value.monitorPin,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { monitorPin = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { monitorPin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.detection_trigger_type),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
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 } },
|
||||
selectedItem = formState.value.detectionTriggerType,
|
||||
onItemSelected = { formState.value = formState.value.copy { detectionTriggerType = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -158,35 +141,11 @@ fun DetectionSensorConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_input_pullup_mode),
|
||||
checked = detectionSensorInput.usePullup,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { usePullup = it } },
|
||||
checked = formState.value.usePullup,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePullup = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && detectionSensorInput != detectionSensorConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
detectionSensorInput = detectionSensorConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(detectionSensorInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DetectionSensorConfigPreview() {
|
||||
DetectionSensorConfigItemList(
|
||||
detectionSensorConfig = ModuleConfig.DetectionSensorConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ package com.geeksville.mesh.ui.settings.radio.components
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
|
|
@ -46,16 +44,15 @@ import androidx.compose.ui.text.TextLinkStyles
|
|||
import androidx.compose.ui.text.fromHtml
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DeviceConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
|
@ -91,24 +88,135 @@ private val DeviceConfig.RebroadcastMode.description: Int
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun DeviceConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
val deviceConfig = state.radioConfig.device
|
||||
val formState = rememberConfigState(initialValue = deviceConfig)
|
||||
var selectedRole by rememberSaveable { mutableStateOf(formState.value.role) }
|
||||
val infrastructureRoles = listOf(DeviceConfig.Role.ROUTER, DeviceConfig.Role.REPEATER)
|
||||
if (selectedRole != formState.value.role) {
|
||||
if (selectedRole in infrastructureRoles) {
|
||||
RouterRoleConfirmationDialog(
|
||||
onDismiss = { selectedRole = formState.value.role },
|
||||
onConfirm = { formState.value = formState.value.copy { role = selectedRole } },
|
||||
)
|
||||
} else {
|
||||
formState.value = formState.value.copy { role = selectedRole }
|
||||
}
|
||||
}
|
||||
|
||||
DeviceConfigItemList(
|
||||
deviceConfig = state.radioConfig.device,
|
||||
val focusManager = LocalFocusManager.current
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.device),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { deviceInput ->
|
||||
val config = config { device = deviceInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { device = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.role),
|
||||
enabled = state.connected,
|
||||
selectedItem = formState.value.role,
|
||||
onItemSelected = { selectedRole = it },
|
||||
summary = stringResource(id = formState.value.role.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.rebroadcast_mode),
|
||||
enabled = state.connected,
|
||||
selectedItem = formState.value.rebroadcastMode,
|
||||
onItemSelected = { formState.value = formState.value.copy { rebroadcastMode = it } },
|
||||
summary = stringResource(id = formState.value.rebroadcastMode.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nodeinfo_broadcast_interval),
|
||||
value = formState.value.nodeInfoBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { nodeInfoBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.hardware)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.double_tap_as_button_press),
|
||||
summary = stringResource(id = R.string.config_device_doubleTapAsButtonPress_summary),
|
||||
checked = formState.value.doubleTapAsButtonPress,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { doubleTapAsButtonPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.triple_click_adhoc_ping),
|
||||
summary = stringResource(id = R.string.config_device_tripleClickAsAdHocPing_summary),
|
||||
checked = !formState.value.disableTripleClick,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { disableTripleClick = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_heartbeat),
|
||||
summary = stringResource(id = R.string.config_device_ledHeartbeatEnabled_summary),
|
||||
checked = !formState.value.ledHeartbeatDisabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ledHeartbeatDisabled = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.debug)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.time_zone),
|
||||
value = formState.value.tzdef,
|
||||
summary = stringResource(id = R.string.config_device_tzdef_summary),
|
||||
maxSize = 64, // tzdef max_size:65
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { tzdef = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.gpio)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.button_gpio),
|
||||
value = formState.value.buttonGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { buttonGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.buzzer_gpio),
|
||||
value = formState.value.buzzerGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { formState.value = formState.value.copy { buzzerGpio = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun RouterRoleConfirmationDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
|
||||
val dialogTitle = stringResource(R.string.are_you_sure)
|
||||
|
|
@ -141,140 +249,3 @@ fun RouterRoleConfirmationDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
|
|||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
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)
|
||||
if (selectedRole != deviceInput.role) {
|
||||
if (selectedRole in infrastructureRoles) {
|
||||
RouterRoleConfirmationDialog(
|
||||
onDismiss = { selectedRole = deviceInput.role },
|
||||
onConfirm = { deviceInput = deviceInput.copy { role = selectedRole } },
|
||||
)
|
||||
} else {
|
||||
deviceInput = deviceInput.copy { role = selectedRole }
|
||||
}
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.role),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.role,
|
||||
onItemSelected = { selectedRole = it },
|
||||
summary = stringResource(id = deviceInput.role.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.rebroadcast_mode),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.rebroadcastMode,
|
||||
onItemSelected = { deviceInput = deviceInput.copy { rebroadcastMode = it } },
|
||||
summary = stringResource(id = deviceInput.rebroadcastMode.description),
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nodeinfo_broadcast_interval),
|
||||
value = deviceInput.nodeInfoBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.hardware)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.double_tap_as_button_press),
|
||||
summary = stringResource(id = R.string.config_device_doubleTapAsButtonPress_summary),
|
||||
checked = deviceInput.doubleTapAsButtonPress,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.triple_click_adhoc_ping),
|
||||
summary = stringResource(id = R.string.config_device_tripleClickAsAdHocPing_summary),
|
||||
checked = !deviceInput.disableTripleClick,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_heartbeat),
|
||||
summary = stringResource(id = R.string.config_device_ledHeartbeatEnabled_summary),
|
||||
checked = !deviceInput.ledHeartbeatDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.debug)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.time_zone),
|
||||
value = deviceInput.tzdef,
|
||||
summary = stringResource(id = R.string.config_device_tzdef_summary),
|
||||
maxSize = 64, // tzdef max_size:65
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { tzdef = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.gpio)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.button_gpio),
|
||||
value = deviceInput.buttonGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buttonGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.buzzer_gpio),
|
||||
value = deviceInput.buzzerGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buzzerGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && deviceInput != deviceConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
deviceInput = deviceConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(deviceInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DeviceConfigPreview() {
|
||||
DeviceConfigItemList(deviceConfig = DeviceConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,65 +17,52 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun DisplayConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val displayConfig = state.radioConfig.display
|
||||
val formState = rememberConfigState(initialValue = displayConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
DisplayConfigItemList(
|
||||
displayConfig = state.radioConfig.display,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.display),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { displayInput ->
|
||||
val config = config { display = displayInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { display = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSaveClicked: (DisplayConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var displayInput by rememberSaveable { mutableStateOf(displayConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.display_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.always_point_north),
|
||||
summary = stringResource(id = R.string.config_display_compass_north_top_summary),
|
||||
checked = displayInput.compassNorthTop,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } },
|
||||
checked = formState.value.compassNorthTop,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { compassNorthTop = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -83,9 +70,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.use_12h_format),
|
||||
summary = stringResource(R.string.display_time_in_12h_format),
|
||||
enabled = enabled,
|
||||
checked = displayInput.use12HClock,
|
||||
onCheckedChange = { displayInput = displayInput.copy { use12HClock = it } },
|
||||
enabled = state.connected,
|
||||
checked = formState.value.use12HClock,
|
||||
onCheckedChange = { formState.value = formState.value.copy { use12HClock = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -93,9 +80,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.bold_heading),
|
||||
summary = stringResource(id = R.string.config_display_heading_bold_summary),
|
||||
checked = displayInput.headingBold,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { headingBold = it } },
|
||||
checked = formState.value.headingBold,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { headingBold = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -103,13 +90,13 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.display_units),
|
||||
summary = stringResource(id = R.string.config_display_units_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.DisplayUnits.entries
|
||||
.filter { it != DisplayConfig.DisplayUnits.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.units,
|
||||
onItemSelected = { displayInput = displayInput.copy { units = it } },
|
||||
selectedItem = formState.value.units,
|
||||
onItemSelected = { formState.value = formState.value.copy { units = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -119,10 +106,10 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.screen_on_for),
|
||||
summary = stringResource(id = R.string.config_display_screen_on_secs_summary),
|
||||
value = displayInput.screenOnSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.screenOnSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { screenOnSecs = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -131,10 +118,10 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.carousel_interval),
|
||||
summary = stringResource(id = R.string.config_display_auto_screen_carousel_secs_summary),
|
||||
value = displayInput.autoScreenCarouselSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.autoScreenCarouselSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { autoScreenCarouselSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { autoScreenCarouselSecs = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -142,9 +129,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.wake_on_tap_or_motion),
|
||||
summary = stringResource(id = R.string.config_display_wake_on_tap_or_motion_summary),
|
||||
checked = displayInput.wakeOnTapOrMotion,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { wakeOnTapOrMotion = it } },
|
||||
checked = formState.value.wakeOnTapOrMotion,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { wakeOnTapOrMotion = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -152,9 +139,9 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.flip_screen),
|
||||
summary = stringResource(id = R.string.config_display_flip_screen_summary),
|
||||
checked = displayInput.flipScreen,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } },
|
||||
checked = formState.value.flipScreen,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { flipScreen = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -162,13 +149,13 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.display_mode),
|
||||
summary = stringResource(id = R.string.config_display_displaymode_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.DisplayMode.entries
|
||||
.filter { it != DisplayConfig.DisplayMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.displaymode,
|
||||
onItemSelected = { displayInput = displayInput.copy { displaymode = it } },
|
||||
selectedItem = formState.value.displaymode,
|
||||
onItemSelected = { formState.value = formState.value.copy { displaymode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -176,48 +163,28 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.oled_type),
|
||||
summary = stringResource(id = R.string.config_display_oled_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.OledType.entries
|
||||
.filter { it != DisplayConfig.OledType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.oled,
|
||||
onItemSelected = { displayInput = displayInput.copy { oled = it } },
|
||||
selectedItem = formState.value.oled,
|
||||
onItemSelected = { formState.value = formState.value.copy { oled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.compass_orientation),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
DisplayConfig.CompassOrientation.entries
|
||||
.filter { it != DisplayConfig.CompassOrientation.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.compassOrientation,
|
||||
onItemSelected = { displayInput = displayInput.copy { compassOrientation = it } },
|
||||
selectedItem = formState.value.compassOrientation,
|
||||
onItemSelected = { formState.value = formState.value.copy { compassOrientation = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && displayInput != displayConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
displayInput = displayConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(displayInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DisplayConfigPreview() {
|
||||
DisplayConfigItemList(displayConfig = DisplayConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
|
|
@ -27,80 +25,69 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.ExternalNotificationConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.common.components.TextDividerPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun ExternalNotificationConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val extNotificationConfig = state.moduleConfig.externalNotification
|
||||
val ringtone = state.ringtone
|
||||
val formState = rememberConfigState(initialValue = extNotificationConfig)
|
||||
var ringtoneInput by rememberSaveable(ringtone) { mutableStateOf(ringtone) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
ExternalNotificationConfigItemList(
|
||||
ringtone = state.ringtone,
|
||||
extNotificationConfig = state.moduleConfig.externalNotification,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.external_notification),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { ringtoneInput, extNotificationInput ->
|
||||
if (ringtoneInput != state.ringtone) {
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
if (ringtoneInput != ringtone) {
|
||||
viewModel.setRingtone(ringtoneInput)
|
||||
}
|
||||
if (extNotificationInput != state.moduleConfig.externalNotification) {
|
||||
val config = moduleConfig { externalNotification = extNotificationInput }
|
||||
if (formState.value != extNotificationConfig) {
|
||||
val config = moduleConfig { externalNotification = formState.value }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExternalNotificationConfigItemList(
|
||||
ringtone: String,
|
||||
extNotificationConfig: ExternalNotificationConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ringtone: String, config: ExternalNotificationConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var ringtoneInput by rememberSaveable { mutableStateOf(ringtone) }
|
||||
var externalNotificationInput by rememberSaveable { mutableStateOf(extNotificationConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.external_notification_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.external_notification_enabled),
|
||||
checked = externalNotificationInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { TextDividerPreference(stringResource(R.string.notifications_on_message_receipt), enabled = enabled) }
|
||||
item {
|
||||
TextDividerPreference(stringResource(R.string.notifications_on_message_receipt), enabled = state.connected)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_message_led),
|
||||
checked = externalNotificationInput.alertMessage,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertMessage = it } },
|
||||
checked = formState.value.alertMessage,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertMessage = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -108,11 +95,9 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_message_buzzer),
|
||||
checked = externalNotificationInput.alertMessageBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessageBuzzer = it }
|
||||
},
|
||||
checked = formState.value.alertMessageBuzzer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertMessageBuzzer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -120,22 +105,25 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_message_vibra),
|
||||
checked = externalNotificationInput.alertMessageVibra,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessageVibra = it }
|
||||
},
|
||||
checked = formState.value.alertMessageVibra,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertMessageVibra = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { TextDividerPreference(stringResource(R.string.notifications_on_alert_bell_receipt), enabled = enabled) }
|
||||
item {
|
||||
TextDividerPreference(
|
||||
stringResource(R.string.notifications_on_alert_bell_receipt),
|
||||
enabled = state.connected,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_bell_led),
|
||||
checked = externalNotificationInput.alertBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertBell = it } },
|
||||
checked = formState.value.alertBell,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -143,11 +131,9 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_bell_buzzer),
|
||||
checked = externalNotificationInput.alertBellBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBellBuzzer = it }
|
||||
},
|
||||
checked = formState.value.alertBellBuzzer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertBellBuzzer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -155,11 +141,9 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.alert_bell_vibra),
|
||||
checked = externalNotificationInput.alertBellVibra,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBellVibra = it }
|
||||
},
|
||||
checked = formState.value.alertBellVibra,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { alertBellVibra = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -167,20 +151,20 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_led_gpio),
|
||||
value = externalNotificationInput.output,
|
||||
enabled = enabled,
|
||||
value = formState.value.output,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { output = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { output = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (externalNotificationInput.output != 0) {
|
||||
if (formState.value.output != 0) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.output_led_active_high),
|
||||
checked = externalNotificationInput.active,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { active = it } },
|
||||
checked = formState.value.active,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { active = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -189,20 +173,20 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_buzzer_gpio),
|
||||
value = externalNotificationInput.outputBuzzer,
|
||||
enabled = enabled,
|
||||
value = formState.value.outputBuzzer,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { outputBuzzer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (externalNotificationInput.outputBuzzer != 0) {
|
||||
if (formState.value.outputBuzzer != 0) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_pwm_buzzer),
|
||||
checked = externalNotificationInput.usePwm,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { usePwm = it } },
|
||||
checked = formState.value.usePwm,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePwm = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -211,30 +195,30 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_vibra_gpio),
|
||||
value = externalNotificationInput.outputVibra,
|
||||
enabled = enabled,
|
||||
value = formState.value.outputVibra,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputVibra = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { outputVibra = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.output_duration_milliseconds),
|
||||
value = externalNotificationInput.outputMs,
|
||||
enabled = enabled,
|
||||
value = formState.value.outputMs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputMs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { outputMs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nag_timeout_seconds),
|
||||
value = externalNotificationInput.nagTimeout,
|
||||
enabled = enabled,
|
||||
value = formState.value.nagTimeout,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { nagTimeout = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { nagTimeout = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +227,7 @@ fun ExternalNotificationConfigItemList(
|
|||
title = stringResource(R.string.ringtone),
|
||||
value = ringtoneInput,
|
||||
maxSize = 230, // ringtone max_size:231
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
|
|
@ -255,39 +239,11 @@ fun ExternalNotificationConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_i2s_as_buzzer),
|
||||
checked = externalNotificationInput.useI2SAsBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { useI2SAsBuzzer = it }
|
||||
},
|
||||
checked = formState.value.useI2SAsBuzzer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { useI2SAsBuzzer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && externalNotificationInput != extNotificationConfig || ringtoneInput != ringtone,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
ringtoneInput = ringtone
|
||||
externalNotificationInput = extNotificationConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(ringtoneInput, externalNotificationInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun ExternalNotificationConfigPreview() {
|
||||
ExternalNotificationConfigItemList(
|
||||
ringtone = "",
|
||||
extNotificationConfig = ExternalNotificationConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,30 +17,24 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ChannelProtos.ChannelSettings
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.SignedIntegerEditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
|
@ -50,73 +44,61 @@ import org.meshtastic.core.model.numChannels
|
|||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun LoRaConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun LoRaConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val loraConfig = state.radioConfig.lora
|
||||
val primarySettings = state.channelList.getOrNull(0) ?: return
|
||||
val formState = rememberConfigState(initialValue = loraConfig)
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
val primaryChannel by remember(formState.value) { mutableStateOf(Channel(primarySettings, formState.value)) }
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
LoRaConfigItemList(
|
||||
loraConfig = state.radioConfig.lora,
|
||||
primarySettings = state.channelList.getOrNull(0) ?: return,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.lora),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { loraInput ->
|
||||
val config = config { lora = loraInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { lora = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
hasPaFan = viewModel.hasPaFan,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun LoRaConfigItemList(
|
||||
loraConfig: LoRaConfig,
|
||||
primarySettings: ChannelSettings,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (LoRaConfig) -> Unit,
|
||||
hasPaFan: Boolean = false,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var loraInput by rememberSaveable { mutableStateOf(loraConfig) }
|
||||
val primaryChannel by remember(loraInput) { mutableStateOf(Channel(primarySettings, loraInput)) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.region_frequency_plan),
|
||||
summary = stringResource(id = R.string.config_lora_region_summary),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = loraInput.region,
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } },
|
||||
selectedItem = formState.value.region,
|
||||
onItemSelected = { formState.value = formState.value.copy { region = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_modem_preset),
|
||||
checked = loraInput.usePreset,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } },
|
||||
checked = formState.value.usePreset,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { usePreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (loraInput.usePreset) {
|
||||
if (formState.value.usePreset) {
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.modem_preset),
|
||||
summary = stringResource(id = R.string.config_lora_modem_preset_summary),
|
||||
enabled = enabled && loraInput.usePreset,
|
||||
enabled = state.connected && formState.value.usePreset,
|
||||
items =
|
||||
LoRaConfig.ModemPreset.entries
|
||||
.filter { it != LoRaConfig.ModemPreset.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = loraInput.modemPreset,
|
||||
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } },
|
||||
selectedItem = formState.value.modemPreset,
|
||||
onItemSelected = { formState.value = formState.value.copy { modemPreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -124,30 +106,30 @@ fun LoRaConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.bandwidth),
|
||||
value = loraInput.bandwidth,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
value = formState.value.bandwidth,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { bandwidth = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { bandwidth = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.spread_factor),
|
||||
value = loraInput.spreadFactor,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
value = formState.value.spreadFactor,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { spreadFactor = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.coding_rate),
|
||||
value = loraInput.codingRate,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
value = formState.value.codingRate,
|
||||
enabled = state.connected && !formState.value.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { codingRate = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { codingRate = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -156,18 +138,18 @@ fun LoRaConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = loraInput.ignoreMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } },
|
||||
checked = formState.value.ignoreMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ignoreMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = loraInput.configOkToMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } },
|
||||
checked = formState.value.configOkToMqtt,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { configOkToMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -175,9 +157,9 @@ fun LoRaConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.tx_enabled),
|
||||
checked = loraInput.txEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } },
|
||||
checked = formState.value.txEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { txEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -185,10 +167,10 @@ fun LoRaConfigItemList(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.hop_limit),
|
||||
summary = stringResource(id = R.string.config_lora_hop_limit_summary),
|
||||
value = loraInput.hopLimit,
|
||||
enabled = enabled,
|
||||
value = formState.value.hopLimit,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { hopLimit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -198,13 +180,18 @@ fun LoRaConfigItemList(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.frequency_slot),
|
||||
summary = stringResource(id = R.string.config_lora_frequency_slot_summary),
|
||||
value = if (isFocused || loraInput.channelNum != 0) loraInput.channelNum else primaryChannel.channelNum,
|
||||
enabled = enabled,
|
||||
value =
|
||||
if (isFocused || formState.value.channelNum != 0) {
|
||||
formState.value.channelNum
|
||||
} else {
|
||||
primaryChannel.channelNum
|
||||
},
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = {
|
||||
if (it <= loraInput.numChannels) { // total num of LoRa channels
|
||||
loraInput = loraInput.copy { channelNum = it }
|
||||
if (it <= formState.value.numChannels) { // total num of LoRa channels
|
||||
formState.value = formState.value.copy { channelNum = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -213,9 +200,9 @@ fun LoRaConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.sx126x_rx_boosted_gain),
|
||||
checked = loraInput.sx126XRxBoostedGain,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } },
|
||||
checked = formState.value.sx126XRxBoostedGain,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { sx126XRxBoostedGain = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -224,63 +211,38 @@ fun LoRaConfigItemList(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.override_frequency_mhz),
|
||||
value =
|
||||
if (isFocused || loraInput.overrideFrequency != 0f) {
|
||||
loraInput.overrideFrequency
|
||||
if (isFocused || formState.value.overrideFrequency != 0f) {
|
||||
formState.value.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { overrideFrequency = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.tx_power_dbm),
|
||||
value = loraInput.txPower,
|
||||
enabled = enabled,
|
||||
value = formState.value.txPower,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { txPower = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { txPower = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (hasPaFan) {
|
||||
if (viewModel.hasPaFan) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.pa_fan_disabled),
|
||||
checked = loraInput.paFanDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { paFanDisabled = it } },
|
||||
checked = formState.value.paFanDisabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { paFanDisabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && loraInput != loraConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
loraInput = loraConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(loraInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun LoRaConfigPreview() {
|
||||
LoRaConfigItemList(
|
||||
loraConfig = Channel.default.loraConfig,
|
||||
primarySettings = Channel.default.settings,
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,83 +19,72 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.MQTTConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.EditPasswordPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun MQTTConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun MQTTConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val destNode by viewModel.destNode.collectAsStateWithLifecycle()
|
||||
val destNum = destNode?.num
|
||||
val mqttConfig = state.moduleConfig.mqtt
|
||||
val formState = rememberConfigState(initialValue = mqttConfig)
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
if (!formState.value.mapReportSettings.shouldReportLocation) {
|
||||
val settings =
|
||||
formState.value.mapReportSettings.copy {
|
||||
this.shouldReportLocation = viewModel.shouldReportLocation(destNum)
|
||||
}
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
}
|
||||
|
||||
MQTTConfigItemList(
|
||||
mqttConfig = state.moduleConfig.mqtt,
|
||||
enabled = state.connected,
|
||||
shouldReportLocation = viewModel.shouldReportLocation(destNum),
|
||||
onShouldReportLocationChanged = { shouldReportLocation ->
|
||||
viewModel.setShouldReportLocation(destNum, shouldReportLocation)
|
||||
},
|
||||
onSaveClicked = { mqttInput ->
|
||||
val config = moduleConfig { mqtt = mqttInput }
|
||||
val consentValid =
|
||||
if (formState.value.mapReportingEnabled) {
|
||||
formState.value.mapReportSettings.shouldReportLocation &&
|
||||
mqttConfig.mapReportSettings.publishIntervalSecs >= MIN_INTERVAL_SECS
|
||||
} else {
|
||||
true
|
||||
}
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.mqtt),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected && consentValid,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { mqtt = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MQTTConfigItemList(
|
||||
mqttConfig: MQTTConfig,
|
||||
enabled: Boolean,
|
||||
shouldReportLocation: Boolean,
|
||||
onShouldReportLocationChanged: (shouldReportLocation: Boolean) -> Unit,
|
||||
onSaveClicked: (MQTTConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var mqttInput by rememberSaveable { mutableStateOf(mqttConfig) }
|
||||
if (!mqttInput.mapReportSettings.shouldReportLocation) {
|
||||
val settings = mqttInput.mapReportSettings.copy { this.shouldReportLocation = shouldReportLocation }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.mqtt_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.mqtt_enabled),
|
||||
checked = mqttInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -103,48 +92,48 @@ fun MQTTConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.address),
|
||||
value = mqttInput.address,
|
||||
value = formState.value.address,
|
||||
maxSize = 63, // address max_size:64
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { address = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { address = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.username),
|
||||
value = mqttInput.username,
|
||||
value = formState.value.username,
|
||||
maxSize = 63, // username max_size:64
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { username = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { username = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditPasswordPreference(
|
||||
title = stringResource(R.string.password),
|
||||
value = mqttInput.password,
|
||||
value = formState.value.password,
|
||||
maxSize = 63, // password max_size:64
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { password = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { password = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.encryption_enabled),
|
||||
checked = mqttInput.encryptionEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { encryptionEnabled = it } },
|
||||
checked = formState.value.encryptionEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { encryptionEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -152,22 +141,22 @@ fun MQTTConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.json_output_enabled),
|
||||
checked = mqttInput.jsonEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { jsonEnabled = it } },
|
||||
checked = formState.value.jsonEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { jsonEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
val defaultAddress = stringResource(R.string.default_mqtt_address)
|
||||
val isDefault = mqttInput.address.isEmpty() || mqttInput.address.contains(defaultAddress)
|
||||
val enforceTls = isDefault && mqttInput.proxyToClientEnabled
|
||||
val isDefault = formState.value.address.isEmpty() || formState.value.address.contains(defaultAddress)
|
||||
val enforceTls = isDefault && formState.value.proxyToClientEnabled
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.tls_enabled),
|
||||
checked = mqttInput.tlsEnabled || enforceTls,
|
||||
enabled = enabled && !enforceTls,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { tlsEnabled = it } },
|
||||
checked = formState.value.tlsEnabled || enforceTls,
|
||||
enabled = state.connected && !enforceTls,
|
||||
onCheckedChange = { formState.value = formState.value.copy { tlsEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -175,23 +164,23 @@ fun MQTTConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.root_topic),
|
||||
value = mqttInput.root,
|
||||
value = formState.value.root,
|
||||
maxSize = 31, // root max_size:32
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { mqttInput = mqttInput.copy { root = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { root = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.proxy_to_client_enabled),
|
||||
checked = mqttInput.proxyToClientEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { mqttInput = mqttInput.copy { proxyToClientEnabled = it } },
|
||||
checked = formState.value.proxyToClientEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { proxyToClientEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -200,63 +189,30 @@ fun MQTTConfigItemList(
|
|||
|
||||
item {
|
||||
MapReportingPreference(
|
||||
mapReportingEnabled = mqttInput.mapReportingEnabled,
|
||||
onMapReportingEnabledChanged = { mqttInput = mqttInput.copy { mapReportingEnabled = it } },
|
||||
shouldReportLocation = mqttInput.mapReportSettings.shouldReportLocation,
|
||||
mapReportingEnabled = formState.value.mapReportingEnabled,
|
||||
onMapReportingEnabledChanged = { formState.value = formState.value.copy { mapReportingEnabled = it } },
|
||||
shouldReportLocation = formState.value.mapReportSettings.shouldReportLocation,
|
||||
onShouldReportLocationChanged = {
|
||||
onShouldReportLocationChanged(it)
|
||||
val settings = mqttInput.mapReportSettings.copy { this.shouldReportLocation = it }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
viewModel.setShouldReportLocation(destNum, it)
|
||||
val settings = formState.value.mapReportSettings.copy { this.shouldReportLocation = it }
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
},
|
||||
positionPrecision = mqttInput.mapReportSettings.positionPrecision,
|
||||
positionPrecision = formState.value.mapReportSettings.positionPrecision,
|
||||
onPositionPrecisionChanged = {
|
||||
val settings = mqttInput.mapReportSettings.copy { positionPrecision = it }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
val settings = formState.value.mapReportSettings.copy { positionPrecision = it }
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
},
|
||||
publishIntervalSecs = mqttInput.mapReportSettings.publishIntervalSecs,
|
||||
publishIntervalSecs = formState.value.mapReportSettings.publishIntervalSecs,
|
||||
onPublishIntervalSecsChanged = {
|
||||
val settings = mqttInput.mapReportSettings.copy { publishIntervalSecs = it }
|
||||
mqttInput = mqttInput.copy { mapReportSettings = settings }
|
||||
val settings = formState.value.mapReportSettings.copy { publishIntervalSecs = it }
|
||||
formState.value = formState.value.copy { mapReportSettings = settings }
|
||||
},
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
focusManager = focusManager,
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
val consentValid =
|
||||
if (mqttInput.mapReportingEnabled) {
|
||||
mqttInput.mapReportSettings.shouldReportLocation &&
|
||||
mqttConfig.mapReportSettings.publishIntervalSecs >= MIN_INTERVAL_SECS
|
||||
} else {
|
||||
true
|
||||
}
|
||||
PreferenceFooter(
|
||||
enabled = enabled && mqttInput != mqttConfig && consentValid,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
mqttInput = mqttConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(mqttInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val MIN_INTERVAL_SECS = 3600
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MQTTConfigPreview() {
|
||||
MQTTConfigItemList(
|
||||
mqttConfig = MQTTConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
shouldReportLocation = true,
|
||||
onShouldReportLocationChanged = { _ -> },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,67 +17,50 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun NeighborInfoConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun NeighborInfoConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val neighborInfoConfig = state.moduleConfig.neighborInfo
|
||||
val formState = rememberConfigState(initialValue = neighborInfoConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
NeighborInfoConfigItemList(
|
||||
neighborInfoConfig = state.moduleConfig.neighborInfo,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.neighbor_info),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { neighborInfoInput ->
|
||||
val config = moduleConfig { neighborInfo = neighborInfoInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { neighborInfo = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NeighborInfoConfigItemList(
|
||||
neighborInfoConfig: ModuleConfigProtos.ModuleConfig.NeighborInfoConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfigProtos.ModuleConfig.NeighborInfoConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var neighborInfoInput by rememberSaveable { mutableStateOf(neighborInfoConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.neighbor_info_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.neighbor_info_enabled),
|
||||
checked = neighborInfoInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -85,10 +68,10 @@ fun NeighborInfoConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.update_interval_seconds),
|
||||
value = neighborInfoInput.updateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.updateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { neighborInfoInput = neighborInfoInput.copy { updateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { updateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -96,35 +79,11 @@ fun NeighborInfoConfigItemList(
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.transmit_over_lora),
|
||||
summary = stringResource(id = R.string.config_device_transmitOverLora_summary),
|
||||
checked = neighborInfoInput.transmitOverLora,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { transmitOverLora = it } },
|
||||
checked = formState.value.transmitOverLora,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { transmitOverLora = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && neighborInfoInput != neighborInfoConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
neighborInfoInput = neighborInfoConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(neighborInfoInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NeighborInfoConfigPreview() {
|
||||
NeighborInfoConfigItemList(
|
||||
neighborInfoConfig = ModuleConfigProtos.ModuleConfig.NeighborInfoConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,11 +18,9 @@
|
|||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.Button
|
||||
|
|
@ -38,10 +36,10 @@ import androidx.compose.ui.platform.LocalFocusManager
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.NetworkConfig
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
|
|
@ -50,7 +48,6 @@ import com.geeksville.mesh.ui.common.components.EditIPv4Preference
|
|||
import com.geeksville.mesh.ui.common.components.EditPasswordPreference
|
||||
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.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
|
@ -63,40 +60,10 @@ 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(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
NetworkConfigItemList(
|
||||
hasWifi = state.metadata?.hasWifi ?: true,
|
||||
hasEthernet = state.metadata?.hasEthernet ?: true,
|
||||
networkConfig = state.radioConfig.network,
|
||||
enabled = state.connected,
|
||||
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)
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun NetworkConfigItemList(
|
||||
hasWifi: Boolean,
|
||||
hasEthernet: Boolean,
|
||||
networkConfig: NetworkConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (NetworkConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var networkInput by rememberSaveable { mutableStateOf(networkConfig) }
|
||||
val networkConfig = state.radioConfig.network
|
||||
val formState = rememberConfigState(initialValue = networkConfig)
|
||||
|
||||
var showScanErrorDialog: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
if (showScanErrorDialog) {
|
||||
|
|
@ -108,8 +75,8 @@ fun NetworkConfigItemList(
|
|||
if (result.contents != null) {
|
||||
val (ssid, psk) = extractWifiCredentials(result.contents)
|
||||
if (ssid != null && psk != null) {
|
||||
networkInput =
|
||||
networkInput.copy {
|
||||
formState.value =
|
||||
formState.value.copy {
|
||||
wifiSsid = ssid
|
||||
wifiPsk = psk
|
||||
}
|
||||
|
|
@ -129,17 +96,29 @@ fun NetworkConfigItemList(
|
|||
}
|
||||
barcodeLauncher.launch(zxingScan)
|
||||
}
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
if (hasWifi) {
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.network),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { network = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
if (state.metadata?.hasWifi == true) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.wifi_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wifi_enabled),
|
||||
summary = stringResource(id = R.string.config_network_wifi_enabled_summary),
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = enabled && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } },
|
||||
checked = formState.value.wifiEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { wifiEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -147,25 +126,25 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ssid),
|
||||
value = networkInput.wifiSsid,
|
||||
value = formState.value.wifiSsid,
|
||||
maxSize = 32, // wifi_ssid max_size:33
|
||||
enabled = enabled && hasWifi,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiSsid = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { wifiSsid = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditPasswordPreference(
|
||||
title = stringResource(R.string.password),
|
||||
value = networkInput.wifiPsk,
|
||||
value = formState.value.wifiPsk,
|
||||
maxSize = 64, // wifi_psk max_size:65
|
||||
enabled = enabled && hasWifi,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { wifiPsk = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -173,37 +152,38 @@ fun NetworkConfigItemList(
|
|||
Button(
|
||||
onClick = { zxingScan() },
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).height(48.dp),
|
||||
enabled = enabled && hasWifi,
|
||||
enabled = state.connected,
|
||||
) {
|
||||
Text(text = stringResource(R.string.wifi_qr_code_scan))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasEthernet) {
|
||||
if (state.metadata?.hasEthernet == true) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.ethernet_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ethernet_enabled),
|
||||
summary = stringResource(id = R.string.config_network_eth_enabled_summary),
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled && hasEthernet,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } },
|
||||
checked = formState.value.ethEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { ethEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
if (hasEthernet || hasWifi) {
|
||||
if (state.metadata?.hasEthernet == true || state.metadata?.hasWifi == true) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.udp_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.udp_enabled),
|
||||
summary = stringResource(id = R.string.config_network_udp_enabled_summary),
|
||||
checked = networkInput.enabledProtocols == 1,
|
||||
enabled = enabled,
|
||||
checked = formState.value.enabledProtocols == 1,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = {
|
||||
networkInput = networkInput.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
formState.value =
|
||||
formState.value.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -215,41 +195,41 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ntp_server),
|
||||
value = networkInput.ntpServer,
|
||||
value = formState.value.ntpServer,
|
||||
maxSize = 32, // ntp_server max_size:33
|
||||
enabled = enabled,
|
||||
isError = networkInput.ntpServer.isEmpty(),
|
||||
enabled = state.connected,
|
||||
isError = formState.value.ntpServer.isEmpty(),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { ntpServer = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { ntpServer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.rsyslog_server),
|
||||
value = networkInput.rsyslogServer,
|
||||
value = formState.value.rsyslogServer,
|
||||
maxSize = 32, // rsyslog_server max_size:33
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { rsyslogServer = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { rsyslogServer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.ipv4_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
NetworkConfig.AddressMode.entries
|
||||
.filter { it != NetworkConfig.AddressMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = networkInput.addressMode,
|
||||
onItemSelected = { networkInput = networkInput.copy { addressMode = it } },
|
||||
selectedItem = formState.value.addressMode,
|
||||
onItemSelected = { formState.value = formState.value.copy { addressMode = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -257,12 +237,12 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
EditIPv4Preference(
|
||||
title = stringResource(R.string.ip),
|
||||
value = networkInput.ipv4Config.ip,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.ip,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { ip = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { ip = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -270,12 +250,12 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
EditIPv4Preference(
|
||||
title = stringResource(R.string.gateway),
|
||||
value = networkInput.ipv4Config.gateway,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.gateway,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { gateway = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { gateway = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -283,12 +263,12 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
EditIPv4Preference(
|
||||
title = stringResource(R.string.subnet),
|
||||
value = networkInput.ipv4Config.subnet,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.subnet,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { subnet = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { subnet = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -296,47 +276,19 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
EditIPv4Preference(
|
||||
title = "DNS",
|
||||
value = networkInput.ipv4Config.dns,
|
||||
enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
value = formState.value.ipv4Config.dns,
|
||||
enabled = state.connected && formState.value.addressMode == NetworkConfig.AddressMode.STATIC,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { dns = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
val ipv4 = formState.value.ipv4Config.copy { dns = it }
|
||||
formState.value = formState.value.copy { ipv4Config = ipv4 }
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && networkInput != networkConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
networkInput = networkConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(networkInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NetworkConfigPreview() {
|
||||
NetworkConfigItemList(
|
||||
hasWifi = true,
|
||||
hasEthernet = true,
|
||||
networkConfig = NetworkConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun QrCodeErrorDialogPreview() {
|
||||
ScanErrorDialog()
|
||||
}
|
||||
private fun extractWifiCredentials(qrCode: String) =
|
||||
Regex("""WIFI:S:(.*?);.*?P:(.*?);""").find(qrCode)?.destructured?.let { (ssid, password) -> ssid to password }
|
||||
?: (null to null)
|
||||
|
|
|
|||
|
|
@ -17,69 +17,51 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.SignedIntegerEditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun PaxcounterConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val paxcounterConfig = state.moduleConfig.paxcounter
|
||||
val formState = rememberConfigState(initialValue = paxcounterConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PaxcounterConfigItemList(
|
||||
paxcounterConfig = state.moduleConfig.paxcounter,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.paxcounter),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { paxcounterConfigInput ->
|
||||
val config = moduleConfig { paxcounter = paxcounterConfigInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { paxcounter = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun PaxcounterConfigItemList(
|
||||
paxcounterConfig: ModuleConfigProtos.ModuleConfig.PaxcounterConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (ModuleConfigProtos.ModuleConfig.PaxcounterConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var paxcounterInput by rememberSaveable { mutableStateOf(paxcounterConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.paxcounter_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.paxcounter_enabled),
|
||||
checked = paxcounterInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { paxcounterInput = paxcounterInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -87,55 +69,31 @@ fun PaxcounterConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.update_interval_seconds),
|
||||
value = paxcounterInput.paxcounterUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.paxcounterUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { paxcounterUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { paxcounterUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.wifi_rssi_threshold_defaults_to_80),
|
||||
value = paxcounterInput.wifiThreshold,
|
||||
enabled = enabled,
|
||||
value = formState.value.wifiThreshold,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { wifiThreshold = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { wifiThreshold = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.ble_rssi_threshold_defaults_to_80),
|
||||
value = paxcounterInput.bleThreshold,
|
||||
enabled = enabled,
|
||||
value = formState.value.bleThreshold,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { bleThreshold = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && paxcounterInput != paxcounterConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
paxcounterInput = paxcounterConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(paxcounterInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { bleThreshold = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PaxcounterConfigPreview() {
|
||||
PaxcounterConfigItemList(
|
||||
paxcounterConfig = ModuleConfigProtos.ModuleConfig.PaxcounterConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ import android.Manifest
|
|||
import android.annotation.SuppressLint
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -35,13 +33,12 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.core.location.LocationCompat
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import com.geeksville.mesh.ConfigProtos.Config.PositionConfig
|
||||
import com.geeksville.mesh.Position
|
||||
|
|
@ -51,7 +48,6 @@ import com.geeksville.mesh.ui.common.components.BitwisePreference
|
|||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
|
|
@ -61,7 +57,7 @@ import org.meshtastic.core.strings.R
|
|||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun PositionConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var phoneLocation: Location? by remember { mutableStateOf(null) }
|
||||
|
|
@ -73,120 +69,104 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
|||
altitude = node?.position?.altitude ?: 0,
|
||||
time = 1, // ignore time for fixed_position
|
||||
)
|
||||
val positionConfig = state.radioConfig.position
|
||||
val formState = rememberConfigState(initialValue = positionConfig)
|
||||
var locationInput by rememberSaveable { mutableStateOf(currentPosition) }
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PositionConfigItemList(
|
||||
phoneLocation = phoneLocation,
|
||||
location = currentPosition,
|
||||
positionConfig = state.radioConfig.position,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { locationInput, positionInput ->
|
||||
if (positionInput.fixedPosition) {
|
||||
if (locationInput != currentPosition) {
|
||||
viewModel.setFixedPosition(locationInput)
|
||||
}
|
||||
} else {
|
||||
if (state.radioConfig.position.fixedPosition) {
|
||||
// fixed position changed from enabled to disabled
|
||||
viewModel.removeFixedPosition()
|
||||
}
|
||||
}
|
||||
val config = config { position = positionInput }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onUseCurrentLocation = {
|
||||
@SuppressLint("MissingPermission")
|
||||
coroutineScope.launch { phoneLocation = viewModel.getCurrentLocation() }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun PositionConfigItemList(
|
||||
phoneLocation: Location? = null,
|
||||
location: Position,
|
||||
positionConfig: PositionConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (position: Position, config: PositionConfig) -> Unit,
|
||||
onUseCurrentLocation: suspend () -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val locationPermissionState =
|
||||
rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) { granted ->
|
||||
if (granted) {
|
||||
coroutineScope.launch { onUseCurrentLocation() }
|
||||
@SuppressLint("MissingPermission")
|
||||
coroutineScope.launch { phoneLocation = viewModel.getCurrentLocation() }
|
||||
}
|
||||
}
|
||||
var locationInput by rememberSaveable { mutableStateOf(location) }
|
||||
var positionInput by rememberSaveable { mutableStateOf(positionConfig) }
|
||||
|
||||
LaunchedEffect(phoneLocation) {
|
||||
if (phoneLocation != null) {
|
||||
phoneLocation?.let { phoneLoc ->
|
||||
locationInput =
|
||||
Position(
|
||||
latitude = phoneLocation.latitude,
|
||||
longitude = phoneLocation.longitude,
|
||||
latitude = phoneLoc.latitude,
|
||||
longitude = phoneLoc.longitude,
|
||||
altitude =
|
||||
LocationCompat.hasMslAltitude(phoneLocation).let {
|
||||
LocationCompat.hasMslAltitude(phoneLoc).let {
|
||||
if (it && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
phoneLocation.mslAltitudeMeters.toInt()
|
||||
phoneLoc.mslAltitudeMeters.toInt()
|
||||
} else {
|
||||
phoneLocation.altitude.toInt()
|
||||
phoneLoc.altitude.toInt()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.position),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
if (formState.value.fixedPosition) {
|
||||
if (locationInput != currentPosition) {
|
||||
viewModel.setFixedPosition(locationInput)
|
||||
}
|
||||
} else {
|
||||
if (positionConfig.fixedPosition) {
|
||||
// fixed position changed from enabled to disabled
|
||||
viewModel.removeFixedPosition()
|
||||
}
|
||||
}
|
||||
val config = config { position = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_packet)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.broadcast_interval),
|
||||
summary = stringResource(id = R.string.config_position_broadcast_secs_summary),
|
||||
value = positionInput.positionBroadcastSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.positionBroadcastSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { positionBroadcastSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { positionBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.smart_position),
|
||||
checked = positionInput.positionBroadcastSmartEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { positionInput = positionInput.copy { positionBroadcastSmartEnabled = it } },
|
||||
checked = formState.value.positionBroadcastSmartEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { positionBroadcastSmartEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (positionInput.positionBroadcastSmartEnabled) {
|
||||
if (formState.value.positionBroadcastSmartEnabled) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_interval),
|
||||
summary =
|
||||
stringResource(id = R.string.config_position_broadcast_smart_minimum_interval_secs_summary),
|
||||
value = positionInput.broadcastSmartMinimumIntervalSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.broadcastSmartMinimumIntervalSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumIntervalSecs = it } },
|
||||
onValueChanged = {
|
||||
formState.value = formState.value.copy { broadcastSmartMinimumIntervalSecs = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_distance),
|
||||
summary = stringResource(id = R.string.config_position_broadcast_smart_minimum_distance_summary),
|
||||
value = positionInput.broadcastSmartMinimumDistance,
|
||||
enabled = enabled,
|
||||
value = formState.value.broadcastSmartMinimumDistance,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumDistance = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { broadcastSmartMinimumDistance = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -194,19 +174,19 @@ fun PositionConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.fixed_position),
|
||||
checked = positionInput.fixedPosition,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { positionInput = positionInput.copy { fixedPosition = it } },
|
||||
checked = formState.value.fixedPosition,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { fixedPosition = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
if (positionInput.fixedPosition) {
|
||||
if (formState.value.fixedPosition) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.latitude),
|
||||
value = locationInput.latitude,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value >= -90 && value <= 90.0) {
|
||||
|
|
@ -219,7 +199,7 @@ fun PositionConfigItemList(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.longitude),
|
||||
value = locationInput.longitude,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value ->
|
||||
if (value >= -180 && value <= 180.0) {
|
||||
|
|
@ -232,14 +212,14 @@ fun PositionConfigItemList(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.altitude),
|
||||
value = locationInput.altitude,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { value -> locationInput = locationInput.copy(altitude = value) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
TextButton(
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
onClick = { coroutineScope.launch { locationPermissionState.launchPermissionRequest() } },
|
||||
) {
|
||||
Text(text = stringResource(R.string.position_config_set_fixed_from_phone))
|
||||
|
|
@ -250,13 +230,13 @@ fun PositionConfigItemList(
|
|||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.gps_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
ConfigProtos.Config.PositionConfig.GpsMode.entries
|
||||
.filter { it != ConfigProtos.Config.PositionConfig.GpsMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = positionInput.gpsMode,
|
||||
onItemSelected = { positionInput = positionInput.copy { gpsMode = it } },
|
||||
selectedItem = formState.value.gpsMode,
|
||||
onItemSelected = { formState.value = formState.value.copy { gpsMode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -265,10 +245,10 @@ fun PositionConfigItemList(
|
|||
EditTextPreference(
|
||||
title = stringResource(R.string.update_interval),
|
||||
summary = stringResource(id = R.string.config_position_gps_update_interval_summary),
|
||||
value = positionInput.gpsUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.gpsUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { gpsUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { gpsUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_flags)) }
|
||||
|
|
@ -276,15 +256,15 @@ fun PositionConfigItemList(
|
|||
BitwisePreference(
|
||||
title = stringResource(R.string.position_flags),
|
||||
summary = stringResource(id = R.string.config_position_flags_summary),
|
||||
value = positionInput.positionFlags,
|
||||
enabled = enabled,
|
||||
value = formState.value.positionFlags,
|
||||
enabled = state.connected,
|
||||
items =
|
||||
ConfigProtos.Config.PositionConfig.PositionFlags.entries
|
||||
.filter {
|
||||
it != PositionConfig.PositionFlags.UNSET && it != PositionConfig.PositionFlags.UNRECOGNIZED
|
||||
}
|
||||
.map { it.number to it.name },
|
||||
onItemSelected = { positionInput = positionInput.copy { positionFlags = it } },
|
||||
onItemSelected = { formState.value = formState.value.copy { positionFlags = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -293,58 +273,31 @@ fun PositionConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_receive_gpio),
|
||||
value = positionInput.rxGpio,
|
||||
enabled = enabled,
|
||||
value = formState.value.rxGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { rxGpio = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { rxGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_transmit_gpio),
|
||||
value = positionInput.txGpio,
|
||||
enabled = enabled,
|
||||
value = formState.value.txGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { txGpio = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { txGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_en_gpio),
|
||||
value = positionInput.gpsEnGpio,
|
||||
enabled = enabled,
|
||||
value = formState.value.gpsEnGpio,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { gpsEnGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && positionInput != positionConfig || locationInput != location,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
locationInput = location
|
||||
positionInput = positionConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(locationInput, positionInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { gpsEnGpio = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PositionConfigPreview() {
|
||||
PositionConfigItemList(
|
||||
location = Position(0.0, 0.0, 0),
|
||||
positionConfig = PositionConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { _, _ -> },
|
||||
onUseCurrentLocation = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,67 +17,51 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ConfigProtos.Config.PowerConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun PowerConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val powerConfig = state.radioConfig.power
|
||||
val formState = rememberConfigState(initialValue = powerConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PowerConfigItemList(
|
||||
powerConfig = state.radioConfig.power,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.power),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { powerInput ->
|
||||
val config = config { power = powerInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { power = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicked: (PowerConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var powerInput by rememberSaveable { mutableStateOf(powerConfig) }
|
||||
var shutdownOnPowerLoss by rememberSaveable { mutableStateOf(powerConfig.onBatteryShutdownAfterSecs > 0) }
|
||||
var adcOverride by rememberSaveable { mutableStateOf(powerConfig.adcMultiplierOverride > 0f) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.power_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.enable_power_saving_mode),
|
||||
summary = stringResource(id = R.string.config_power_is_power_saving_summary),
|
||||
checked = powerInput.isPowerSaving,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { powerInput = powerInput.copy { isPowerSaving = it } },
|
||||
checked = formState.value.isPowerSaving,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isPowerSaving = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -85,23 +69,22 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.shutdown_on_power_loss),
|
||||
checked = shutdownOnPowerLoss,
|
||||
enabled = enabled,
|
||||
checked = formState.value.onBatteryShutdownAfterSecs > 0,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = {
|
||||
shutdownOnPowerLoss = it
|
||||
if (!it) powerInput = powerInput.copy { onBatteryShutdownAfterSecs = 0 }
|
||||
formState.value = formState.value.copy { onBatteryShutdownAfterSecs = if (it) 3600 else 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (shutdownOnPowerLoss) {
|
||||
if (formState.value.onBatteryShutdownAfterSecs > 0) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.shutdown_on_battery_delay_seconds),
|
||||
value = powerInput.onBatteryShutdownAfterSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.onBatteryShutdownAfterSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { onBatteryShutdownAfterSecs = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -111,23 +94,22 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override),
|
||||
checked = adcOverride,
|
||||
enabled = enabled,
|
||||
checked = formState.value.adcMultiplierOverride > 0f,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = {
|
||||
adcOverride = it
|
||||
if (!it) powerInput = powerInput.copy { adcMultiplierOverride = 0f }
|
||||
formState.value = formState.value.copy { adcMultiplierOverride = if (it) 1.0f else 0.0f }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (adcOverride) {
|
||||
if (formState.value.adcMultiplierOverride > 0f) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override_ratio),
|
||||
value = powerInput.adcMultiplierOverride,
|
||||
enabled = enabled,
|
||||
value = formState.value.adcMultiplierOverride,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { adcMultiplierOverride = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -137,61 +119,41 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.wait_for_bluetooth_duration_seconds),
|
||||
value = powerInput.waitBluetoothSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.waitBluetoothSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { waitBluetoothSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { waitBluetoothSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.super_deep_sleep_duration_seconds),
|
||||
value = powerInput.sdsSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.sdsSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { sdsSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { sdsSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_wake_time_seconds),
|
||||
value = powerInput.minWakeSecs,
|
||||
enabled = enabled,
|
||||
value = formState.value.minWakeSecs,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { minWakeSecs = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { minWakeSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.battery_ina_2xx_i2c_address),
|
||||
value = powerInput.deviceBatteryInaAddress,
|
||||
enabled = enabled,
|
||||
value = formState.value.deviceBatteryInaAddress,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { deviceBatteryInaAddress = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && powerInput != powerConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
powerInput = powerConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(powerInput)
|
||||
},
|
||||
onValueChanged = { formState.value = formState.value.copy { deviceBatteryInaAddress = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PowerConfigPreview() {
|
||||
PowerConfigItemList(powerConfig = PowerConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import com.geeksville.mesh.ui.common.components.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.settings.radio.ResponseState
|
||||
import com.google.protobuf.MessageLite
|
||||
|
||||
@Composable
|
||||
fun <T : MessageLite> RadioConfigScreenList(
|
||||
title: String,
|
||||
onBack: () -> Unit,
|
||||
responseState: ResponseState<Any>,
|
||||
onDismissPacketResponse: () -> Unit,
|
||||
configState: ConfigState<T>,
|
||||
enabled: Boolean,
|
||||
onSave: (T) -> Unit,
|
||||
content: LazyListScope.() -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = responseState, onDismiss = onDismissPacketResponse)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = title,
|
||||
canNavigateUp = true,
|
||||
onNavigateUp = onBack,
|
||||
ourNode = null,
|
||||
isConnected = false,
|
||||
showNodeChip = false,
|
||||
actions = {},
|
||||
onAction = {},
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
LazyColumn(modifier = Modifier.fillMaxSize().padding(innerPadding)) {
|
||||
content()
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && configState.isDirty,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
configState.reset()
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSave(configState.value)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,67 +17,50 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.RangeTestConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun RangeTestConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val rangeTestConfig = state.moduleConfig.rangeTest
|
||||
val formState = rememberConfigState(initialValue = rangeTestConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
RangeTestConfigItemList(
|
||||
rangeTestConfig = state.moduleConfig.rangeTest,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.range_test),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { rangeTestInput ->
|
||||
val config = moduleConfig { rangeTest = rangeTestInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { rangeTest = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RangeTestConfigItemList(
|
||||
rangeTestConfig: RangeTestConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (RangeTestConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var rangeTestInput by rememberSaveable { mutableStateOf(rangeTestConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.range_test_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.range_test_enabled),
|
||||
checked = rangeTestInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -85,41 +68,21 @@ fun RangeTestConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.sender_message_interval_seconds),
|
||||
value = rangeTestInput.sender,
|
||||
enabled = enabled,
|
||||
value = formState.value.sender,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { sender = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.save_csv_in_storage_esp32_only),
|
||||
checked = rangeTestInput.save,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } },
|
||||
checked = formState.value.save,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { save = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && rangeTestInput != rangeTestConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
rangeTestInput = rangeTestConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(rangeTestInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RangeTestConfig() {
|
||||
RangeTestConfigItemList(rangeTestConfig = RangeTestConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,67 +17,50 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.RemoteHardwareConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun RemoteHardwareConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun RemoteHardwareConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val remoteHardwareConfig = state.moduleConfig.remoteHardware
|
||||
val formState = rememberConfigState(initialValue = remoteHardwareConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig = state.moduleConfig.remoteHardware,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.remote_hardware),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { remoteHardwareInput ->
|
||||
val config = moduleConfig { remoteHardware = remoteHardwareInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { remoteHardware = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig: RemoteHardwareConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (RemoteHardwareConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var remoteHardwareInput by rememberSaveable { mutableStateOf(remoteHardwareConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.remote_hardware_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.remote_hardware_enabled),
|
||||
checked = remoteHardwareInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -85,9 +68,9 @@ fun RemoteHardwareConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.allow_undefined_pin_access),
|
||||
checked = remoteHardwareInput.allowUndefinedPinAccess,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { allowUndefinedPinAccess = it } },
|
||||
checked = formState.value.allowUndefinedPinAccess,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { allowUndefinedPinAccess = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -95,42 +78,18 @@ fun RemoteHardwareConfigItemList(
|
|||
item {
|
||||
EditListPreference(
|
||||
title = stringResource(R.string.available_pins),
|
||||
list = remoteHardwareInput.availablePinsList,
|
||||
list = formState.value.availablePinsList,
|
||||
maxCount = 4, // available_pins max_count:4
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = { list ->
|
||||
remoteHardwareInput =
|
||||
remoteHardwareInput.copy {
|
||||
formState.value =
|
||||
formState.value.copy {
|
||||
availablePins.clear()
|
||||
availablePins.addAll(list)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && remoteHardwareInput != remoteHardwareConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
remoteHardwareInput = remoteHardwareConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(remoteHardwareInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RemoteHardwareConfigPreview() {
|
||||
RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig = RemoteHardwareConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,9 @@ package com.geeksville.mesh.ui.settings.radio.components
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Warning
|
||||
|
|
@ -42,19 +39,17 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ConfigProtos.Config.SecurityConfig
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.config
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.ui.common.components.CopyIconButton
|
||||
import com.geeksville.mesh.ui.common.components.EditBase64Preference
|
||||
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.node.NodeActionButton
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
|
@ -64,45 +59,19 @@ import com.google.protobuf.ByteString
|
|||
import org.meshtastic.core.strings.R
|
||||
import java.security.SecureRandom
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun SecurityConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val node by viewModel.destNode.collectAsStateWithLifecycle()
|
||||
val securityConfig = state.radioConfig.security
|
||||
val formState = rememberConfigState(initialValue = securityConfig)
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
SecurityConfigItemList(
|
||||
user = node?.user,
|
||||
securityConfig = state.radioConfig.security,
|
||||
enabled = state.connected,
|
||||
onConfirm = { securityInput ->
|
||||
val config = config { security = securityInput }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onExport = { uri, securityConfig -> viewModel.exportSecurityConfig(uri, securityConfig) },
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SecurityConfigItemList(
|
||||
user: MeshProtos.User? = null,
|
||||
securityConfig: SecurityConfig,
|
||||
enabled: Boolean,
|
||||
onConfirm: (config: SecurityConfig) -> Unit,
|
||||
onExport: (uri: Uri, securityConfig: SecurityConfig) -> Unit = { _, _ -> },
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var securityInput by rememberSaveable { mutableStateOf(securityConfig) }
|
||||
|
||||
var publicKey by rememberSaveable { mutableStateOf(securityInput.publicKey) }
|
||||
LaunchedEffect(securityInput.privateKey) {
|
||||
if (securityInput.privateKey != securityConfig.privateKey) {
|
||||
var publicKey by rememberSaveable { mutableStateOf(formState.value.publicKey) }
|
||||
LaunchedEffect(formState.value.privateKey) {
|
||||
if (formState.value.privateKey != securityConfig.privateKey) {
|
||||
publicKey = "".toByteString()
|
||||
} else if (securityInput.privateKey == securityConfig.privateKey) {
|
||||
} else if (formState.value.privateKey == securityConfig.privateKey) {
|
||||
publicKey = securityConfig.publicKey
|
||||
}
|
||||
}
|
||||
|
|
@ -110,18 +79,18 @@ fun SecurityConfigItemList(
|
|||
val exportConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> onExport(uri, securityConfig) }
|
||||
it.data?.data?.let { uri -> viewModel.exportSecurityConfig(uri, securityConfig) }
|
||||
}
|
||||
}
|
||||
|
||||
var showKeyGenerationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
PrivateKeyRegenerateDialog(
|
||||
showKeyGenerationDialog = showKeyGenerationDialog,
|
||||
config = securityInput,
|
||||
onConfirm = { newConfig ->
|
||||
securityInput = newConfig
|
||||
onConfirm = {
|
||||
formState.value = it
|
||||
showKeyGenerationDialog = false
|
||||
onConfirm(securityInput)
|
||||
val config = config { security = formState.value }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onDismiss = { showKeyGenerationDialog = false },
|
||||
)
|
||||
|
|
@ -141,7 +110,7 @@ fun SecurityConfigItemList(
|
|||
type = "application/*"
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
"${user?.shortName}_keys_${System.currentTimeMillis()}.json",
|
||||
"${node?.user?.shortName}_keys_${System.currentTimeMillis()}.json",
|
||||
)
|
||||
}
|
||||
exportConfigLauncher.launch(intent)
|
||||
|
|
@ -153,7 +122,19 @@ fun SecurityConfigItemList(
|
|||
)
|
||||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.security),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = config { security = it }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.direct_message_key)) }
|
||||
|
||||
item {
|
||||
|
|
@ -161,15 +142,15 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.public_key),
|
||||
summary = stringResource(id = R.string.config_security_public_key),
|
||||
value = publicKey,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
readOnly = true,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChange = {
|
||||
if (it.size() == 32) {
|
||||
securityInput = securityInput.copy { this.publicKey = it }
|
||||
formState.value = formState.value.copy { this.publicKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.publicKey.encodeToString()) },
|
||||
trailingIcon = { CopyIconButton(valueToCopy = formState.value.publicKey.encodeToString()) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -177,15 +158,15 @@ fun SecurityConfigItemList(
|
|||
EditBase64Preference(
|
||||
title = stringResource(R.string.private_key),
|
||||
summary = stringResource(id = R.string.config_security_private_key),
|
||||
value = securityInput.privateKey,
|
||||
enabled = enabled,
|
||||
value = formState.value.privateKey,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChange = {
|
||||
if (it.size() == 32) {
|
||||
securityInput = securityInput.copy { privateKey = it }
|
||||
formState.value = formState.value.copy { privateKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.privateKey.encodeToString()) },
|
||||
trailingIcon = { CopyIconButton(valueToCopy = formState.value.privateKey.encodeToString()) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -193,7 +174,7 @@ fun SecurityConfigItemList(
|
|||
NodeActionButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
title = stringResource(R.string.regenerate_private_key),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = { showKeyGenerationDialog = true },
|
||||
)
|
||||
|
|
@ -203,7 +184,7 @@ fun SecurityConfigItemList(
|
|||
NodeActionButton(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
title = stringResource(R.string.export_keys),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = { showEditSecurityConfigDialog = true },
|
||||
)
|
||||
|
|
@ -213,13 +194,13 @@ fun SecurityConfigItemList(
|
|||
EditListPreference(
|
||||
title = stringResource(R.string.admin_key),
|
||||
summary = stringResource(id = R.string.config_security_admin_key),
|
||||
list = securityInput.adminKeyList,
|
||||
list = formState.value.adminKeyList,
|
||||
maxCount = 3,
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = {
|
||||
securityInput =
|
||||
securityInput.copy {
|
||||
formState.value =
|
||||
formState.value.copy {
|
||||
adminKey.clear()
|
||||
adminKey.addAll(it)
|
||||
}
|
||||
|
|
@ -231,9 +212,9 @@ fun SecurityConfigItemList(
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.serial_console),
|
||||
summary = stringResource(id = R.string.config_security_serial_enabled),
|
||||
checked = securityInput.serialEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } },
|
||||
checked = formState.value.serialEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { serialEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -242,9 +223,9 @@ fun SecurityConfigItemList(
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.debug_log_api_enabled),
|
||||
summary = stringResource(id = R.string.config_security_debug_log_api_enabled),
|
||||
checked = securityInput.debugLogApiEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { debugLogApiEnabled = it } },
|
||||
checked = formState.value.debugLogApiEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { debugLogApiEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -253,9 +234,9 @@ fun SecurityConfigItemList(
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.managed_mode),
|
||||
summary = stringResource(id = R.string.config_security_is_managed),
|
||||
checked = securityInput.isManaged,
|
||||
enabled = enabled && securityInput.adminKeyCount > 0,
|
||||
onCheckedChange = { securityInput = securityInput.copy { isManaged = it } },
|
||||
checked = formState.value.isManaged,
|
||||
enabled = state.connected && formState.value.adminKeyCount > 0,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isManaged = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -263,26 +244,12 @@ fun SecurityConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.legacy_admin_channel),
|
||||
checked = securityInput.adminChannelEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { adminChannelEnabled = it } },
|
||||
checked = formState.value.adminChannelEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { adminChannelEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && securityInput != securityConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
securityInput = securityConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -290,11 +257,9 @@ fun SecurityConfigItemList(
|
|||
@Composable
|
||||
fun PrivateKeyRegenerateDialog(
|
||||
showKeyGenerationDialog: Boolean,
|
||||
config: SecurityConfig,
|
||||
onConfirm: (SecurityConfig) -> Unit,
|
||||
onDismiss: () -> Unit = {},
|
||||
) {
|
||||
var securityInput by rememberSaveable { mutableStateOf(config) }
|
||||
if (showKeyGenerationDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
|
|
@ -303,20 +268,22 @@ fun PrivateKeyRegenerateDialog(
|
|||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
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)
|
||||
}
|
||||
val securityInput =
|
||||
SecurityConfig.newBuilder()
|
||||
.apply {
|
||||
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)
|
||||
}
|
||||
.build()
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
) {
|
||||
|
|
@ -327,9 +294,3 @@ fun PrivateKeyRegenerateDialog(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SecurityConfigPreview() {
|
||||
SecurityConfigItemList(securityConfig = SecurityConfig.getDefaultInstance(), enabled = true, onConfirm = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,65 +17,52 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.SerialConfig
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
import com.geeksville.mesh.ui.common.components.DropDownPreference
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun SerialConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val serialConfig = state.moduleConfig.serial
|
||||
val formState = rememberConfigState(initialValue = serialConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
SerialConfigItemList(
|
||||
serialConfig = state.moduleConfig.serial,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.serial),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { serialInput ->
|
||||
val config = moduleConfig { serial = serialInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { serial = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveClicked: (SerialConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var serialInput by rememberSaveable { mutableStateOf(serialConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.serial_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.serial_enabled),
|
||||
checked = serialInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -83,9 +70,9 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.echo_enabled),
|
||||
checked = serialInput.echo,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { echo = it } },
|
||||
checked = formState.value.echo,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { echo = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -93,33 +80,33 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = "RX",
|
||||
value = serialInput.rxd,
|
||||
enabled = enabled,
|
||||
value = formState.value.rxd,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { rxd = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { rxd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = "TX",
|
||||
value = serialInput.txd,
|
||||
enabled = enabled,
|
||||
value = formState.value.txd,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { txd = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { txd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.serial_baud_rate),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
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 } },
|
||||
selectedItem = formState.value.baud,
|
||||
onItemSelected = { formState.value = formState.value.copy { baud = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -127,23 +114,23 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.timeout),
|
||||
value = serialInput.timeout,
|
||||
enabled = enabled,
|
||||
value = formState.value.timeout,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { timeout = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { timeout = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.serial_mode),
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
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 } },
|
||||
selectedItem = formState.value.mode,
|
||||
onItemSelected = { formState.value = formState.value.copy { mode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -151,31 +138,11 @@ fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveCli
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.override_console_serial_port),
|
||||
checked = serialInput.overrideConsoleSerialPort,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { overrideConsoleSerialPort = it } },
|
||||
checked = formState.value.overrideConsoleSerialPort,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { overrideConsoleSerialPort = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && serialInput != serialConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
serialInput = serialConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(serialInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SerialConfigPreview() {
|
||||
SerialConfigItemList(serialConfig = SerialConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,67 +17,50 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.StoreForwardConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun StoreForwardConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun StoreForwardConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val storeForwardConfig = state.moduleConfig.storeForward
|
||||
val formState = rememberConfigState(initialValue = storeForwardConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
StoreForwardConfigItemList(
|
||||
storeForwardConfig = state.moduleConfig.storeForward,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.store_forward),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { storeForwardInput ->
|
||||
val config = moduleConfig { storeForward = storeForwardInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { storeForward = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StoreForwardConfigItemList(
|
||||
storeForwardConfig: StoreForwardConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (StoreForwardConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var storeForwardInput by rememberSaveable { mutableStateOf(storeForwardConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.store_forward_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.store_forward_enabled),
|
||||
checked = storeForwardInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { this.enabled = it } },
|
||||
checked = formState.value.enabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -85,9 +68,9 @@ fun StoreForwardConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.heartbeat),
|
||||
checked = storeForwardInput.heartbeat,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } },
|
||||
checked = formState.value.heartbeat,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { heartbeat = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -95,65 +78,41 @@ fun StoreForwardConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.number_of_records),
|
||||
value = storeForwardInput.records,
|
||||
enabled = enabled,
|
||||
value = formState.value.records,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { records = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.history_return_max),
|
||||
value = storeForwardInput.historyReturnMax,
|
||||
enabled = enabled,
|
||||
value = formState.value.historyReturnMax,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnMax = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { historyReturnMax = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.history_return_window),
|
||||
value = storeForwardInput.historyReturnWindow,
|
||||
enabled = enabled,
|
||||
value = formState.value.historyReturnWindow,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnWindow = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { historyReturnWindow = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.server),
|
||||
checked = storeForwardInput.isServer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { isServer = it } },
|
||||
checked = formState.value.isServer,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isServer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && storeForwardInput != storeForwardConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
storeForwardInput = storeForwardConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(storeForwardInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun StoreForwardConfigPreview() {
|
||||
StoreForwardConfigItemList(
|
||||
storeForwardConfig = StoreForwardConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,87 +17,70 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.TelemetryConfig
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.moduleConfig
|
||||
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.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun TelemetryConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val telemetryConfig = state.moduleConfig.telemetry
|
||||
val formState = rememberConfigState(initialValue = telemetryConfig)
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
TelemetryConfigItemList(
|
||||
telemetryConfig = state.moduleConfig.telemetry,
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.telemetry),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { telemetryInput ->
|
||||
val config = moduleConfig { telemetry = telemetryInput }
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = {
|
||||
val config = moduleConfig { telemetry = it }
|
||||
viewModel.setModuleConfig(config)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TelemetryConfigItemList(
|
||||
telemetryConfig: TelemetryConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (TelemetryConfig) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var telemetryInput by rememberSaveable { mutableStateOf(telemetryConfig) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.telemetry_config)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.device_metrics_update_interval_seconds),
|
||||
value = telemetryInput.deviceUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.deviceUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { deviceUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { deviceUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.environment_metrics_update_interval_seconds),
|
||||
value = telemetryInput.environmentUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.environmentUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { environmentUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { environmentUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.environment_metrics_module_enabled),
|
||||
checked = telemetryInput.environmentMeasurementEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it } },
|
||||
checked = formState.value.environmentMeasurementEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { environmentMeasurementEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -105,9 +88,9 @@ fun TelemetryConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.environment_metrics_on_screen_enabled),
|
||||
checked = telemetryInput.environmentScreenEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentScreenEnabled = it } },
|
||||
checked = formState.value.environmentScreenEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { environmentScreenEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -115,9 +98,9 @@ fun TelemetryConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.environment_metrics_use_fahrenheit),
|
||||
checked = telemetryInput.environmentDisplayFahrenheit,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it } },
|
||||
checked = formState.value.environmentDisplayFahrenheit,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { environmentDisplayFahrenheit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -125,9 +108,9 @@ fun TelemetryConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.air_quality_metrics_module_enabled),
|
||||
checked = telemetryInput.airQualityEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { airQualityEnabled = it } },
|
||||
checked = formState.value.airQualityEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { airQualityEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -135,19 +118,19 @@ fun TelemetryConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.air_quality_metrics_update_interval_seconds),
|
||||
value = telemetryInput.airQualityInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.airQualityInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { airQualityInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { airQualityInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.power_metrics_module_enabled),
|
||||
checked = telemetryInput.powerMeasurementEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { powerMeasurementEnabled = it } },
|
||||
checked = formState.value.powerMeasurementEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { powerMeasurementEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -155,41 +138,21 @@ fun TelemetryConfigItemList(
|
|||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.power_metrics_update_interval_seconds),
|
||||
value = telemetryInput.powerUpdateInterval,
|
||||
enabled = enabled,
|
||||
value = formState.value.powerUpdateInterval,
|
||||
enabled = state.connected,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { powerUpdateInterval = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { powerUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.power_metrics_on_screen_enabled),
|
||||
checked = telemetryInput.powerScreenEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { powerScreenEnabled = it } },
|
||||
checked = formState.value.powerScreenEnabled,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { powerScreenEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && telemetryInput != telemetryConfig,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
telemetryInput = telemetryConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(telemetryInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TelemetryConfigPreview() {
|
||||
TelemetryConfigItemList(telemetryConfig = TelemetryConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,32 +17,23 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import androidx.navigation.NavController
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.deviceMetadata
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.isUnmessageableRole
|
||||
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.RegularPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
|
@ -50,74 +41,65 @@ import com.geeksville.mesh.user
|
|||
import org.meshtastic.core.strings.R
|
||||
|
||||
@Composable
|
||||
fun UserConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
fun UserConfigScreen(navController: NavController, viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val userConfig = state.userConfig
|
||||
val formState = rememberConfigState(initialValue = userConfig)
|
||||
val firmwareVersion = DeviceVersion(state.metadata?.firmwareVersion ?: "")
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
UserConfigItemList(
|
||||
userConfig = state.userConfig,
|
||||
enabled = true,
|
||||
onSaveClicked = viewModel::setOwner,
|
||||
metadata = state.metadata,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun UserConfigItemList(
|
||||
metadata: MeshProtos.DeviceMetadata?,
|
||||
userConfig: MeshProtos.User,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (MeshProtos.User) -> Unit,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var userInput by rememberSaveable { mutableStateOf(userConfig) }
|
||||
val firmwareVersion = DeviceVersion(metadata?.firmwareVersion ?: "")
|
||||
|
||||
val validLongName = userInput.longName.isNotBlank()
|
||||
val validShortName = userInput.shortName.isNotBlank()
|
||||
val validLongName = formState.value.longName.isNotBlank()
|
||||
val validShortName = formState.value.shortName.isNotBlank()
|
||||
val validNames = validLongName && validShortName
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
RadioConfigScreenList(
|
||||
title = stringResource(id = R.string.user),
|
||||
onBack = { navController.popBackStack() },
|
||||
configState = formState,
|
||||
enabled = state.connected && validNames,
|
||||
responseState = state.responseState,
|
||||
onDismissPacketResponse = viewModel::clearPacketResponse,
|
||||
onSave = viewModel::setOwner,
|
||||
) {
|
||||
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 = formState.value.id, onClick = {})
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.long_name),
|
||||
value = userInput.longName,
|
||||
value = formState.value.longName,
|
||||
maxSize = 39, // long_name max_size:40
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = !validLongName,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { userInput = userInput.copy { longName = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { longName = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.short_name),
|
||||
value = userInput.shortName,
|
||||
value = formState.value.shortName,
|
||||
maxSize = 4, // short_name max_size:5
|
||||
enabled = enabled,
|
||||
enabled = state.connected,
|
||||
isError = !validShortName,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { userInput = userInput.copy { shortName = it } },
|
||||
onValueChanged = { formState.value = formState.value.copy { shortName = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
RegularPreference(
|
||||
title = stringResource(R.string.hardware_model),
|
||||
subtitle = userInput.hwModel.name,
|
||||
subtitle = formState.value.hwModel.name,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -128,10 +110,10 @@ fun UserConfigItemList(
|
|||
title = stringResource(R.string.unmessageable),
|
||||
summary = stringResource(R.string.unmonitored_or_infrastructure),
|
||||
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 } },
|
||||
formState.value.isUnmessagable ||
|
||||
(firmwareVersion < DeviceVersion("2.6.9") && formState.value.role.isUnmessageableRole()),
|
||||
enabled = formState.value.hasIsUnmessagable() || firmwareVersion >= DeviceVersion("2.6.9"),
|
||||
onCheckedChange = { formState.value = formState.value.copy { isUnmessagable = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,43 +123,11 @@ fun UserConfigItemList(
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.licensed_amateur_radio),
|
||||
summary = stringResource(R.string.licensed_amateur_radio_text),
|
||||
checked = userInput.isLicensed,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { userInput = userInput.copy { isLicensed = it } },
|
||||
checked = formState.value.isLicensed,
|
||||
enabled = state.connected,
|
||||
onCheckedChange = { formState.value = formState.value.copy { isLicensed = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && userInput != userConfig && validNames,
|
||||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
userInput = userConfig
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(userInput)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun UserConfigPreview() {
|
||||
UserConfigItemList(
|
||||
userConfig =
|
||||
user {
|
||||
id = "!a280d9c8"
|
||||
longName = "Meshtastic d9c8"
|
||||
shortName = "d9c8"
|
||||
hwModel = MeshProtos.HardwareModel.RAK4631
|
||||
isLicensed = false
|
||||
},
|
||||
enabled = true,
|
||||
onSaveClicked = {},
|
||||
metadata = deviceMetadata { firmwareVersion = "2.8.0" },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue