diff --git a/app/build.gradle b/app/build.gradle index 999b479be..f795c8f06 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -160,8 +160,14 @@ dependencies { // Hilt implementation "com.google.dagger:hilt-android:$hilt_version" + // implementation "androidx.hilt:hilt-navigation-compose:1.0.0" kapt "com.google.dagger:hilt-compiler:$hilt_version" + // Navigation + def nav_version = "2.5.3" + implementation "androidx.navigation:navigation-compose:$nav_version" + androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" + // Compose def composeBom = platform('androidx.compose:compose-bom:2023.03.00') implementation composeBom diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 86000ceec..3c7b578ec 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -768,20 +768,13 @@ class MainActivity : AppCompatActivity(), Logging { handler.removeCallbacksAndMessages(null) return true } - R.id.device_settings -> { + R.id.radio_config -> { supportFragmentManager.beginTransaction() .add(R.id.mainActivityLayout, DeviceSettingsFragment()) .addToBackStack(null) .commit() return true } - R.id.module_settings -> { - supportFragmentManager.beginTransaction() - .add(R.id.mainActivityLayout, ModuleSettingsFragment()) - .addToBackStack(null) - .commit() - return true - } R.id.save_messages_csv -> { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) diff --git a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt index 1ade5d694..62455c5c6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt @@ -4,13 +4,61 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Card +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.twotone.KeyboardArrowRight +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import androidx.fragment.app.activityViewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController import com.geeksville.mesh.R import com.geeksville.mesh.android.Logging import com.geeksville.mesh.model.UIViewModel +import com.geeksville.mesh.service.MeshService +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.config.AudioConfigItemList +import com.geeksville.mesh.ui.components.config.BluetoothConfigItemList +import com.geeksville.mesh.ui.components.config.CannedMessageConfigItemList +import com.geeksville.mesh.ui.components.config.DeviceConfigItemList +import com.geeksville.mesh.ui.components.config.DisplayConfigItemList +import com.geeksville.mesh.ui.components.config.ExternalNotificationConfigItemList +import com.geeksville.mesh.ui.components.config.LoRaConfigItemList +import com.geeksville.mesh.ui.components.config.MQTTConfigItemList +import com.geeksville.mesh.ui.components.config.NetworkConfigItemList +import com.geeksville.mesh.ui.components.config.PositionConfigItemList +import com.geeksville.mesh.ui.components.config.PowerConfigItemList +import com.geeksville.mesh.ui.components.config.RangeTestConfigItemList +import com.geeksville.mesh.ui.components.config.RemoteHardwareConfigItemList +import com.geeksville.mesh.ui.components.config.SerialConfigItemList +import com.geeksville.mesh.ui.components.config.StoreForwardConfigItemList +import com.geeksville.mesh.ui.components.config.TelemetryConfigItemList +import com.geeksville.mesh.ui.components.config.UserConfigItemList import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import dagger.hilt.android.AndroidEntryPoint @@ -29,9 +77,296 @@ class DeviceSettingsFragment : ScreenFragment("Device Settings"), Logging { setBackgroundColor(ContextCompat.getColor(context, R.color.colorAdvancedBackground)) setContent { AppCompatTheme { - DeviceSettingsItemList(model) + RadioConfigNavHost(model) } } } } } + +enum class ConfigDest(val title: String, val route: String) { + USER("User", "user"), + DEVICE("Device", "device"), + POSITION("Position", "position"), + POWER("Power", "power"), + NETWORK("Network", "network"), + DISPLAY("Display", "display"), + LORA("LoRa", "lora"), + BLUETOOTH("Bluetooth", "bluetooth") +} + +enum class ModuleDest(val title: String, val route: String) { + MQTT("MQTT", "mqtt"), + SERIAL("Serial", "serial"), + EXT_NOTIFICATION("External Notification", "ext_notification"), + STORE_FORWARD("Store & Forward", "store_forward"), + RANGE_TEST("Range Test", "range_test"), + TELEMETRY("Telemetry", "telemetry"), + CANNED_MESSAGE("Canned Message", "canned_message"), + AUDIO("Audio", "audio"), + REMOTE_HARDWARE("Remote Hardware", "remote_hardware") +} + +@Composable +fun RadioConfigNavHost(viewModel: UIViewModel = viewModel()) { + val navController = rememberNavController() + val focusManager = LocalFocusManager.current + + val connectionState by viewModel.connectionState.observeAsState() + val connected = connectionState == MeshService.ConnectionState.CONNECTED + + val ourNodeInfo by viewModel.ourNodeInfo.collectAsStateWithLifecycle() + val localConfig by viewModel.localConfig.collectAsStateWithLifecycle() + val moduleConfig by viewModel.moduleConfig.collectAsStateWithLifecycle() + + NavHost(navController = navController, startDestination = "home") { + composable("home") { RadioSettingsScreen(navController) } + composable("user") { + UserConfigItemList( + userConfig = ourNodeInfo?.user!!, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { userInput -> + focusManager.clearFocus() + viewModel.setOwner(userInput) + } + ) + } + composable("device") { + DeviceConfigItemList( + deviceConfig = localConfig.device, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { deviceInput -> + focusManager.clearFocus() + viewModel.updateDeviceConfig { deviceInput } + } + ) + } + composable("position") { + PositionConfigItemList( + positionInfo = ourNodeInfo?.position, + positionConfig = localConfig.position, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { positionPair -> + focusManager.clearFocus() + val (locationInput, positionInput) = positionPair + if (locationInput != ourNodeInfo?.position && positionInput.fixedPosition) + locationInput?.let { viewModel.requestPosition(0, it) } + if (positionInput != localConfig.position) viewModel.updatePositionConfig { positionInput } + } + ) + } + composable("power") { + PowerConfigItemList( + powerConfig = localConfig.power, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { powerInput -> + focusManager.clearFocus() + viewModel.updatePowerConfig { powerInput } + } + ) + } + composable("network") { + NetworkConfigItemList( + networkConfig = localConfig.network, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { networkInput -> + focusManager.clearFocus() + viewModel.updateNetworkConfig { networkInput } + } + ) + } + composable("display") { + DisplayConfigItemList( + displayConfig = localConfig.display, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { displayInput -> + focusManager.clearFocus() + viewModel.updateDisplayConfig { displayInput } + } + ) + } + composable("lora") { + LoRaConfigItemList( + loraConfig = localConfig.lora, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { loraInput -> + focusManager.clearFocus() + viewModel.updateLoraConfig { loraInput } + } + ) + } + composable("bluetooth") { + BluetoothConfigItemList( + bluetoothConfig = localConfig.bluetooth, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { bluetoothInput -> + focusManager.clearFocus() + viewModel.updateBluetoothConfig { bluetoothInput } + } + ) + } + composable("mqtt") { + MQTTConfigItemList( + mqttConfig = moduleConfig.mqtt, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { mqttInput -> + focusManager.clearFocus() + viewModel.updateMQTTConfig { mqttInput } + } + ) + } + composable("serial") { + SerialConfigItemList( + serialConfig = moduleConfig.serial, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { serialInput -> + focusManager.clearFocus() + viewModel.updateSerialConfig { serialInput } + } + ) + } + composable("ext_notification") { + ExternalNotificationConfigItemList( + externalNotificationConfig = moduleConfig.externalNotification, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { externalNotificationInput -> + focusManager.clearFocus() + viewModel.updateExternalNotificationConfig { externalNotificationInput } + } + ) + } + composable("store_forward") { + StoreForwardConfigItemList( + storeForwardConfig = moduleConfig.storeForward, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { storeForwardInput -> + focusManager.clearFocus() + viewModel.updateStoreForwardConfig { storeForwardInput } + } + ) + } + composable("range_test") { + RangeTestConfigItemList( + rangeTestConfig = moduleConfig.rangeTest, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { rangeTestInput -> + focusManager.clearFocus() + viewModel.updateRangeTestConfig { rangeTestInput } + } + ) + } + composable("telemetry") { + TelemetryConfigItemList( + telemetryConfig = moduleConfig.telemetry, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { telemetryInput -> + focusManager.clearFocus() + viewModel.updateTelemetryConfig { telemetryInput } + } + ) + } + composable("canned_message") { + CannedMessageConfigItemList( + cannedMessageConfig = moduleConfig.cannedMessage, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { cannedMessageInput -> + focusManager.clearFocus() + viewModel.updateCannedMessageConfig { cannedMessageInput } + } + ) + } + composable("audio") { + AudioConfigItemList( + audioConfig = moduleConfig.audio, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { audioInput -> + focusManager.clearFocus() + viewModel.updateAudioConfig { audioInput } + } + ) + } + composable("remote_hardware") { + RemoteHardwareConfigItemList( + remoteHardwareConfig = moduleConfig.remoteHardware, + enabled = connected, + focusManager = focusManager, + onSaveClicked = { remoteHardwareInput -> + focusManager.clearFocus() + viewModel.updateRemoteHardwareConfig { remoteHardwareInput } + } + ) + } + } +} + +@Composable +fun NavCard(title: String, onClick: () -> Unit) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 2.dp, horizontal = 16.dp) + .clickable { onClick() }, + elevation = 4.dp + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 16.dp, horizontal = 16.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.body1, + modifier = Modifier.weight(1f) + ) + Icon( + Icons.TwoTone.KeyboardArrowRight, "trailingIcon", + modifier = Modifier.wrapContentSize(), + ) + } + } +} + +@Composable +fun RadioSettingsScreen(navController: NavHostController) { + LazyColumn { + item { + PreferenceCategory( + stringResource(id = R.string.device_settings), Modifier.padding(horizontal = 16.dp) + ) + } + items(ConfigDest.values()) { configs -> + NavCard(configs.title) { navController.navigate(configs.route) } + } + + item { + PreferenceCategory( + stringResource(id = R.string.module_settings), Modifier.padding(horizontal = 16.dp) + ) + } + + items(ModuleDest.values()) { modules -> + NavCard(modules.title) { navController.navigate(modules.route) } + } + } +} + +@Preview(showBackground = true) +@Composable +fun RadioSettingsScreenPreview(){ + RadioSettingsScreen(NavHostController(LocalContext.current)) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsItemList.kt deleted file mode 100644 index 598df5d93..000000000 --- a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsItemList.kt +++ /dev/null @@ -1,910 +0,0 @@ -package com.geeksville.mesh.ui - -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.material.Divider -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -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.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import com.geeksville.mesh.ConfigProtos -import com.geeksville.mesh.ConfigProtos.Config.NetworkConfig -import com.geeksville.mesh.R -import com.geeksville.mesh.copy -import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.model.getInitials -import com.geeksville.mesh.service.MeshService -import com.geeksville.mesh.ui.components.BitwisePreference -import com.geeksville.mesh.ui.components.DropDownPreference -import com.geeksville.mesh.ui.components.EditIPv4Preference -import com.geeksville.mesh.ui.components.EditListPreference -import com.geeksville.mesh.ui.components.EditTextPreference -import com.geeksville.mesh.ui.components.PreferenceCategory -import com.geeksville.mesh.ui.components.PreferenceFooter -import com.geeksville.mesh.ui.components.RegularPreference -import com.geeksville.mesh.ui.components.SwitchPreference - -@Composable -fun DeviceSettingsItemList(viewModel: UIViewModel = viewModel()) { - val focusManager = LocalFocusManager.current - - val hasWifi = viewModel.hasWifi() - val connectionState by viewModel.connectionState.observeAsState() - val connected = connectionState == MeshService.ConnectionState.CONNECTED - - val localConfig by viewModel.localConfig.collectAsStateWithLifecycle() - val ourNodeInfo by viewModel.ourNodeInfo.collectAsStateWithLifecycle() - var userInput by remember(ourNodeInfo?.user) { mutableStateOf(ourNodeInfo?.user) } - var positionInfo by remember(ourNodeInfo?.position) { mutableStateOf(ourNodeInfo?.position) } - - // Temporary [ConfigProtos.Config] state holders - var deviceInput by remember(localConfig.device) { mutableStateOf(localConfig.device) } - var positionInput by remember(localConfig.position) { mutableStateOf(localConfig.position) } - var powerInput by remember(localConfig.power) { mutableStateOf(localConfig.power) } - var networkInput by remember(localConfig.network) { mutableStateOf(localConfig.network) } - var displayInput by remember(localConfig.display) { mutableStateOf(localConfig.display) } - var loraInput by remember(localConfig.lora) { mutableStateOf(localConfig.lora) } - var bluetoothInput by remember(localConfig.bluetooth) { mutableStateOf(localConfig.bluetooth) } - - LazyColumn( - modifier = Modifier.fillMaxSize() - ) { - item { PreferenceCategory(text = "User Config") } - - item { - RegularPreference(title = "Node ID", - subtitle = userInput?.id ?: stringResource(id = R.string.unknown), - onClick = {}) - } - item { Divider() } - - item { - EditTextPreference(title = "Long name", - value = userInput?.longName ?: stringResource(id = R.string.unknown_username), - maxSize = 39, // long_name max_size:40 - enabled = connected && userInput?.longName != null, - isError = userInput?.longName.isNullOrEmpty(), - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { value -> - userInput?.let { userInput = it.copy(longName = value) } - if (getInitials(value).toByteArray().size <= 4) // short_name max_size:5 - userInput?.let { userInput = it.copy(shortName = getInitials(value)) } - }) - } - - item { - EditTextPreference(title = "Short name", - value = userInput?.shortName ?: stringResource(id = R.string.unknown), - maxSize = 4, // short_name max_size:5 - enabled = connected && userInput?.shortName != null, - isError = userInput?.shortName.isNullOrEmpty(), - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { value -> - userInput?.let { userInput = it.copy(shortName = value) } - }) - } - - item { - RegularPreference(title = "Hardware model", - subtitle = userInput?.hwModel?.name ?: stringResource(id = R.string.unknown), - onClick = {}) - } - item { Divider() } - - item { - SwitchPreference(title = "Licensed amateur radio", - checked = userInput?.isLicensed ?: false, - enabled = connected && userInput?.isLicensed != null, - onCheckedChange = { value -> - userInput?.let { userInput = it.copy(isLicensed = value) } - }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = userInput != ourNodeInfo?.user, - onCancelClicked = { - focusManager.clearFocus() - userInput = ourNodeInfo?.user - }, onSaveClicked = { - focusManager.clearFocus() - userInput?.let { viewModel.setOwner(it) } - }) - } - - item { PreferenceCategory(text = "Device Config") } - - item { - DropDownPreference(title = "Role", - enabled = connected, - items = ConfigProtos.Config.DeviceConfig.Role.values() - .filter { it != ConfigProtos.Config.DeviceConfig.Role.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = deviceInput.role, - onItemSelected = { deviceInput = deviceInput.copy { role = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Serial output enabled", - checked = deviceInput.serialEnabled, - enabled = connected, - onCheckedChange = { deviceInput = deviceInput.copy { serialEnabled = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Debug log enabled", - checked = deviceInput.debugLogEnabled, - enabled = connected, - onCheckedChange = { deviceInput = deviceInput.copy { debugLogEnabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Redefine PIN_BUTTON", - value = deviceInput.buttonGpio, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - deviceInput = deviceInput.copy { buttonGpio = it } - }) - } - - item { - EditTextPreference(title = "Redefine PIN_BUZZER", - value = deviceInput.buzzerGpio, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - deviceInput = deviceInput.copy { buzzerGpio = it } - }) - } - - item { - DropDownPreference(title = "Rebroadcast mode", - enabled = connected, - items = ConfigProtos.Config.DeviceConfig.RebroadcastMode.values() - .filter { it != ConfigProtos.Config.DeviceConfig.RebroadcastMode.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = deviceInput.rebroadcastMode, - onItemSelected = { deviceInput = deviceInput.copy { rebroadcastMode = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "NodeInfo broadcast interval", - value = deviceInput.nodeInfoBroadcastSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } - }) - } - - item { - SwitchPreference(title = "Double tap as button press", - checked = deviceInput.doubleTapAsButtonPress, - enabled = connected, - onCheckedChange = { - deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } - }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = deviceInput != localConfig.device, - onCancelClicked = { - focusManager.clearFocus() - deviceInput = localConfig.device - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateDeviceConfig { deviceInput } - }) - } - - item { PreferenceCategory(text = "Position Config") } - - item { - EditTextPreference(title = "Position broadcast interval", - value = positionInput.positionBroadcastSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - positionInput = positionInput.copy { positionBroadcastSecs = it } - }) - } - - item { - SwitchPreference(title = "Smart position enabled", - checked = positionInput.positionBroadcastSmartEnabled, - enabled = connected, - onCheckedChange = { - positionInput = positionInput.copy { positionBroadcastSmartEnabled = it } - }) - } - item { Divider() } - - if (positionInput.positionBroadcastSmartEnabled) { - item { - EditTextPreference(title = "Smart broadcast minimum distance", - value = positionInput.broadcastSmartMinimumDistance, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - positionInput = positionInput.copy { broadcastSmartMinimumDistance = it } - }) - } - - item { - EditTextPreference(title = "Smart broadcast minimum interval", - value = positionInput.broadcastSmartMinimumIntervalSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - positionInput = positionInput.copy { broadcastSmartMinimumIntervalSecs = it } - }) - } - } - - item { - SwitchPreference(title = "Use fixed position", - checked = positionInput.fixedPosition, - enabled = connected, - onCheckedChange = { positionInput = positionInput.copy { fixedPosition = it } }) - } - item { Divider() } - - if (positionInput.fixedPosition) { - item { - EditTextPreference(title = "Latitude", - value = positionInfo?.latitude ?: 0.0, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { value -> - if (value >= -90 && value <= 90.0) - positionInfo?.let { positionInfo = it.copy(latitude = value) } - }) - } - item { - EditTextPreference(title = "Longitude", - value = positionInfo?.longitude ?: 0.0, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { value -> - if (value >= -180 && value <= 180.0) - positionInfo?.let { positionInfo = it.copy(longitude = value) } - }) - } - item { - EditTextPreference(title = "Altitude", - value = positionInfo?.altitude ?: 0, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { value -> - positionInfo?.let { positionInfo = it.copy(altitude = value) } - }) - } - } - - item { - SwitchPreference(title = "GPS enabled", - checked = positionInput.gpsEnabled, - enabled = connected, - onCheckedChange = { positionInput = positionInput.copy { gpsEnabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "GPS update interval", - value = positionInput.gpsUpdateInterval, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { positionInput = positionInput.copy { gpsUpdateInterval = it } }) - } - - item { - EditTextPreference(title = "Fix attempt duration", - value = positionInput.gpsAttemptTime, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { positionInput = positionInput.copy { gpsAttemptTime = it } }) - } - - item { - BitwisePreference(title = "Position flags", - value = positionInput.positionFlags, - enabled = connected, - items = ConfigProtos.Config.PositionConfig.PositionFlags.values() - .filter { it != ConfigProtos.Config.PositionConfig.PositionFlags.UNSET && it != ConfigProtos.Config.PositionConfig.PositionFlags.UNRECOGNIZED } - .map { it.number to it.name }, - onItemSelected = { positionInput = positionInput.copy { positionFlags = it } } - ) - } - item { Divider() } - - item { - EditTextPreference(title = "Redefine GPS_RX_PIN", - value = positionInput.rxGpio, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { positionInput = positionInput.copy { rxGpio = it } }) - } - - item { - EditTextPreference(title = "Redefine GPS_TX_PIN", - value = positionInput.txGpio, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { positionInput = positionInput.copy { txGpio = it } }) - } - - item { - PreferenceFooter( - enabled = positionInput != localConfig.position || positionInfo != ourNodeInfo?.position, - onCancelClicked = { - focusManager.clearFocus() - positionInput = localConfig.position - positionInfo = ourNodeInfo?.position - }, - onSaveClicked = { - focusManager.clearFocus() - if (positionInfo != ourNodeInfo?.position && positionInput.fixedPosition) - positionInfo?.let { viewModel.requestPosition(0, it) } - if (positionInput != localConfig.position) viewModel.updatePositionConfig { positionInput } - }) - } - - item { PreferenceCategory(text = "Power Config") } - - item { - SwitchPreference(title = "Enable power saving mode", - checked = powerInput.isPowerSaving, - enabled = connected && hasWifi, // We consider hasWifi = ESP32 - onCheckedChange = { powerInput = powerInput.copy { isPowerSaving = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Shutdown on battery delay", - value = powerInput.onBatteryShutdownAfterSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } - }) - } - - item { - EditTextPreference(title = "ADC multiplier override ratio", - value = powerInput.adcMultiplierOverride, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } }) - } - - item { - EditTextPreference(title = "Wait for Bluetooth duration", - value = powerInput.waitBluetoothSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { powerInput = powerInput.copy { waitBluetoothSecs = it } }) - } - - item { - EditTextPreference(title = "Mesh SDS timeout", - value = powerInput.meshSdsTimeoutSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { powerInput = powerInput.copy { meshSdsTimeoutSecs = it } }) - } - - item { - EditTextPreference(title = "Super deep sleep duration", - value = powerInput.sdsSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { powerInput = powerInput.copy { sdsSecs = it } }) - } - - item { - EditTextPreference(title = "Light sleep duration", - value = powerInput.lsSecs, - enabled = connected && hasWifi, // we consider hasWifi = ESP32 - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { powerInput = powerInput.copy { lsSecs = it } }) - } - - item { - EditTextPreference(title = "Minimum wake time", - value = powerInput.minWakeSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { powerInput = powerInput.copy { minWakeSecs = it } }) - } - - item { - PreferenceFooter( - enabled = powerInput != localConfig.power, - onCancelClicked = { - focusManager.clearFocus() - powerInput = localConfig.power - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updatePowerConfig { powerInput } - }) - } - - item { PreferenceCategory(text = "Network Config") } - - item { - SwitchPreference(title = "WiFi enabled", - checked = networkInput.wifiEnabled, - enabled = connected && hasWifi, - onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "SSID", - value = networkInput.wifiSsid, - maxSize = 32, // wifi_ssid max_size:33 - enabled = connected && hasWifi, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - networkInput = networkInput.copy { wifiSsid = it } - }) - } - - item { - EditTextPreference(title = "PSK", - value = networkInput.wifiPsk, - maxSize = 63, // wifi_psk max_size:64 - enabled = connected && hasWifi, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Password, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - networkInput = networkInput.copy { wifiPsk = it } - }) - } - - item { - EditTextPreference(title = "NTP server", - value = networkInput.ntpServer, - maxSize = 32, // ntp_server max_size:33 - enabled = connected && hasWifi, - isError = networkInput.ntpServer.isEmpty(), - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - networkInput = networkInput.copy { ntpServer = it } - }) - } - - item { - EditTextPreference(title = "rsyslog server", - value = networkInput.rsyslogServer, - maxSize = 32, // rsyslog_server max_size:33 - enabled = connected && hasWifi, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - networkInput = networkInput.copy { rsyslogServer = it } - }) - } - - item { - SwitchPreference(title = "Ethernet enabled", - checked = networkInput.ethEnabled, - enabled = connected, - onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } }) - } - item { Divider() } - - item { - DropDownPreference(title = "IPv4 mode", - enabled = connected, - items = NetworkConfig.AddressMode.values() - .filter { it != NetworkConfig.AddressMode.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = networkInput.addressMode, - onItemSelected = { networkInput = networkInput.copy { addressMode = it } }) - } - item { Divider() } - - item { PreferenceCategory(text = "IPv4 Config") } - - item { - EditIPv4Preference(title = "IP", - value = networkInput.ipv4Config.ip, - enabled = connected && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - val ipv4 = networkInput.ipv4Config.copy { ip = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - }) - } - - item { - EditIPv4Preference(title = "Gateway", - value = networkInput.ipv4Config.gateway, - enabled = connected && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - val ipv4 = networkInput.ipv4Config.copy { gateway = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - }) - } - - item { - EditIPv4Preference(title = "Subnet", - value = networkInput.ipv4Config.subnet, - enabled = connected && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - val ipv4 = networkInput.ipv4Config.copy { subnet = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - }) - } - - item { - EditIPv4Preference(title = "DNS", - value = networkInput.ipv4Config.dns, - enabled = connected && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - val ipv4 = networkInput.ipv4Config.copy { dns = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - }) - } - - item { - PreferenceFooter( - enabled = networkInput != localConfig.network, - onCancelClicked = { - focusManager.clearFocus() - networkInput = localConfig.network - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateNetworkConfig { networkInput } - }) - } - - item { PreferenceCategory(text = "Display Config") } - - item { - EditTextPreference(title = "Screen timeout", - value = displayInput.screenOnSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } }) - } - - item { - DropDownPreference(title = "GPS coordinates format", - enabled = connected, - items = ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.values() - .filter { it != ConfigProtos.Config.DisplayConfig.GpsCoordinateFormat.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = displayInput.gpsFormat, - onItemSelected = { displayInput = displayInput.copy { gpsFormat = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Auto screen carousel", - value = displayInput.autoScreenCarouselSecs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - displayInput = displayInput.copy { autoScreenCarouselSecs = it } - }) - } - - item { - SwitchPreference(title = "Compass north top", - checked = displayInput.compassNorthTop, - enabled = connected, - onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Flip screen", - checked = displayInput.flipScreen, - enabled = connected, - onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } }) - } - item { Divider() } - - item { - DropDownPreference(title = "Display units", - enabled = connected, - items = ConfigProtos.Config.DisplayConfig.DisplayUnits.values() - .filter { it != ConfigProtos.Config.DisplayConfig.DisplayUnits.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = displayInput.units, - onItemSelected = { displayInput = displayInput.copy { units = it } }) - } - item { Divider() } - - item { - DropDownPreference(title = "Override OLED auto-detect", - enabled = connected, - items = ConfigProtos.Config.DisplayConfig.OledType.values() - .filter { it != ConfigProtos.Config.DisplayConfig.OledType.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = displayInput.oled, - onItemSelected = { displayInput = displayInput.copy { oled = it } }) - } - item { Divider() } - - item { - DropDownPreference(title = "Display mode", - enabled = connected, - items = ConfigProtos.Config.DisplayConfig.DisplayMode.values() - .filter { it != ConfigProtos.Config.DisplayConfig.DisplayMode.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = displayInput.displaymode, - onItemSelected = { displayInput = displayInput.copy { displaymode = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Heading bold", - checked = displayInput.headingBold, - enabled = connected, - onCheckedChange = { displayInput = displayInput.copy { headingBold = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Wake screen on tap or motion", - checked = displayInput.wakeOnTapOrMotion, - enabled = connected, - onCheckedChange = { displayInput = displayInput.copy { wakeOnTapOrMotion = it } }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = displayInput != localConfig.display, - onCancelClicked = { - focusManager.clearFocus() - displayInput = localConfig.display - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateDisplayConfig { displayInput } - }) - } - - item { PreferenceCategory(text = "LoRa Config") } - - item { - SwitchPreference(title = "Use modem preset", - checked = loraInput.usePreset, - enabled = connected, - onCheckedChange = { loraInput = loraInput.copy { usePreset = it } }) - } - item { Divider() } - - if (loraInput.usePreset) { - item { - DropDownPreference(title = "Modem preset", - enabled = connected && loraInput.usePreset, - items = ConfigProtos.Config.LoRaConfig.ModemPreset.values() - .filter { it != ConfigProtos.Config.LoRaConfig.ModemPreset.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = loraInput.modemPreset, - onItemSelected = { loraInput = loraInput.copy { modemPreset = it } }) - } - item { Divider() } - } else { - item { - EditTextPreference(title = "Bandwidth", - value = loraInput.bandwidth, - enabled = connected && !loraInput.usePreset, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { bandwidth = it } }) - } - - item { - EditTextPreference(title = "Spread factor", - value = loraInput.spreadFactor, - enabled = connected && !loraInput.usePreset, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } }) - } - - item { - EditTextPreference(title = "Coding rate", - value = loraInput.codingRate, - enabled = connected && !loraInput.usePreset, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { codingRate = it } }) - } - } - - item { - EditTextPreference(title = "Frequency offset (MHz)", - value = loraInput.frequencyOffset, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } }) - } - - item { - DropDownPreference(title = "Region (frequency plan)", - enabled = connected, - items = ConfigProtos.Config.LoRaConfig.RegionCode.values() - .filter { it != ConfigProtos.Config.LoRaConfig.RegionCode.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = loraInput.region, - onItemSelected = { loraInput = loraInput.copy { region = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Hop limit", - value = loraInput.hopLimit, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { hopLimit = it } }) - } - - item { - SwitchPreference(title = "TX enabled", - checked = loraInput.txEnabled, - enabled = connected, - onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "TX power", - value = loraInput.txPower, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { txPower = it } }) - } - - item { - EditTextPreference(title = "Channel number", - value = loraInput.channelNum, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { channelNum = it } }) - } - - item { - SwitchPreference(title = "Override Duty Cycle", - checked = loraInput.overrideDutyCycle, - enabled = connected, - onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } }) - } - item { Divider() } - - item { - EditListPreference(title = "Ignore incoming", - list = loraInput.ignoreIncomingList, - maxCount = 3, // ignore_incoming max_count:3 - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValuesChanged = { list -> - loraInput = loraInput.copy { - ignoreIncoming.clear() - ignoreIncoming.addAll(list.filter { it != 0 }) - } - }) - } - - item { - SwitchPreference(title = "SX126X RX boosted gain", - checked = loraInput.sx126XRxBoostedGain, - enabled = connected, - onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Override frequency (MHz)", - value = loraInput.overrideFrequency, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } }) - } - - item { - PreferenceFooter( - enabled = loraInput != localConfig.lora, - onCancelClicked = { - focusManager.clearFocus() - loraInput = localConfig.lora - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateLoraConfig { loraInput } - }) - } - - item { PreferenceCategory(text = "Bluetooth Config") } - - item { - SwitchPreference(title = "Bluetooth enabled", - checked = bluetoothInput.enabled, - enabled = connected, - onCheckedChange = { bluetoothInput = bluetoothInput.copy { enabled = it } }) - } - item { Divider() } - - item { - DropDownPreference(title = "Pairing mode", - enabled = connected, - items = ConfigProtos.Config.BluetoothConfig.PairingMode.values() - .filter { it != ConfigProtos.Config.BluetoothConfig.PairingMode.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = bluetoothInput.mode, - onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Fixed PIN", - value = bluetoothInput.fixedPin, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - if (it.toString().length == 6) // ensure 6 digits - bluetoothInput = bluetoothInput.copy { fixedPin = it } - }) - } - - item { - PreferenceFooter( - enabled = bluetoothInput != localConfig.bluetooth, - onCancelClicked = { - focusManager.clearFocus() - bluetoothInput = localConfig.bluetooth - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateBluetoothConfig { bluetoothInput } - }) - } - } -} diff --git a/app/src/main/java/com/geeksville/mesh/ui/ModuleSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ModuleSettingsFragment.kt deleted file mode 100644 index ba25b10ac..000000000 --- a/app/src/main/java/com/geeksville/mesh/ui/ModuleSettingsFragment.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.geeksville.mesh.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.platform.ViewCompositionStrategy -import androidx.core.content.ContextCompat -import androidx.fragment.app.activityViewModels -import com.geeksville.mesh.R -import com.geeksville.mesh.android.Logging -import com.geeksville.mesh.model.UIViewModel -import com.google.accompanist.themeadapter.appcompat.AppCompatTheme -import dagger.hilt.android.AndroidEntryPoint - -@AndroidEntryPoint -class ModuleSettingsFragment : ScreenFragment("Module Settings"), Logging { - - private val model: UIViewModel by activityViewModels() - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - return ComposeView(requireContext()).apply { - setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) - setBackgroundColor(ContextCompat.getColor(context, R.color.colorAdvancedBackground)) - setContent { - AppCompatTheme { - ModuleSettingsItemList(model) - } - } - } - } -} diff --git a/app/src/main/java/com/geeksville/mesh/ui/ModuleSettingsItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/ModuleSettingsItemList.kt deleted file mode 100644 index fe48d68a7..000000000 --- a/app/src/main/java/com/geeksville/mesh/ui/ModuleSettingsItemList.kt +++ /dev/null @@ -1,790 +0,0 @@ -package com.geeksville.mesh.ui - -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.material.Divider -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.text.input.KeyboardType -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel -import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig -import com.geeksville.mesh.copy -import com.geeksville.mesh.model.UIViewModel -import com.geeksville.mesh.service.MeshService -import com.geeksville.mesh.ui.components.DropDownPreference -import com.geeksville.mesh.ui.components.EditTextPreference -import com.geeksville.mesh.ui.components.PreferenceCategory -import com.geeksville.mesh.ui.components.PreferenceFooter -import com.geeksville.mesh.ui.components.SwitchPreference - -@Composable -fun ModuleSettingsItemList(viewModel: UIViewModel = viewModel()) { - val focusManager = LocalFocusManager.current - - val connectionState by viewModel.connectionState.observeAsState() - val connected = connectionState == MeshService.ConnectionState.CONNECTED - - val moduleConfig by viewModel.moduleConfig.collectAsStateWithLifecycle() - - // Temporary [ModuleConfigProtos.ModuleConfig] state holders - var mqttInput by remember(moduleConfig.mqtt) { mutableStateOf(moduleConfig.mqtt) } - var serialInput by remember(moduleConfig.serial) { mutableStateOf(moduleConfig.serial) } - var externalNotificationInput by remember(moduleConfig.externalNotification) { mutableStateOf(moduleConfig.externalNotification) } - var storeForwardInput by remember(moduleConfig.storeForward) { mutableStateOf(moduleConfig.storeForward) } - var rangeTestInput by remember(moduleConfig.rangeTest) { mutableStateOf(moduleConfig.rangeTest) } - var telemetryInput by remember(moduleConfig.telemetry) { mutableStateOf(moduleConfig.telemetry) } - var cannedMessageInput by remember(moduleConfig.cannedMessage) { mutableStateOf(moduleConfig.cannedMessage) } - var audioInput by remember(moduleConfig.audio) { mutableStateOf(moduleConfig.audio) } - var remoteHardwareInput by remember(moduleConfig.remoteHardware) { mutableStateOf(moduleConfig.remoteHardware) } - - LazyColumn( - modifier = Modifier.fillMaxSize() - ) { - item { PreferenceCategory(text = "MQTT Config") } - - item { - SwitchPreference(title = "MQTT enabled", - checked = mqttInput.enabled, - enabled = connected, - onCheckedChange = { mqttInput = mqttInput.copy { enabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Address", - value = mqttInput.address, - maxSize = 63, // address max_size:64 - enabled = connected, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { mqttInput = mqttInput.copy { address = it } }) - } - - item { - EditTextPreference(title = "Username", - value = mqttInput.username, - maxSize = 63, // username max_size:64 - enabled = connected, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { mqttInput = mqttInput.copy { username = it } }) - } - - item { - EditTextPreference(title = "Password", - value = mqttInput.password, - maxSize = 63, // password max_size:64 - enabled = connected, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { mqttInput = mqttInput.copy { password = it } }) - } - - item { - SwitchPreference(title = "Encryption enabled", - checked = mqttInput.encryptionEnabled, - enabled = connected, - onCheckedChange = { mqttInput = mqttInput.copy { encryptionEnabled = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "JSON output enabled", - checked = mqttInput.jsonEnabled, - enabled = connected, - onCheckedChange = { mqttInput = mqttInput.copy { jsonEnabled = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "TLS enabled", - checked = mqttInput.tlsEnabled, - enabled = connected, - onCheckedChange = { mqttInput = mqttInput.copy { tlsEnabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Root topic", - value = mqttInput.root, - maxSize = 15, // root max_size:16 - enabled = connected, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { mqttInput = mqttInput.copy { root = it } }) - } - - item { - PreferenceFooter( - enabled = mqttInput != moduleConfig.mqtt, - onCancelClicked = { - focusManager.clearFocus() - mqttInput = moduleConfig.mqtt - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateMQTTConfig { mqttInput } - }) - } - - item { PreferenceCategory(text = "Serial Config") } - - item { - SwitchPreference(title = "Serial enabled", - checked = serialInput.enabled, - enabled = connected, - onCheckedChange = { serialInput = serialInput.copy { enabled = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Echo enabled", - checked = serialInput.echo, - enabled = connected, - onCheckedChange = { serialInput = serialInput.copy { echo = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "RX", - value = serialInput.rxd, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { serialInput = serialInput.copy { rxd = it } }) - } - - item { - EditTextPreference(title = "TX", - value = serialInput.txd, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { serialInput = serialInput.copy { txd = it } }) - } - - item { - DropDownPreference(title = "Serial baud rate", - enabled = connected, - items = ModuleConfig.SerialConfig.Serial_Baud.values() - .filter { it != ModuleConfig.SerialConfig.Serial_Baud.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = serialInput.baud, - onItemSelected = { serialInput = serialInput.copy { baud = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Timeout", - value = serialInput.timeout, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { serialInput = serialInput.copy { timeout = it } }) - } - - item { - DropDownPreference(title = "Serial mode", - enabled = connected, - items = ModuleConfig.SerialConfig.Serial_Mode.values() - .filter { it != ModuleConfig.SerialConfig.Serial_Mode.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = serialInput.mode, - onItemSelected = { serialInput = serialInput.copy { mode = it } }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = serialInput != moduleConfig.serial, - onCancelClicked = { - focusManager.clearFocus() - serialInput = moduleConfig.serial - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateSerialConfig { serialInput } - }) - } - - item { PreferenceCategory(text = "External Notification Config") } - - item { - SwitchPreference(title = "External notification enabled", - checked = externalNotificationInput.enabled, - enabled = connected, - onCheckedChange = { - externalNotificationInput = externalNotificationInput.copy { enabled = it } - }) - } - item { Divider() } - - item { - EditTextPreference(title = "Output milliseconds", - value = externalNotificationInput.outputMs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - externalNotificationInput = externalNotificationInput.copy { outputMs = it } - }) - } - - item { - EditTextPreference(title = "Output", - value = externalNotificationInput.output, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - externalNotificationInput = externalNotificationInput.copy { output = it } - }) - } - - item { - SwitchPreference(title = "Active", - checked = externalNotificationInput.active, - enabled = connected, - onCheckedChange = { - externalNotificationInput = externalNotificationInput.copy { active = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Alert message", - checked = externalNotificationInput.alertMessage, - enabled = connected, - onCheckedChange = { - externalNotificationInput = externalNotificationInput.copy { alertMessage = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Alert bell", - checked = externalNotificationInput.alertBell, - enabled = connected, - onCheckedChange = { - externalNotificationInput = externalNotificationInput.copy { alertBell = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Use PWM buzzer", - checked = externalNotificationInput.usePwm, - enabled = connected, - onCheckedChange = { - externalNotificationInput = externalNotificationInput.copy { usePwm = it } - }) - } - item { Divider() } - - item { - EditTextPreference(title = "Output vibra", - value = externalNotificationInput.outputVibra, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - externalNotificationInput = externalNotificationInput.copy { outputVibra = it } - }) - } - - item { - EditTextPreference(title = "Output buzzer", - value = externalNotificationInput.outputBuzzer, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it } - }) - } - - item { - SwitchPreference(title = "Alert message vibra", - checked = externalNotificationInput.alertMessageVibra, - enabled = connected, - onCheckedChange = { - externalNotificationInput = - externalNotificationInput.copy { alertMessageVibra = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Alert message buzzer", - checked = externalNotificationInput.alertMessageBuzzer, - enabled = connected, - onCheckedChange = { - externalNotificationInput = - externalNotificationInput.copy { alertMessageBuzzer = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Alert bell vibra", - checked = externalNotificationInput.alertBellVibra, - enabled = connected, - onCheckedChange = { - externalNotificationInput = - externalNotificationInput.copy { alertBellVibra = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Alert bell buzzer", - checked = externalNotificationInput.alertBellBuzzer, - enabled = connected, - onCheckedChange = { - externalNotificationInput = - externalNotificationInput.copy { alertBellBuzzer = it } - }) - } - item { Divider() } - - item { - EditTextPreference(title = "Nag timeout", - value = externalNotificationInput.nagTimeout, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - externalNotificationInput = externalNotificationInput.copy { nagTimeout = it } - }) - } - - item { - PreferenceFooter( - enabled = externalNotificationInput != moduleConfig.externalNotification, - onCancelClicked = { - focusManager.clearFocus() - externalNotificationInput = moduleConfig.externalNotification - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateExternalNotificationConfig { externalNotificationInput } - }) - } - - item { PreferenceCategory(text = "Store & Forward Config") } - - item { - SwitchPreference(title = "Store & Forward enabled", - checked = storeForwardInput.enabled, - enabled = connected, - onCheckedChange = { storeForwardInput = storeForwardInput.copy { enabled = it } }) - } - item { Divider() } - - item { - SwitchPreference(title = "Heartbeat", - checked = storeForwardInput.heartbeat, - enabled = connected, - onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Number of records", - value = storeForwardInput.records, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } }) - } - - item { - EditTextPreference(title = "History return max", - value = storeForwardInput.historyReturnMax, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - storeForwardInput = storeForwardInput.copy { historyReturnMax = it } - }) - } - - item { - EditTextPreference(title = "History return window", - value = storeForwardInput.historyReturnWindow, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - storeForwardInput = storeForwardInput.copy { historyReturnWindow = it } - }) - } - - item { - PreferenceFooter( - enabled = storeForwardInput != moduleConfig.storeForward, - onCancelClicked = { - focusManager.clearFocus() - storeForwardInput = moduleConfig.storeForward - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateStoreForwardConfig { storeForwardInput } - }) - } - - item { PreferenceCategory(text = "Range Test Config") } - - item { - SwitchPreference(title = "Range test enabled", - checked = rangeTestInput.enabled, - enabled = connected, - onCheckedChange = { rangeTestInput = rangeTestInput.copy { enabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "Sender message interval", - value = rangeTestInput.sender, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } }) - } - - item { - SwitchPreference(title = "Save .CSV in storage (ESP32 only)", - checked = rangeTestInput.save, - enabled = connected, - onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = rangeTestInput != moduleConfig.rangeTest, - onCancelClicked = { - focusManager.clearFocus() - rangeTestInput = moduleConfig.rangeTest - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateRangeTestConfig { rangeTestInput } - }) - } - - item { PreferenceCategory(text = "Telemetry Config") } - - item { - EditTextPreference(title = "Device metrics update interval", - value = telemetryInput.deviceUpdateInterval, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - telemetryInput = telemetryInput.copy { deviceUpdateInterval = it } - }) - } - - item { - EditTextPreference(title = "Environment metrics update interval", - value = telemetryInput.environmentUpdateInterval, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - telemetryInput = telemetryInput.copy { environmentUpdateInterval = it } - }) - } - - item { - SwitchPreference(title = "Environment metrics module enabled", - checked = telemetryInput.environmentMeasurementEnabled, - enabled = connected, - onCheckedChange = { - telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Environment metrics on-screen enabled", - checked = telemetryInput.environmentScreenEnabled, - enabled = connected, - onCheckedChange = { - telemetryInput = telemetryInput.copy { environmentScreenEnabled = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Environment metrics use Fahrenheit", - checked = telemetryInput.environmentDisplayFahrenheit, - enabled = connected, - onCheckedChange = { - telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it } - }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = telemetryInput != moduleConfig.telemetry, - onCancelClicked = { - focusManager.clearFocus() - telemetryInput = moduleConfig.telemetry - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateTelemetryConfig { telemetryInput } - }) - } - - item { PreferenceCategory(text = "Canned Message Config") } - - item { - SwitchPreference(title = "Canned message enabled", - checked = cannedMessageInput.enabled, - enabled = connected, - onCheckedChange = { - cannedMessageInput = cannedMessageInput.copy { enabled = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Rotary encoder #1 enabled", - checked = cannedMessageInput.rotary1Enabled, - enabled = connected, - onCheckedChange = { - cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it } - }) - } - item { Divider() } - - item { - EditTextPreference(title = "GPIO pin for rotary encoder A port", - value = cannedMessageInput.inputbrokerPinA, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it } - }) - } - - item { - EditTextPreference(title = "GPIO pin for rotary encoder B port", - value = cannedMessageInput.inputbrokerPinB, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it } - }) - } - - item { - EditTextPreference(title = "GPIO pin for rotary encoder Press port", - value = cannedMessageInput.inputbrokerPinPress, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it } - }) - } - - item { - DropDownPreference(title = "Generate input event on Press", - enabled = connected, - items = ModuleConfig.CannedMessageConfig.InputEventChar.values() - .filter { it != ModuleConfig.CannedMessageConfig.InputEventChar.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = cannedMessageInput.inputbrokerEventPress, - onItemSelected = { - cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it } - }) - } - item { Divider() } - - item { - DropDownPreference(title = "Generate input event on CW", - enabled = connected, - items = ModuleConfig.CannedMessageConfig.InputEventChar.values() - .filter { it != ModuleConfig.CannedMessageConfig.InputEventChar.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = cannedMessageInput.inputbrokerEventCw, - onItemSelected = { - cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it } - }) - } - item { Divider() } - - item { - DropDownPreference(title = "Generate input event on CCW", - enabled = connected, - items = ModuleConfig.CannedMessageConfig.InputEventChar.values() - .filter { it != ModuleConfig.CannedMessageConfig.InputEventChar.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = cannedMessageInput.inputbrokerEventCcw, - onItemSelected = { - cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it } - }) - } - item { Divider() } - - item { - SwitchPreference(title = "Up/Down/Select input enabled", - checked = cannedMessageInput.updown1Enabled, - enabled = connected, - onCheckedChange = { - cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it } - }) - } - item { Divider() } - - item { - EditTextPreference(title = "Allow input source", - value = cannedMessageInput.allowInputSource, - maxSize = 63, // allow_input_source max_size:16 - enabled = connected, - isError = false, - keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Done - ), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { - cannedMessageInput = cannedMessageInput.copy { allowInputSource = it } - }) - } - - item { - SwitchPreference(title = "Send bell", - checked = cannedMessageInput.sendBell, - enabled = connected, - onCheckedChange = { - cannedMessageInput = cannedMessageInput.copy { sendBell = it } - }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = cannedMessageInput != moduleConfig.cannedMessage, - onCancelClicked = { - focusManager.clearFocus() - cannedMessageInput = moduleConfig.cannedMessage - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateCannedMessageConfig { cannedMessageInput } - }) - } - - item { PreferenceCategory(text = "Audio Config") } - - item { - SwitchPreference(title = "CODEC 2 enabled", - checked = audioInput.codec2Enabled, - enabled = connected, - onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "PTT pin", - value = audioInput.pttPin, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { audioInput = audioInput.copy { pttPin = it } }) - } - - item { - DropDownPreference(title = "CODEC2 sample rate", - enabled = connected, - items = ModuleConfig.AudioConfig.Audio_Baud.values() - .filter { it != ModuleConfig.AudioConfig.Audio_Baud.UNRECOGNIZED } - .map { it to it.name }, - selectedItem = audioInput.bitrate, - onItemSelected = { audioInput = audioInput.copy { bitrate = it } }) - } - item { Divider() } - - item { - EditTextPreference(title = "I2S word select", - value = audioInput.i2SWs, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { audioInput = audioInput.copy { i2SWs = it } }) - } - - item { - EditTextPreference(title = "I2S data in", - value = audioInput.i2SSd, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { audioInput = audioInput.copy { i2SSd = it } }) - } - - item { - EditTextPreference(title = "I2S data out", - value = audioInput.i2SDin, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { audioInput = audioInput.copy { i2SDin = it } }) - } - - item { - EditTextPreference(title = "I2S clock", - value = audioInput.i2SSck, - enabled = connected, - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), - onValueChanged = { audioInput = audioInput.copy { i2SSck = it } }) - } - - item { - PreferenceFooter( - enabled = audioInput != moduleConfig.audio, - onCancelClicked = { - focusManager.clearFocus() - audioInput = moduleConfig.audio - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateAudioConfig { audioInput } - }) - } - - item { PreferenceCategory(text = "Remote Hardware Config") } - - item { - SwitchPreference(title = "Remote Hardware enabled", - checked = remoteHardwareInput.enabled, - enabled = connected, - onCheckedChange = { - remoteHardwareInput = remoteHardwareInput.copy { enabled = it } - }) - } - item { Divider() } - - item { - PreferenceFooter( - enabled = remoteHardwareInput != moduleConfig.remoteHardware, - onCancelClicked = { - focusManager.clearFocus() - remoteHardwareInput = moduleConfig.remoteHardware - }, - onSaveClicked = { - focusManager.clearFocus() - viewModel.updateRemoteHardwareConfig { remoteHardwareInput } - }) - } - } -} - diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/PreferenceFooter.kt b/app/src/main/java/com/geeksville/mesh/ui/components/PreferenceFooter.kt index c965d506a..d3a30a6f4 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/PreferenceFooter.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/PreferenceFooter.kt @@ -41,35 +41,41 @@ fun PreferenceFooter( Row( modifier = modifier .fillMaxWidth() - .size(48.dp), + .height(64.dp), verticalAlignment = Alignment.CenterVertically, ) { Button( modifier = modifier .fillMaxWidth() + .height(48.dp) .weight(1f), enabled = enabled, onClick = onNegativeClicked, - colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red) + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.Red.copy(alpha = 0.6f), + disabledContentColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) + ) ) { Text( text = stringResource(id = negativeText), style = MaterialTheme.typography.body1, - color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified, ) } Button( modifier = modifier .fillMaxWidth() + .height(48.dp) .weight(1f), enabled = enabled, onClick = onPositiveClicked, - colors = ButtonDefaults.buttonColors(backgroundColor = Color.Green) + colors = ButtonDefaults.buttonColors( + backgroundColor = Color.Green.copy(alpha = 0.6f), + disabledContentColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) + ) ) { Text( text = stringResource(id = positiveText), style = MaterialTheme.typography.body1, - color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.DarkGray, ) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/TextDividerPreference.kt b/app/src/main/java/com/geeksville/mesh/ui/components/TextDividerPreference.kt new file mode 100644 index 000000000..64f5db65a --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/TextDividerPreference.kt @@ -0,0 +1,73 @@ +package com.geeksville.mesh.ui.components + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material.Card +import androidx.compose.material.ContentAlpha +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun TextDividerPreference( + title: String, + modifier: Modifier = Modifier, + enabled: Boolean = true, + trailingIcon: ImageVector? = null, +) { + TextDividerPreference( + title = AnnotatedString(text = title), + enabled = enabled, + modifier = modifier, + trailingIcon = trailingIcon, + ) +} + +@Composable +fun TextDividerPreference( + title: AnnotatedString, + modifier: Modifier = Modifier, + enabled: Boolean = true, + trailingIcon: ImageVector? = null, +) { + Card( + modifier = modifier.fillMaxWidth(), + backgroundColor = if (isSystemInDarkTheme()) Color.DarkGray else Color.LightGray, + ) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(all = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + style = MaterialTheme.typography.body1, + color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified, + ) + if (trailingIcon != null) Icon( + trailingIcon, "trailingIcon", + modifier = modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.End), + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun TextDividerPreferencePreview() { + TextDividerPreference(title = "Advanced settings") +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/AudioConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/AudioConfigItemList.kt new file mode 100644 index 000000000..2da8b71d3 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/AudioConfigItemList.kt @@ -0,0 +1,119 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.AudioConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun AudioConfigItemList( + audioConfig: AudioConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (AudioConfig) -> Unit, +) { + var audioInput by remember(audioConfig) { mutableStateOf(audioConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Audio Config") } + + item { + SwitchPreference(title = "CODEC 2 enabled", + checked = audioInput.codec2Enabled, + enabled = enabled, + onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "PTT pin", + value = audioInput.pttPin, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { audioInput = audioInput.copy { pttPin = it } }) + } + + item { + DropDownPreference(title = "CODEC2 sample rate", + enabled = enabled, + items = AudioConfig.Audio_Baud.values() + .filter { it != AudioConfig.Audio_Baud.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = audioInput.bitrate, + onItemSelected = { audioInput = audioInput.copy { bitrate = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "I2S word select", + value = audioInput.i2SWs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { audioInput = audioInput.copy { i2SWs = it } }) + } + + item { + EditTextPreference(title = "I2S data in", + value = audioInput.i2SSd, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { audioInput = audioInput.copy { i2SSd = it } }) + } + + item { + EditTextPreference(title = "I2S data out", + value = audioInput.i2SDin, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { audioInput = audioInput.copy { i2SDin = it } }) + } + + item { + EditTextPreference(title = "I2S clock", + value = audioInput.i2SSck, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { audioInput = audioInput.copy { i2SSck = it } }) + } + + item { + PreferenceFooter( + enabled = audioInput != audioConfig, + onCancelClicked = { + focusManager.clearFocus() + audioInput = audioConfig + }, + onSaveClicked = { onSaveClicked(audioInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun AudioConfigPreview(){ + AudioConfigItemList( + audioConfig = AudioConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/BluetoothConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/BluetoothConfigItemList.kt new file mode 100644 index 000000000..57f0a2352 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/BluetoothConfigItemList.kt @@ -0,0 +1,90 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos.Config.BluetoothConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun BluetoothConfigItemList( + bluetoothConfig: BluetoothConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (BluetoothConfig) -> Unit, +) { + var bluetoothInput by remember(bluetoothConfig) { mutableStateOf(bluetoothConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Bluetooth Config") } + + item { + SwitchPreference(title = "Bluetooth enabled", + checked = bluetoothInput.enabled, + enabled = enabled, + onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } }) + } + item { Divider() } + + item { + DropDownPreference(title = "Pairing mode", + enabled = enabled, + items = BluetoothConfig.PairingMode.values() + .filter { it != BluetoothConfig.PairingMode.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = bluetoothInput.mode, + onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Fixed PIN", + value = bluetoothInput.fixedPin, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + if (it.toString().length == 6) // ensure 6 digits + bluetoothInput = bluetoothInput.copy { fixedPin = it } + }) + } + + item { + PreferenceFooter( + enabled = bluetoothInput != bluetoothConfig, + onCancelClicked = { + focusManager.clearFocus() + bluetoothInput = bluetoothConfig + }, + onSaveClicked = { onSaveClicked(bluetoothInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun BluetoothConfigPreview(){ + BluetoothConfigItemList( + bluetoothConfig = BluetoothConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/CannedMessageConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/CannedMessageConfigItemList.kt new file mode 100644 index 000000000..ae22b8451 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/CannedMessageConfigItemList.kt @@ -0,0 +1,187 @@ +package com.geeksville.mesh.ui.components.config + +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.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.CannedMessageConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun CannedMessageConfigItemList( + cannedMessageConfig: CannedMessageConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (CannedMessageConfig) -> Unit, +) { + var cannedMessageInput by remember(cannedMessageConfig) { mutableStateOf(cannedMessageConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Canned Message Config") } + + item { + SwitchPreference(title = "Canned message enabled", + checked = cannedMessageInput.enabled, + enabled = enabled, + onCheckedChange = { + cannedMessageInput = cannedMessageInput.copy { this.enabled = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Rotary encoder #1 enabled", + checked = cannedMessageInput.rotary1Enabled, + enabled = enabled, + onCheckedChange = { + cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it } + }) + } + item { Divider() } + + item { + EditTextPreference(title = "GPIO pin for rotary encoder A port", + value = cannedMessageInput.inputbrokerPinA, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it } + }) + } + + item { + EditTextPreference(title = "GPIO pin for rotary encoder B port", + value = cannedMessageInput.inputbrokerPinB, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it } + }) + } + + item { + EditTextPreference(title = "GPIO pin for rotary encoder Press port", + value = cannedMessageInput.inputbrokerPinPress, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it } + }) + } + + item { + DropDownPreference(title = "Generate input event on Press", + enabled = enabled, + items = CannedMessageConfig.InputEventChar.values() + .filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = cannedMessageInput.inputbrokerEventPress, + onItemSelected = { + cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it } + }) + } + item { Divider() } + + item { + DropDownPreference(title = "Generate input event on CW", + enabled = enabled, + items = CannedMessageConfig.InputEventChar.values() + .filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = cannedMessageInput.inputbrokerEventCw, + onItemSelected = { + cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it } + }) + } + item { Divider() } + + item { + DropDownPreference(title = "Generate input event on CCW", + enabled = enabled, + items = CannedMessageConfig.InputEventChar.values() + .filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = cannedMessageInput.inputbrokerEventCcw, + onItemSelected = { + cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Up/Down/Select input enabled", + checked = cannedMessageInput.updown1Enabled, + enabled = enabled, + onCheckedChange = { + cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it } + }) + } + item { Divider() } + + item { + EditTextPreference(title = "Allow input source", + value = cannedMessageInput.allowInputSource, + maxSize = 63, // allow_input_source max_size:16 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + cannedMessageInput = cannedMessageInput.copy { allowInputSource = it } + }) + } + + item { + SwitchPreference(title = "Send bell", + checked = cannedMessageInput.sendBell, + enabled = enabled, + onCheckedChange = { + cannedMessageInput = cannedMessageInput.copy { sendBell = it } + }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = cannedMessageInput != cannedMessageConfig, + onCancelClicked = { + focusManager.clearFocus() + cannedMessageInput = cannedMessageConfig + }, + onSaveClicked = { onSaveClicked(cannedMessageInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun CannedMessageConfigPreview(){ + CannedMessageConfigItemList( + cannedMessageConfig = CannedMessageConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/DeviceConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/DeviceConfigItemList.kt new file mode 100644 index 000000000..1b9dc1fc6 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/DeviceConfigItemList.kt @@ -0,0 +1,138 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos.Config.DeviceConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun DeviceConfigItemList( + deviceConfig: DeviceConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (DeviceConfig) -> Unit, +) { + var deviceInput by remember(deviceConfig) { mutableStateOf(deviceConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Device Config") } + + item { + DropDownPreference(title = "Role", + enabled = enabled, + items = DeviceConfig.Role.values() + .filter { it != DeviceConfig.Role.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = deviceInput.role, + onItemSelected = { deviceInput = deviceInput.copy { role = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "Serial output enabled", + checked = deviceInput.serialEnabled, + enabled = enabled, + onCheckedChange = { deviceInput = deviceInput.copy { serialEnabled = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "Debug log enabled", + checked = deviceInput.debugLogEnabled, + enabled = enabled, + onCheckedChange = { deviceInput = deviceInput.copy { debugLogEnabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Redefine PIN_BUTTON", + value = deviceInput.buttonGpio, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + deviceInput = deviceInput.copy { buttonGpio = it } + }) + } + + item { + EditTextPreference(title = "Redefine PIN_BUZZER", + value = deviceInput.buzzerGpio, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + deviceInput = deviceInput.copy { buzzerGpio = it } + }) + } + + item { + DropDownPreference(title = "Rebroadcast mode", + enabled = enabled, + items = DeviceConfig.RebroadcastMode.values() + .filter { it != DeviceConfig.RebroadcastMode.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = deviceInput.rebroadcastMode, + onItemSelected = { deviceInput = deviceInput.copy { rebroadcastMode = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "NodeInfo broadcast interval (seconds)", + value = deviceInput.nodeInfoBroadcastSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } + }) + } + + item { + SwitchPreference(title = "Double tap as button press", + checked = deviceInput.doubleTapAsButtonPress, + enabled = enabled, + onCheckedChange = { + deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } + }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = deviceInput != deviceConfig, + onCancelClicked = { + focusManager.clearFocus() + deviceInput = deviceConfig + }, + onSaveClicked = { onSaveClicked(deviceInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun DeviceConfigPreview(){ + DeviceConfigItemList( + deviceConfig = DeviceConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/DisplayConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/DisplayConfigItemList.kt new file mode 100644 index 000000000..b281d0aab --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/DisplayConfigItemList.kt @@ -0,0 +1,155 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun DisplayConfigItemList( + displayConfig: DisplayConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (DisplayConfig) -> Unit, +) { + var displayInput by remember(displayConfig) { mutableStateOf(displayConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Display Config") } + + item { + EditTextPreference(title = "Screen timeout (seconds)", + value = displayInput.screenOnSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } }) + } + + item { + DropDownPreference(title = "GPS coordinates format", + enabled = enabled, + items = DisplayConfig.GpsCoordinateFormat.values() + .filter { it != DisplayConfig.GpsCoordinateFormat.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = displayInput.gpsFormat, + onItemSelected = { displayInput = displayInput.copy { gpsFormat = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Auto screen carousel (seconds)", + value = displayInput.autoScreenCarouselSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + displayInput = displayInput.copy { autoScreenCarouselSecs = it } + }) + } + + item { + SwitchPreference(title = "Compass north top", + checked = displayInput.compassNorthTop, + enabled = enabled, + onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "Flip screen", + checked = displayInput.flipScreen, + enabled = enabled, + onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } }) + } + item { Divider() } + + item { + DropDownPreference(title = "Display units", + enabled = enabled, + items = DisplayConfig.DisplayUnits.values() + .filter { it != DisplayConfig.DisplayUnits.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = displayInput.units, + onItemSelected = { displayInput = displayInput.copy { units = it } }) + } + item { Divider() } + + item { + DropDownPreference(title = "Override OLED auto-detect", + enabled = enabled, + items = DisplayConfig.OledType.values() + .filter { it != DisplayConfig.OledType.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = displayInput.oled, + onItemSelected = { displayInput = displayInput.copy { oled = it } }) + } + item { Divider() } + + item { + DropDownPreference(title = "Display mode", + enabled = enabled, + items = DisplayConfig.DisplayMode.values() + .filter { it != DisplayConfig.DisplayMode.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = displayInput.displaymode, + onItemSelected = { displayInput = displayInput.copy { displaymode = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "Heading bold", + checked = displayInput.headingBold, + enabled = enabled, + onCheckedChange = { displayInput = displayInput.copy { headingBold = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "Wake screen on tap or motion", + checked = displayInput.wakeOnTapOrMotion, + enabled = enabled, + onCheckedChange = { displayInput = displayInput.copy { wakeOnTapOrMotion = it } }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = displayInput != displayConfig, + onCancelClicked = { + focusManager.clearFocus() + displayInput = displayConfig + }, + onSaveClicked = { onSaveClicked(displayInput) } + ) + } + } + +} + +@Preview(showBackground = true) +@Composable +fun DisplayConfigPreview(){ + DisplayConfigItemList( + displayConfig = DisplayConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/ExternalNotificationConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/ExternalNotificationConfigItemList.kt new file mode 100644 index 000000000..24fbe86fd --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/ExternalNotificationConfigItemList.kt @@ -0,0 +1,208 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.ExternalNotificationConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference +import com.geeksville.mesh.ui.components.TextDividerPreference + +@Composable +fun ExternalNotificationConfigItemList( + externalNotificationConfig: ExternalNotificationConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (ExternalNotificationConfig) -> Unit, +) { + var externalNotificationInput by remember(externalNotificationConfig) { + mutableStateOf(externalNotificationConfig) + } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "External Notification Config") } + + item { + SwitchPreference(title = "External notification enabled", + checked = externalNotificationInput.enabled, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = externalNotificationInput.copy { this.enabled = it } + }) + } + + item { TextDividerPreference("Notifications on message receipt", enabled = enabled) } + + item { + SwitchPreference(title = "Alert message LED", + checked = externalNotificationInput.alertMessage, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = externalNotificationInput.copy { alertMessage = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Alert message buzzer", + checked = externalNotificationInput.alertMessageBuzzer, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = + externalNotificationInput.copy { alertMessageBuzzer = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Alert message vibra", + checked = externalNotificationInput.alertMessageVibra, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = + externalNotificationInput.copy { alertMessageVibra = it } + }) + } + + item { TextDividerPreference("Notifications on alert/bell receipt", enabled = enabled) } + + item { + SwitchPreference(title = "Alert bell LED", + checked = externalNotificationInput.alertBell, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = externalNotificationInput.copy { alertBell = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Alert bell buzzer", + checked = externalNotificationInput.alertBellBuzzer, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = + externalNotificationInput.copy { alertBellBuzzer = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Alert bell vibra", + checked = externalNotificationInput.alertBellVibra, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = + externalNotificationInput.copy { alertBellVibra = it } + }) + } + item { Divider() } + + item { + EditTextPreference(title = "Output LED (GPIO)", + value = externalNotificationInput.output, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + externalNotificationInput = externalNotificationInput.copy { output = it } + }) + } + + if (externalNotificationInput.output != 0) item { + SwitchPreference(title = "Output LED active high", + checked = externalNotificationInput.active, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = externalNotificationInput.copy { active = it } + }) + } + item { Divider() } + + item { + EditTextPreference(title = "Output buzzer (GPIO)", + value = externalNotificationInput.outputBuzzer, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it } + }) + } + + if (externalNotificationInput.outputBuzzer != 0) item { + SwitchPreference(title = "Use PWM buzzer", + checked = externalNotificationInput.usePwm, + enabled = enabled, + onCheckedChange = { + externalNotificationInput = externalNotificationInput.copy { usePwm = it } + }) + } + item { Divider() } + + item { + EditTextPreference(title = "Output vibra (GPIO)", + value = externalNotificationInput.outputVibra, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + externalNotificationInput = externalNotificationInput.copy { outputVibra = it } + }) + } + + item { + EditTextPreference(title = "Output duration (milliseconds)", + value = externalNotificationInput.outputMs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + externalNotificationInput = externalNotificationInput.copy { outputMs = it } + }) + } + + item { + EditTextPreference(title = "Nag timeout (seconds)", + value = externalNotificationInput.nagTimeout, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + externalNotificationInput = externalNotificationInput.copy { nagTimeout = it } + }) + } + + item { + PreferenceFooter( + enabled = externalNotificationInput != externalNotificationConfig, + onCancelClicked = { + focusManager.clearFocus() + externalNotificationInput = externalNotificationConfig + }, + onSaveClicked = { onSaveClicked(externalNotificationInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun ExternalNotificationConfigPreview(){ + ExternalNotificationConfigItemList( + externalNotificationConfig = ExternalNotificationConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/LoRaConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/LoRaConfigItemList.kt new file mode 100644 index 000000000..c3a7a834b --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/LoRaConfigItemList.kt @@ -0,0 +1,195 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos.Config.LoRaConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditListPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun LoRaConfigItemList( + loraConfig: LoRaConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (LoRaConfig) -> Unit, +) { + var loraInput by remember(loraConfig) { mutableStateOf(loraConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "LoRa Config") } + + item { + SwitchPreference(title = "Use modem preset", + checked = loraInput.usePreset, + enabled = enabled, + onCheckedChange = { loraInput = loraInput.copy { usePreset = it } }) + } + item { Divider() } + + if (loraInput.usePreset) { + item { + DropDownPreference(title = "Modem preset", + enabled = enabled && loraInput.usePreset, + items = LoRaConfig.ModemPreset.values() + .filter { it != LoRaConfig.ModemPreset.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = loraInput.modemPreset, + onItemSelected = { loraInput = loraInput.copy { modemPreset = it } }) + } + item { Divider() } + } else { + item { + EditTextPreference(title = "Bandwidth", + value = loraInput.bandwidth, + enabled = enabled && !loraInput.usePreset, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { bandwidth = it } }) + } + + item { + EditTextPreference(title = "Spread factor", + value = loraInput.spreadFactor, + enabled = enabled && !loraInput.usePreset, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } }) + } + + item { + EditTextPreference(title = "Coding rate", + value = loraInput.codingRate, + enabled = enabled && !loraInput.usePreset, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { codingRate = it } }) + } + } + + item { + EditTextPreference(title = "Frequency offset (MHz)", + value = loraInput.frequencyOffset, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } }) + } + + item { + DropDownPreference(title = "Region (frequency plan)", + enabled = enabled, + items = LoRaConfig.RegionCode.values() + .filter { it != LoRaConfig.RegionCode.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = loraInput.region, + onItemSelected = { loraInput = loraInput.copy { region = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Hop limit", + value = loraInput.hopLimit, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { hopLimit = it } }) + } + + item { + SwitchPreference(title = "TX enabled", + checked = loraInput.txEnabled, + enabled = enabled, + onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "TX power", + value = loraInput.txPower, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { txPower = it } }) + } + + item { + EditTextPreference(title = "Channel number", + value = loraInput.channelNum, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { channelNum = it } }) + } + + item { + SwitchPreference(title = "Override Duty Cycle", + checked = loraInput.overrideDutyCycle, + enabled = enabled, + onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } }) + } + item { Divider() } + + item { + EditListPreference(title = "Ignore incoming", + list = loraInput.ignoreIncomingList, + maxCount = 3, // ignore_incoming max_count:3 + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValuesChanged = { list -> + loraInput = loraInput.copy { + ignoreIncoming.clear() + ignoreIncoming.addAll(list.filter { it != 0 }) + } + }) + } + + item { + SwitchPreference(title = "SX126X RX boosted gain", + checked = loraInput.sx126XRxBoostedGain, + enabled = enabled, + onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Override frequency (MHz)", + value = loraInput.overrideFrequency, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } }) + } + + item { + PreferenceFooter( + enabled = loraInput != loraConfig, + onCancelClicked = { + focusManager.clearFocus() + loraInput = loraConfig + }, + onSaveClicked = { onSaveClicked(loraInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun LoRaConfigPreview(){ + LoRaConfigItemList( + loraConfig = LoRaConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/MQTTConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/MQTTConfigItemList.kt new file mode 100644 index 000000000..eb20c8003 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/MQTTConfigItemList.kt @@ -0,0 +1,146 @@ +package com.geeksville.mesh.ui.components.config + +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.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.MQTTConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun MQTTConfigItemList( + mqttConfig: MQTTConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (MQTTConfig) -> Unit, +) { + var mqttInput by remember(mqttConfig) { mutableStateOf(mqttConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "MQTT Config") } + + item { + SwitchPreference(title = "MQTT enabled", + checked = mqttInput.enabled, + enabled = enabled, + onCheckedChange = { mqttInput = mqttInput.copy { this.enabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Address", + value = mqttInput.address, + maxSize = 63, // address max_size:64 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { mqttInput = mqttInput.copy { address = it } }) + } + + item { + EditTextPreference(title = "Username", + value = mqttInput.username, + maxSize = 63, // username max_size:64 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { mqttInput = mqttInput.copy { username = it } }) + } + + item { + EditTextPreference(title = "Password", + value = mqttInput.password, + maxSize = 63, // password max_size:64 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { mqttInput = mqttInput.copy { password = it } }) + } + + item { + SwitchPreference(title = "Encryption enabled", + checked = mqttInput.encryptionEnabled, + enabled = enabled, + onCheckedChange = { mqttInput = mqttInput.copy { encryptionEnabled = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "JSON output enabled", + checked = mqttInput.jsonEnabled, + enabled = enabled, + onCheckedChange = { mqttInput = mqttInput.copy { jsonEnabled = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "TLS enabled", + checked = mqttInput.tlsEnabled, + enabled = enabled, + onCheckedChange = { mqttInput = mqttInput.copy { tlsEnabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Root topic", + value = mqttInput.root, + maxSize = 15, // root max_size:16 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { mqttInput = mqttInput.copy { root = it } }) + } + + item { + PreferenceFooter( + enabled = mqttInput != mqttConfig, + onCancelClicked = { + focusManager.clearFocus() + mqttInput = mqttConfig + }, + onSaveClicked = { onSaveClicked(mqttInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun MQTTConfigPreview(){ + MQTTConfigItemList( + mqttConfig = MQTTConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/NetworkConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/NetworkConfigItemList.kt new file mode 100644 index 000000000..3912b4782 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/NetworkConfigItemList.kt @@ -0,0 +1,195 @@ +package com.geeksville.mesh.ui.components.config + +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.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos.Config.NetworkConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditIPv4Preference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun NetworkConfigItemList( + networkConfig: NetworkConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (NetworkConfig) -> Unit, +) { + var networkInput by remember(networkConfig) { mutableStateOf(networkConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Network Config") } + + item { + SwitchPreference(title = "WiFi enabled", + checked = networkInput.wifiEnabled, + enabled = enabled, + onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "SSID", + value = networkInput.wifiSsid, + maxSize = 32, // wifi_ssid max_size:33 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + networkInput = networkInput.copy { wifiSsid = it } + }) + } + + item { + EditTextPreference(title = "PSK", + value = networkInput.wifiPsk, + maxSize = 63, // wifi_psk max_size:64 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Password, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + networkInput = networkInput.copy { wifiPsk = it } + }) + } + + item { + EditTextPreference(title = "NTP server", + value = networkInput.ntpServer, + maxSize = 32, // ntp_server max_size:33 + enabled = enabled, + isError = networkInput.ntpServer.isEmpty(), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + networkInput = networkInput.copy { ntpServer = it } + }) + } + + item { + EditTextPreference(title = "rsyslog server", + value = networkInput.rsyslogServer, + maxSize = 32, // rsyslog_server max_size:33 + enabled = enabled, + isError = false, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + networkInput = networkInput.copy { rsyslogServer = it } + }) + } + + item { + SwitchPreference(title = "Ethernet enabled", + checked = networkInput.ethEnabled, + enabled = enabled, + onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } }) + } + item { Divider() } + + item { + DropDownPreference(title = "IPv4 mode", + enabled = enabled, + items = NetworkConfig.AddressMode.values() + .filter { it != NetworkConfig.AddressMode.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = networkInput.addressMode, + onItemSelected = { networkInput = networkInput.copy { addressMode = it } }) + } + item { Divider() } + + item { + EditIPv4Preference(title = "IP", + value = networkInput.ipv4Config.ip, + enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { ip = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } + }) + } + + item { + EditIPv4Preference(title = "Gateway", + value = networkInput.ipv4Config.gateway, + enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { gateway = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } + }) + } + + item { + EditIPv4Preference(title = "Subnet", + value = networkInput.ipv4Config.subnet, + enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { subnet = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } + }) + } + + item { + EditIPv4Preference(title = "DNS", + value = networkInput.ipv4Config.dns, + enabled = enabled && networkInput.addressMode == NetworkConfig.AddressMode.STATIC, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { dns = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } + }) + } + + item { + PreferenceFooter( + enabled = networkInput != networkConfig, + onCancelClicked = { + focusManager.clearFocus() + networkInput = networkConfig + }, + onSaveClicked = { onSaveClicked(networkInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun NetworkConfigPreview(){ + NetworkConfigItemList( + networkConfig = NetworkConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/PositionConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/PositionConfigItemList.kt new file mode 100644 index 000000000..06f0b275e --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/PositionConfigItemList.kt @@ -0,0 +1,200 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos +import com.geeksville.mesh.ConfigProtos.Config.PositionConfig +import com.geeksville.mesh.Position +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.BitwisePreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun PositionConfigItemList( + positionInfo: Position?, + positionConfig: PositionConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (Pair) -> Unit, +) { + var locationInput by remember(positionInfo) { mutableStateOf(positionInfo) } + var positionInput by remember(positionConfig) { mutableStateOf(positionConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Position Config") } + + item { + EditTextPreference(title = "Position broadcast interval (seconds)", + value = positionInput.positionBroadcastSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + positionInput = positionInput.copy { positionBroadcastSecs = it } + }) + } + + item { + SwitchPreference(title = "Smart position enabled", + checked = positionInput.positionBroadcastSmartEnabled, + enabled = enabled, + onCheckedChange = { + positionInput = positionInput.copy { positionBroadcastSmartEnabled = it } + }) + } + item { Divider() } + + if (positionInput.positionBroadcastSmartEnabled) { + item { + EditTextPreference(title = "Smart broadcast minimum distance (meters)", + value = positionInput.broadcastSmartMinimumDistance, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + positionInput = positionInput.copy { broadcastSmartMinimumDistance = it } + }) + } + + item { + EditTextPreference(title = "Smart broadcast minimum interval (seconds)", + value = positionInput.broadcastSmartMinimumIntervalSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + positionInput = positionInput.copy { broadcastSmartMinimumIntervalSecs = it } + }) + } + } + + item { + SwitchPreference(title = "Use fixed position", + checked = positionInput.fixedPosition, + enabled = enabled, + onCheckedChange = { positionInput = positionInput.copy { fixedPosition = it } }) + } + item { Divider() } + + if (positionInput.fixedPosition) { + item { + EditTextPreference(title = "Latitude", + value = locationInput?.latitude ?: 0.0, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { value -> + if (value >= -90 && value <= 90.0) + locationInput?.let { locationInput = it.copy(latitude = value) } + }) + } + item { + EditTextPreference(title = "Longitude", + value = locationInput?.longitude ?: 0.0, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { value -> + if (value >= -180 && value <= 180.0) + locationInput?.let { locationInput = it.copy(longitude = value) } + }) + } + item { + EditTextPreference(title = "Altitude (meters)", + value = locationInput?.altitude ?: 0, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { value -> + locationInput?.let { locationInput = it.copy(altitude = value) } + }) + } + } + + item { + SwitchPreference(title = "GPS enabled", + checked = positionInput.gpsEnabled, + enabled = enabled, + onCheckedChange = { positionInput = positionInput.copy { gpsEnabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "GPS update interval (seconds)", + value = positionInput.gpsUpdateInterval, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { positionInput = positionInput.copy { gpsUpdateInterval = it } }) + } + + item { + EditTextPreference(title = "Fix attempt duration (seconds)", + value = positionInput.gpsAttemptTime, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { positionInput = positionInput.copy { gpsAttemptTime = it } }) + } + + item { + BitwisePreference(title = "Position flags", + value = positionInput.positionFlags, + enabled = enabled, + items = ConfigProtos.Config.PositionConfig.PositionFlags.values() + .filter { it != PositionConfig.PositionFlags.UNSET && it != PositionConfig.PositionFlags.UNRECOGNIZED } + .map { it.number to it.name }, + onItemSelected = { positionInput = positionInput.copy { positionFlags = it } } + ) + } + item { Divider() } + + item { + EditTextPreference(title = "Redefine GPS_RX_PIN", + value = positionInput.rxGpio, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { positionInput = positionInput.copy { rxGpio = it } }) + } + + item { + EditTextPreference(title = "Redefine GPS_TX_PIN", + value = positionInput.txGpio, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { positionInput = positionInput.copy { txGpio = it } }) + } + + item { + PreferenceFooter( + enabled = positionInput != positionConfig || locationInput != positionInfo, + onCancelClicked = { + focusManager.clearFocus() + locationInput = positionInfo + positionInput = positionConfig + }, + onSaveClicked = { onSaveClicked(Pair(locationInput, positionInput)) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun PositionConfigPreview(){ + PositionConfigItemList( + positionInfo = null, + positionConfig = PositionConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/PowerConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/PowerConfigItemList.kt new file mode 100644 index 000000000..8bdf5c979 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/PowerConfigItemList.kt @@ -0,0 +1,125 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ConfigProtos.Config.PowerConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun PowerConfigItemList( + powerConfig: PowerConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (PowerConfig) -> Unit, +) { + var powerInput by remember(powerConfig) { mutableStateOf(powerConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Power Config") } + + item { + SwitchPreference(title = "Enable power saving mode", + checked = powerInput.isPowerSaving, + enabled = enabled, + onCheckedChange = { powerInput = powerInput.copy { isPowerSaving = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Shutdown on battery delay (seconds)", + value = powerInput.onBatteryShutdownAfterSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } + }) + } + + item { + EditTextPreference(title = "ADC multiplier override ratio", + value = powerInput.adcMultiplierOverride, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } }) + } + + item { + EditTextPreference(title = "Wait for Bluetooth duration (seconds)", + value = powerInput.waitBluetoothSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { waitBluetoothSecs = it } }) + } + + item { + EditTextPreference(title = "Mesh SDS timeout (seconds)", + value = powerInput.meshSdsTimeoutSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { meshSdsTimeoutSecs = it } }) + } + + item { + EditTextPreference(title = "Super deep sleep duration (seconds)", + value = powerInput.sdsSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { sdsSecs = it } }) + } + + item { + EditTextPreference(title = "Light sleep duration (seconds)", + value = powerInput.lsSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { lsSecs = it } }) + } + + item { + EditTextPreference(title = "Minimum wake time (seconds)", + value = powerInput.minWakeSecs, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { minWakeSecs = it } }) + } + + item { + PreferenceFooter( + enabled = powerInput != powerConfig, + onCancelClicked = { + focusManager.clearFocus() + powerInput = powerConfig + }, + onSaveClicked = { onSaveClicked(powerInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun PowerConfigPreview(){ + PowerConfigItemList( + powerConfig = PowerConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/RangeTestConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/RangeTestConfigItemList.kt new file mode 100644 index 000000000..94b9e1d9c --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/RangeTestConfigItemList.kt @@ -0,0 +1,83 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.RangeTestConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun RangeTestConfigItemList( + rangeTestConfig: RangeTestConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (RangeTestConfig) -> Unit, +) { + var rangeTestInput by remember(rangeTestConfig) { mutableStateOf(rangeTestConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Range Test Config") } + + item { + SwitchPreference(title = "Range test enabled", + checked = rangeTestInput.enabled, + enabled = enabled, + onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Sender message interval (seconds)", + value = rangeTestInput.sender, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } }) + } + + item { + SwitchPreference(title = "Save .CSV in storage (ESP32 only)", + checked = rangeTestInput.save, + enabled = enabled, + onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = rangeTestInput != rangeTestConfig, + onCancelClicked = { + focusManager.clearFocus() + rangeTestInput = rangeTestConfig + }, + onSaveClicked = { onSaveClicked(rangeTestInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun RangeTestConfig(){ + RangeTestConfigItemList( + rangeTestConfig = RangeTestConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/RemoteHardwareConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/RemoteHardwareConfigItemList.kt new file mode 100644 index 000000000..9718c0a27 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/RemoteHardwareConfigItemList.kt @@ -0,0 +1,67 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.RemoteHardwareConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun RemoteHardwareConfigItemList( + remoteHardwareConfig: RemoteHardwareConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (RemoteHardwareConfig) -> Unit, +) { + var remoteHardwareInput by remember(remoteHardwareConfig) { mutableStateOf(remoteHardwareConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Remote Hardware Config") } + + item { + SwitchPreference(title = "Remote Hardware enabled", + checked = remoteHardwareInput.enabled, + enabled = enabled, + onCheckedChange = { + remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it } + }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = remoteHardwareInput != remoteHardwareConfig, + onCancelClicked = { + focusManager.clearFocus() + remoteHardwareInput = remoteHardwareConfig + }, + onSaveClicked = { onSaveClicked(remoteHardwareInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun RemoteHardwareConfigPreview(){ + RemoteHardwareConfigItemList( + remoteHardwareConfig = RemoteHardwareConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/SerialConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/SerialConfigItemList.kt new file mode 100644 index 000000000..bd425a3b2 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/SerialConfigItemList.kt @@ -0,0 +1,122 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.SerialConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.DropDownPreference +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun SerialConfigItemList( + serialConfig: SerialConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (SerialConfig) -> Unit, +) { + var serialInput by remember(serialConfig) { mutableStateOf(serialConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Serial Config") } + + item { + SwitchPreference(title = "Serial enabled", + checked = serialInput.enabled, + enabled = enabled, + onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } }) + } + item { Divider() } + + item { + SwitchPreference(title = "Echo enabled", + checked = serialInput.echo, + enabled = enabled, + onCheckedChange = { serialInput = serialInput.copy { echo = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "RX", + value = serialInput.rxd, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { serialInput = serialInput.copy { rxd = it } }) + } + + item { + EditTextPreference(title = "TX", + value = serialInput.txd, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { serialInput = serialInput.copy { txd = it } }) + } + + item { + DropDownPreference(title = "Serial baud rate", + enabled = enabled, + items = SerialConfig.Serial_Baud.values() + .filter { it != SerialConfig.Serial_Baud.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = serialInput.baud, + onItemSelected = { serialInput = serialInput.copy { baud = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Timeout", + value = serialInput.timeout, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { serialInput = serialInput.copy { timeout = it } }) + } + + item { + DropDownPreference(title = "Serial mode", + enabled = enabled, + items = SerialConfig.Serial_Mode.values() + .filter { it != SerialConfig.Serial_Mode.UNRECOGNIZED } + .map { it to it.name }, + selectedItem = serialInput.mode, + onItemSelected = { serialInput = serialInput.copy { mode = it } }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = serialInput != serialConfig, + onCancelClicked = { + focusManager.clearFocus() + serialInput = serialConfig + }, + onSaveClicked = { onSaveClicked(serialInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun SerialConfigPreview(){ + SerialConfigItemList( + serialConfig = SerialConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/StoreForwardConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/StoreForwardConfigItemList.kt new file mode 100644 index 000000000..9db7ec6c4 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/StoreForwardConfigItemList.kt @@ -0,0 +1,105 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.StoreForwardConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun StoreForwardConfigItemList( + storeForwardConfig: StoreForwardConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (StoreForwardConfig) -> Unit, +) { + var storeForwardInput by remember(storeForwardConfig) { mutableStateOf(storeForwardConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Store & Forward Config") } + + item { + SwitchPreference(title = "Store & Forward enabled", + checked = storeForwardInput.enabled, + enabled = enabled, + onCheckedChange = { + storeForwardInput = storeForwardInput.copy { this.enabled = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Heartbeat", + checked = storeForwardInput.heartbeat, + enabled = enabled, + onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } }) + } + item { Divider() } + + item { + EditTextPreference(title = "Number of records", + value = storeForwardInput.records, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } }) + } + + item { + EditTextPreference(title = "History return max", + value = storeForwardInput.historyReturnMax, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + storeForwardInput = storeForwardInput.copy { historyReturnMax = it } + }) + } + + item { + EditTextPreference(title = "History return window", + value = storeForwardInput.historyReturnWindow, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + storeForwardInput = storeForwardInput.copy { historyReturnWindow = it } + }) + } + + item { + PreferenceFooter( + enabled = storeForwardInput != storeForwardConfig, + onCancelClicked = { + focusManager.clearFocus() + storeForwardInput = storeForwardConfig + }, + onSaveClicked = { onSaveClicked(storeForwardInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun StoreForwardConfigPreview(){ + StoreForwardConfigItemList( + storeForwardConfig = StoreForwardConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/TelemetryConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/TelemetryConfigItemList.kt new file mode 100644 index 000000000..24395eea0 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/TelemetryConfigItemList.kt @@ -0,0 +1,109 @@ +package com.geeksville.mesh.ui.components.config + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.ModuleConfigProtos.ModuleConfig.TelemetryConfig +import com.geeksville.mesh.copy +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun TelemetryConfigItemList( + telemetryConfig: TelemetryConfig, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (TelemetryConfig) -> Unit, +) { + var telemetryInput by remember(telemetryConfig) { mutableStateOf(telemetryConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "Telemetry Config") } + + item { + EditTextPreference(title = "Device metrics update interval (seconds)", + value = telemetryInput.deviceUpdateInterval, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + telemetryInput = telemetryInput.copy { deviceUpdateInterval = it } + }) + } + + item { + EditTextPreference(title = "Environment metrics update interval (seconds)", + value = telemetryInput.environmentUpdateInterval, + enabled = enabled, + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + telemetryInput = telemetryInput.copy { environmentUpdateInterval = it } + }) + } + + item { + SwitchPreference(title = "Environment metrics module enabled", + checked = telemetryInput.environmentMeasurementEnabled, + enabled = enabled, + onCheckedChange = { + telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Environment metrics on-screen enabled", + checked = telemetryInput.environmentScreenEnabled, + enabled = enabled, + onCheckedChange = { + telemetryInput = telemetryInput.copy { environmentScreenEnabled = it } + }) + } + item { Divider() } + + item { + SwitchPreference(title = "Environment metrics use Fahrenheit", + checked = telemetryInput.environmentDisplayFahrenheit, + enabled = enabled, + onCheckedChange = { + telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it } + }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = telemetryInput != telemetryConfig, + onCancelClicked = { + focusManager.clearFocus() + telemetryInput = telemetryConfig + }, + onSaveClicked = { onSaveClicked(telemetryInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun TelemetryConfigPreview(){ + TelemetryConfigItemList( + telemetryConfig = TelemetryConfig.getDefaultInstance(), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/UserConfigItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/UserConfigItemList.kt new file mode 100644 index 000000000..cf1f5f2e8 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/UserConfigItemList.kt @@ -0,0 +1,121 @@ +package com.geeksville.mesh.ui.components.config + +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.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.MeshUser +import com.geeksville.mesh.model.getInitials +import com.geeksville.mesh.ui.components.EditTextPreference +import com.geeksville.mesh.ui.components.PreferenceCategory +import com.geeksville.mesh.ui.components.PreferenceFooter +import com.geeksville.mesh.ui.components.RegularPreference +import com.geeksville.mesh.ui.components.SwitchPreference + +@Composable +fun UserConfigItemList( + userConfig: MeshUser, + enabled: Boolean, + focusManager: FocusManager, + onSaveClicked: (MeshUser) -> Unit, +) { + var userInput by remember(userConfig) { mutableStateOf(userConfig) } + + LazyColumn( + modifier = Modifier.fillMaxSize() + ) { + item { PreferenceCategory(text = "User Config") } + + item { + RegularPreference(title = "Node ID", + subtitle = userInput.id, + onClick = {}) + } + item { Divider() } + + item { + EditTextPreference(title = "Long name", + value = userInput.longName, + maxSize = 39, // long_name max_size:40 + enabled = enabled, + isError = userInput.longName.isEmpty(), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + userInput = userInput.copy(longName = it) + if (getInitials(it).toByteArray().size <= 4) // short_name max_size:5 + userInput = userInput.copy(shortName = getInitials(it)) + }) + } + + item { + EditTextPreference(title = "Short name", + value = userInput.shortName, + maxSize = 4, // short_name max_size:5 + enabled = enabled, + isError = userInput.shortName.isEmpty(), + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done + ), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { userInput = userInput.copy(shortName = it) }) + } + + item { + RegularPreference(title = "Hardware model", + subtitle = userInput.hwModel.name, + onClick = {}) + } + item { Divider() } + + item { + SwitchPreference(title = "Licensed amateur radio", + checked = userInput.isLicensed, + enabled = enabled, + onCheckedChange = { userInput = userInput.copy(isLicensed = it) }) + } + item { Divider() } + + item { + PreferenceFooter( + enabled = userInput != userConfig, + onCancelClicked = { + focusManager.clearFocus() + userInput = userConfig + }, onSaveClicked = { onSaveClicked(userInput) } + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun UserConfigPreview(){ + UserConfigItemList( + userConfig = MeshUser( + id = "!a280d9c8", + longName = "Meshtastic d9c8", + shortName = "d9c8", + hwModel = MeshProtos.HardwareModel.RAK4631, + isLicensed = false + ), + enabled = true, + focusManager = LocalFocusManager.current, + onSaveClicked = { }, + ) +} diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index 2a2207230..db6b97c7b 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -17,13 +17,9 @@ android:checked="false" android:title="@string/protocol_stress_test" /> -