diff --git a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt index 6efbe8876..414e0f663 100644 --- a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt +++ b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt @@ -1,6 +1,7 @@ package com.geeksville.mesh.model import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshUser @@ -62,5 +63,14 @@ class NodeDB(private val ui: UIViewModel) { } /// Could be null if we haven't received our node DB yet - val ourNodeInfo get() = nodes.value?.get(myId.value) + val ourNodeInfo = MediatorLiveData() + + init { + ourNodeInfo.addSource(_nodes) { updatedValue -> + ourNodeInfo.value = updatedValue[_myId.value] + } + ourNodeInfo.addSource(_myId) { updatedValue -> + ourNodeInfo.value = _nodes.value?.get(updatedValue) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index b66e96f37..d77f15466 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -56,7 +56,7 @@ import kotlin.math.roundToInt /// 3 or more characters, use the first three characters. If not, just take the first 3 characters of the /// original name. fun getInitials(nameIn: String): String { - val nchars = 3 + val nchars = 4 val minchars = 2 val name = nameIn.trim() val words = name.split(Regex("\\s+")).filter { it.isNotEmpty() } @@ -263,7 +263,7 @@ class UIViewModel @Inject constructor( try { // Pull down our real node ID - This must be done AFTER reading the nodedb because we need the DB to find our nodeinof object nodeDB.setMyId(service.myId) - val ownerName = nodeDB.ourNodeInfo?.user?.longName + val ownerName = nodeDB.ourNodeInfo.value?.user?.longName _ownerName.value = ownerName } catch (ex: Exception) { warn("Ignoring failure to get myId, service is probably just uninited... ${ex.message}") @@ -364,13 +364,10 @@ class UIViewModel @Inject constructor( // clean up all this nasty owner state management FIXME fun setOwner(longName: String? = null, shortName: String? = null, isLicensed: Boolean? = null) { - if (longName != null) { - _ownerName.value = longName - - // note: we allow an empty userstring to be written to prefs - preferences.edit { - putString("owner", longName) - } + longName?.trim()?.let { ownerName -> + // note: we allow an empty user string to be written to prefs + _ownerName.value = ownerName + preferences.edit { putString("owner", ownerName) } } // Note: we are careful to not set a new unique ID @@ -379,7 +376,7 @@ class UIViewModel @Inject constructor( meshService?.setOwner( null, _ownerName.value, - shortName ?: getInitials(_ownerName.value!!), + shortName?.trim() ?: getInitials(_ownerName.value!!), isLicensed ?: false ) // Note: we use ?. here because we might be running in the emulator } catch (ex: RemoteException) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt index 46df16528..a9d33d205 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MapFragment.kt @@ -459,7 +459,7 @@ class MapFragment : ScreenFragment("Map"), Logging, View.OnClickListener { marker = MarkerWithLabel(map, label) marker.title = "${it.longName} ${node.batteryStr}" marker.snippet = model.gpsString(p) - model.nodeDB.ourNodeInfo?.let { our -> + model.nodeDB.ourNodeInfo.value?.let { our -> our.distanceStr(node)?.let { dist -> marker.subDescription = "bearing: ${our.bearing(node)}° distance: $dist" } diff --git a/app/src/main/java/com/geeksville/mesh/ui/PreferenceItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/PreferenceItemList.kt index 7a6104bb9..3f75d2cc7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/PreferenceItemList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/PreferenceItemList.kt @@ -21,7 +21,9 @@ import com.geeksville.mesh.ConfigProtos 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.EditTextPreference import com.geeksville.mesh.ui.components.PreferenceCategory @@ -29,29 +31,26 @@ import com.geeksville.mesh.ui.components.PreferenceFooter import com.geeksville.mesh.ui.components.RegularPreference import com.geeksville.mesh.ui.components.SwitchPreference -private fun Int.uintToString(): String = this.toUInt().toString() -private fun String.stringToIntOrNull(): Int? = this.toUIntOrNull()?.toInt() - @Composable fun PreferenceItemList(viewModel: UIViewModel) { val focusManager = LocalFocusManager.current val hasWifi = viewModel.hasWifi() - val connectionState = viewModel.connectionState.observeAsState() - val connected = connectionState.value == MeshService.ConnectionState.CONNECTED + val connectionState by viewModel.connectionState.observeAsState() + val connected = connectionState == MeshService.ConnectionState.CONNECTED val localConfig by viewModel.localConfig.collectAsState() - val user = viewModel.nodeDB.ourNodeInfo?.user + val ourNodeInfo by viewModel.nodeDB.ourNodeInfo.observeAsState() + var userInput by remember(ourNodeInfo?.user) { mutableStateOf(ourNodeInfo?.user) } // Temporary [ConfigProtos.Config] state holders - var userInput by remember { mutableStateOf(user) } - var deviceInput by remember { mutableStateOf(localConfig.device) } - var positionInput by remember { mutableStateOf(localConfig.position) } - var powerInput by remember { mutableStateOf(localConfig.power) } - var networkInput by remember { mutableStateOf(localConfig.network) } - var displayInput by remember { mutableStateOf(localConfig.display) } - var loraInput by remember { mutableStateOf(localConfig.lora) } - var bluetoothInput by remember { mutableStateOf(localConfig.bluetooth) } + 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() @@ -59,8 +58,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceCategory(text = "User Config") } item { - RegularPreference( - title = "Node ID", + RegularPreference(title = "Node ID", subtitle = userInput?.id ?: stringResource(id = R.string.unknown), onClick = {}) } @@ -70,15 +68,16 @@ fun PreferenceItemList(viewModel: UIViewModel) { EditTextPreference(title = "Long name", value = userInput?.longName ?: stringResource(id = R.string.unknown_username), enabled = connected && userInput?.longName != null, + isError = userInput?.longName.isNullOrEmpty(), keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Send + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done ), - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), onValueChanged = { value -> if (value.toByteArray().size <= 39) // long_name max_size:40 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)) } }) } @@ -86,12 +85,11 @@ fun PreferenceItemList(viewModel: UIViewModel) { EditTextPreference(title = "Short name", value = userInput?.shortName ?: stringResource(id = R.string.unknown), enabled = connected && userInput?.shortName != null, + isError = userInput?.shortName.isNullOrEmpty(), keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Send + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done ), - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), onValueChanged = { value -> if (value.toByteArray().size <= 4) // short_name max_size:5 userInput?.let { userInput = it.copy(shortName = value) } @@ -99,8 +97,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { } item { - RegularPreference( - title = "Hardware model", + RegularPreference(title = "Hardware model", subtitle = userInput?.hwModel?.name ?: stringResource(id = R.string.unknown), onClick = {}) } @@ -118,10 +115,10 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceFooter( - enabled = userInput != user, + enabled = userInput != ourNodeInfo?.user, onCancelClicked = { focusManager.clearFocus() - userInput = user + userInput = ourNodeInfo?.user }, onSaveClicked = { focusManager.clearFocus() userInput?.let { viewModel.setOwner(it.longName, it.shortName, it.isLicensed) } @@ -174,14 +171,11 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { EditTextPreference(title = "Position broadcast interval", - value = positionInput.positionBroadcastSecs.uintToString(), + value = positionInput.positionBroadcastSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { positionInput = positionInput.copy { positionBroadcastSecs = it } } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + positionInput = positionInput.copy { positionBroadcastSecs = it } }) } @@ -213,42 +207,31 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { EditTextPreference(title = "GPS update interval", - value = positionInput.gpsUpdateInterval.uintToString(), + value = positionInput.gpsUpdateInterval, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { positionInput = positionInput.copy { gpsUpdateInterval = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { positionInput = positionInput.copy { gpsUpdateInterval = it } }) } item { EditTextPreference(title = "Fix attempt duration", - value = positionInput.gpsAttemptTime.uintToString(), + value = positionInput.gpsAttemptTime, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { positionInput = positionInput.copy { gpsAttemptTime = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { positionInput = positionInput.copy { gpsAttemptTime = it } }) } item { - EditTextPreference(title = "Position flags", - value = positionInput.positionFlags.uintToString(), + BitwisePreference(title = "Position flags", + value = positionInput.positionFlags, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { positionInput = positionInput.copy { positionFlags = it } } - }) + 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 { PreferenceFooter( @@ -274,101 +257,61 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "Shutdown on battery delay", - value = powerInput.onBatteryShutdownAfterSecs.uintToString(), + EditTextPreference(title = "Shutdown on battery delay", + value = powerInput.onBatteryShutdownAfterSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } }) } item { - EditTextPreference( - title = "ADC multiplier override ratio", - value = powerInput.adcMultiplierOverride.toString(), + EditTextPreference(title = "ADC multiplier override ratio", + value = powerInput.adcMultiplierOverride, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.toFloatOrNull() - ?.let { powerInput = powerInput.copy { adcMultiplierOverride = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } }) } item { - EditTextPreference( - title = "Wait for Bluetooth duration", - value = powerInput.waitBluetoothSecs.uintToString(), + EditTextPreference(title = "Wait for Bluetooth duration", + value = powerInput.waitBluetoothSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { powerInput = powerInput.copy { waitBluetoothSecs = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { waitBluetoothSecs = it } }) } item { - EditTextPreference( - title = "Mesh SDS timeout", - value = powerInput.meshSdsTimeoutSecs.uintToString(), + EditTextPreference(title = "Mesh SDS timeout", + value = powerInput.meshSdsTimeoutSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { powerInput = powerInput.copy { meshSdsTimeoutSecs = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { meshSdsTimeoutSecs = it } }) } item { - EditTextPreference( - title = "Super deep sleep duration", - value = powerInput.sdsSecs.uintToString(), + EditTextPreference(title = "Super deep sleep duration", + value = powerInput.sdsSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { powerInput = powerInput.copy { sdsSecs = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { sdsSecs = it } }) } item { - EditTextPreference( - title = "Light sleep duration", - value = powerInput.lsSecs.uintToString(), + EditTextPreference(title = "Light sleep duration", + value = powerInput.lsSecs, enabled = connected && hasWifi, // we consider hasWifi = ESP32 - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { powerInput = powerInput.copy { lsSecs = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { lsSecs = it } }) } item { - EditTextPreference( - title = "Minimum wake time", - value = powerInput.minWakeSecs.uintToString(), + EditTextPreference(title = "Minimum wake time", + value = powerInput.minWakeSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { powerInput = powerInput.copy { minWakeSecs = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { powerInput = powerInput.copy { minWakeSecs = it } }) } item { @@ -387,8 +330,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceCategory(text = "Network Config") } item { - SwitchPreference( - title = "WiFi enabled", + SwitchPreference(title = "WiFi enabled", checked = networkInput.wifiEnabled, enabled = connected && hasWifi, onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } }) @@ -396,16 +338,14 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "SSID", - value = networkInput.wifiSsid.toString(), + EditTextPreference(title = "SSID", + value = networkInput.wifiSsid, enabled = connected && hasWifi, + isError = false, keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Text, imeAction = ImeAction.Send + keyboardType = KeyboardType.Text, imeAction = ImeAction.Done ), - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), onValueChanged = { value -> if (value.toByteArray().size <= 32) // wifi_ssid max_size:33 networkInput = networkInput.copy { wifiSsid = value } @@ -413,16 +353,14 @@ fun PreferenceItemList(viewModel: UIViewModel) { } item { - EditTextPreference( - title = "PSK", - value = networkInput.wifiPsk .toString(), + EditTextPreference(title = "PSK", + value = networkInput.wifiPsk, enabled = connected && hasWifi, + isError = false, keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Password, imeAction = ImeAction.Send + keyboardType = KeyboardType.Password, imeAction = ImeAction.Done ), - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), onValueChanged = { value -> if (value.toByteArray().size <= 63) // wifi_psk max_size:64 networkInput = networkInput.copy { wifiPsk = value } @@ -430,16 +368,14 @@ fun PreferenceItemList(viewModel: UIViewModel) { } item { - EditTextPreference( - title = "NTP server", - value = networkInput.ntpServer.toString(), + EditTextPreference(title = "NTP server", + value = networkInput.ntpServer, enabled = connected && hasWifi, + isError = networkInput.ntpServer.isEmpty(), keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Uri, imeAction = ImeAction.Send + keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done ), - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), onValueChanged = { value -> if (value.toByteArray().size <= 32) // ntp_server max_size:33 networkInput = networkInput.copy { ntpServer = value } @@ -447,8 +383,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { } item { - SwitchPreference( - title = "Ethernet enabled", + SwitchPreference(title = "Ethernet enabled", checked = networkInput.ethEnabled, enabled = connected, onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } }) @@ -469,66 +404,46 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceCategory(text = "IPv4 Config") } item { - EditTextPreference( - title = "IP", - value = networkInput.ipv4Config.ip.toString(), + EditTextPreference(title = "IP", + value = networkInput.ipv4Config.ip, enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.toIntOrNull()?.let { - val ipv4 = networkInput.ipv4Config.copy { ip = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { ip = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } }) } item { - EditTextPreference( - title = "Gateway", - value = networkInput.ipv4Config.gateway.toString(), + EditTextPreference(title = "Gateway", + value = networkInput.ipv4Config.gateway, enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.toIntOrNull()?.let { - val ipv4 = networkInput.ipv4Config.copy { gateway = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { gateway = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } }) } item { - EditTextPreference( - title = "Subnet", - value = networkInput.ipv4Config.subnet.toString(), + EditTextPreference(title = "Subnet", + value = networkInput.ipv4Config.subnet, enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.toIntOrNull()?.let { - val ipv4 = networkInput.ipv4Config.copy { subnet = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { subnet = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } }) } item { - EditTextPreference( - title = "DNS", - value = networkInput.ipv4Config.dns.toString(), + EditTextPreference(title = "DNS", + value = networkInput.ipv4Config.dns, enabled = connected && networkInput.ethMode == ConfigProtos.Config.NetworkConfig.EthMode.STATIC, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.toIntOrNull()?.let { - val ipv4 = networkInput.ipv4Config.copy { dns = it } - networkInput = networkInput.copy { ipv4Config = ipv4 } - } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + val ipv4 = networkInput.ipv4Config.copy { dns = it } + networkInput = networkInput.copy { ipv4Config = ipv4 } }) } @@ -548,17 +463,11 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceCategory(text = "Display Config") } item { - EditTextPreference( - title = "Screen timeout", - value = displayInput.screenOnSecs.uintToString(), + EditTextPreference(title = "Screen timeout", + value = displayInput.screenOnSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { displayInput = displayInput.copy { screenOnSecs = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } }) } item { @@ -573,22 +482,17 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "Auto screen carousel", - value = displayInput.autoScreenCarouselSecs.uintToString(), + EditTextPreference(title = "Auto screen carousel", + value = displayInput.autoScreenCarouselSecs, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { displayInput = displayInput.copy { autoScreenCarouselSecs = it } } + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { + displayInput = displayInput.copy { autoScreenCarouselSecs = it } }) } item { - SwitchPreference( - title = "Compass north top", + SwitchPreference(title = "Compass north top", checked = displayInput.compassNorthTop, enabled = connected, onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } }) @@ -596,8 +500,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - SwitchPreference( - title = "Flip screen", + SwitchPreference(title = "Flip screen", checked = displayInput.flipScreen, enabled = connected, onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } }) @@ -642,8 +545,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceCategory(text = "LoRa Config") } item { - SwitchPreference( - title = "Use modem preset", + SwitchPreference(title = "Use modem preset", checked = loraInput.usePreset, enabled = connected, onCheckedChange = { loraInput = loraInput.copy { usePreset = it } }) @@ -662,59 +564,35 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "Bandwidth", - value = loraInput.bandwidth.uintToString(), + EditTextPreference(title = "Bandwidth", + value = loraInput.bandwidth, enabled = connected && !loraInput.usePreset, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { loraInput = loraInput.copy { bandwidth = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { bandwidth = it } }) } item { - EditTextPreference( - title = "Spread factor", - value = loraInput.spreadFactor.uintToString(), + EditTextPreference(title = "Spread factor", + value = loraInput.spreadFactor, enabled = connected && !loraInput.usePreset, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { loraInput = loraInput.copy { spreadFactor = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } }) } item { - EditTextPreference( - title = "Coding rate", - value = loraInput.codingRate.uintToString(), + EditTextPreference(title = "Coding rate", + value = loraInput.codingRate, enabled = connected && !loraInput.usePreset, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { loraInput = loraInput.copy { codingRate = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { codingRate = it } }) } item { - EditTextPreference( - title = "Frequency offset", - value = loraInput.frequencyOffset.toString(), + EditTextPreference(title = "Frequency offset", + value = loraInput.frequencyOffset, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.toFloatOrNull() - ?.let { loraInput = loraInput.copy { frequencyOffset = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } }) } item { @@ -729,22 +607,15 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "Hop limit", - value = loraInput.hopLimit.uintToString(), + EditTextPreference(title = "Hop limit", + value = loraInput.hopLimit, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { loraInput = loraInput.copy { hopLimit = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { hopLimit = it } }) } item { - SwitchPreference( - title = "TX enabled", + SwitchPreference(title = "TX enabled", checked = loraInput.txEnabled, enabled = connected, onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } }) @@ -752,31 +623,19 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "TX power", - value = loraInput.txPower.uintToString(), + EditTextPreference(title = "TX power", + value = loraInput.txPower, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { loraInput = loraInput.copy { txPower = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { txPower = it } }) } item { - EditTextPreference( - title = "Channel number", - value = loraInput.channelNum.uintToString(), + EditTextPreference(title = "Channel number", + value = loraInput.channelNum, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { loraInput = loraInput.copy { channelNum = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { loraInput = loraInput.copy { channelNum = it } }) } item { @@ -795,8 +654,7 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { PreferenceCategory(text = "Bluetooth Config") } item { - SwitchPreference( - title = "Bluetooth enabled", + SwitchPreference(title = "Bluetooth enabled", checked = bluetoothInput.enabled, enabled = connected, onCheckedChange = { bluetoothInput = bluetoothInput.copy { enabled = it } }) @@ -815,17 +673,11 @@ fun PreferenceItemList(viewModel: UIViewModel) { item { Divider() } item { - EditTextPreference( - title = "Fixed PIN", - value = bluetoothInput.fixedPin.uintToString(), + EditTextPreference(title = "Fixed PIN", + value = bluetoothInput.fixedPin, enabled = connected, - keyboardActions = KeyboardActions(onSend = { - focusManager.clearFocus() - }), - onValueChanged = { value -> - value.stringToIntOrNull() - ?.let { bluetoothInput = bluetoothInput.copy { fixedPin = it } } - }) + keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + onValueChanged = { bluetoothInput = bluetoothInput.copy { fixedPin = it } }) } item { diff --git a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt index d03cc0a93..12e7c2ef0 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/SettingsFragment.kt @@ -334,6 +334,7 @@ class SettingsFragment : ScreenFragment("Settings"), Logging { // show the spinner when [spinner] is true scanModel.spinner.observe(viewLifecycleOwner) { show -> + binding.changeRadioButton.isEnabled = !show binding.scanProgressBar.visibility = if (show) View.VISIBLE else View.GONE } diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index e728a9eee..9b16c73cd 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -221,7 +221,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { holder.coordsView.visibility = View.INVISIBLE } - val ourNodeInfo = model.nodeDB.ourNodeInfo + val ourNodeInfo = model.nodeDB.ourNodeInfo.value val distance = ourNodeInfo?.distanceStr(n) if (distance != null) { holder.distanceView.text = distance diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/BitwisePreference.kt b/app/src/main/java/com/geeksville/mesh/ui/components/BitwisePreference.kt new file mode 100644 index 000000000..a3aa3bfdd --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/components/BitwisePreference.kt @@ -0,0 +1,128 @@ +package com.geeksville.mesh.ui.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Checkbox +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.twotone.KeyboardArrowDown +import androidx.compose.material.icons.twotone.KeyboardArrowUp +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.geeksville.mesh.R + +@Composable +fun BitwisePreference( + title: String, + value: Int, + enabled: Boolean, + items: List>, + onItemSelected: (Int) -> Unit, + modifier: Modifier = Modifier, +) { + var dropDownExpanded by remember { mutableStateOf(value = false) } + + RegularPreference( + title = title, + subtitle = value.toString(), + onClick = { dropDownExpanded = !dropDownExpanded }, + enabled = enabled, + trailingIcon = if (dropDownExpanded) Icons.TwoTone.KeyboardArrowDown + else Icons.TwoTone.KeyboardArrowUp, + ) + + Box { + DropdownMenu( + expanded = dropDownExpanded, + onDismissRequest = { dropDownExpanded = !dropDownExpanded }, + ) { + items.forEach { item -> + DropdownMenuItem( + onClick = { onItemSelected(value xor item.first) }, + modifier = Modifier.fillMaxWidth(), + content = { + Text( + text = item.second, + overflow = TextOverflow.Ellipsis, + ) + Checkbox( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.End), + checked = value and item.first != 0, + onCheckedChange = { onItemSelected(value xor item.first) }, + enabled = enabled, + ) + } + ) + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp, end = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.End + ) { + Button( + modifier = modifier + .fillMaxWidth() + .weight(1f), + enabled = enabled, + onClick = { onItemSelected(0) }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color.Red) + ) { + Text( + text = stringResource(id = R.string.clear_last_messages), + style = MaterialTheme.typography.body1, + color = Color.Unspecified, + ) + } + Button( + modifier = modifier + .fillMaxWidth() + .weight(1f), + enabled = enabled, + onClick = { dropDownExpanded = false }, + colors = ButtonDefaults.buttonColors(backgroundColor = Color.Green) + ) { + Text( + text = stringResource(id = R.string.close), + style = MaterialTheme.typography.body1, + color = Color.DarkGray, + ) + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun BitwisePreferencePreview() { + BitwisePreference( + title = "Settings", + value = 3, + enabled = true, + items = listOf(1 to "TEST1", 2 to "TEST2"), + onItemSelected = {} + ) +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/DropDownPreference.kt b/app/src/main/java/com/geeksville/mesh/ui/components/DropDownPreference.kt index 8780023bc..517f507df 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/DropDownPreference.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/DropDownPreference.kt @@ -6,6 +6,9 @@ import androidx.compose.material.DropdownMenu import androidx.compose.material.DropdownMenuItem import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.twotone.KeyboardArrowDown +import androidx.compose.material.icons.twotone.KeyboardArrowUp import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -33,15 +36,10 @@ fun DropDownPreference( onClick = { dropDownExpanded = true }, - modifier = modifier - .background( - color = if (dropDownExpanded) - MaterialTheme.colors.primary.copy(alpha = 0.2f) - else - Color.Unspecified - ), enabled = enabled, - ) + trailingIcon = if (dropDownExpanded) Icons.TwoTone.KeyboardArrowDown + else Icons.TwoTone.KeyboardArrowUp, + ) Box { DropdownMenu( diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/EditTextPreference.kt b/app/src/main/java/com/geeksville/mesh/ui/components/EditTextPreference.kt index f669f73ae..248bf679f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/EditTextPreference.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/EditTextPreference.kt @@ -3,32 +3,111 @@ package com.geeksville.mesh.ui.components import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Icon +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextField +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.twotone.Info 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.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview -@Composable // Default keyboardOptions: KeyboardType.Number, ImeAction.Send +@Composable fun EditTextPreference( title: String, - value: String, + value: Int, enabled: Boolean, keyboardActions: KeyboardActions, - onValueChanged: (String) -> Unit, + onValueChanged: (Int) -> Unit, modifier: Modifier = Modifier, ) { + var valueState by remember(value) { mutableStateOf(value.toUInt().toString()) } + EditTextPreference( title = title, - value = value, + value = valueState, enabled = enabled, + isError = value.toUInt().toString() != valueState, keyboardOptions = KeyboardOptions.Default.copy( - keyboardType = KeyboardType.Number, imeAction = ImeAction.Send + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done ), keyboardActions = keyboardActions, - onValueChanged = onValueChanged, + onValueChanged = { + if (it.isEmpty()) valueState = it + else it.toUIntOrNull()?.toInt()?.let { int -> + valueState = it + onValueChanged(int) + } + }, + modifier = modifier + ) +} + +@Composable +fun EditTextPreference( + title: String, + value: Float, + enabled: Boolean, + keyboardActions: KeyboardActions, + onValueChanged: (Float) -> Unit, + modifier: Modifier = Modifier, +) { + var valueState by remember(value) { mutableStateOf(value.toString()) } + + EditTextPreference( + title = title, + value = valueState, + enabled = enabled, + isError = value.toString() != valueState, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), + keyboardActions = keyboardActions, + onValueChanged = { + if (it.isEmpty()) valueState = it + else it.toFloatOrNull()?.let { float -> + valueState = it + onValueChanged(float) + } + }, + modifier = modifier + ) +} + +@Composable +fun EditTextPreference( + title: String, + value: Double, + enabled: Boolean, + keyboardActions: KeyboardActions, + onValueChanged: (Double) -> Unit, + modifier: Modifier = Modifier, +) { + var valueState by remember(value) { mutableStateOf(value.toString()) } + + EditTextPreference( + title = title, + value = valueState, + enabled = enabled, + isError = value.toString() != valueState, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, imeAction = ImeAction.Done + ), + keyboardActions = keyboardActions, + onValueChanged = { + if (it.isEmpty()) valueState = it + else it.toDoubleOrNull()?.let { double -> + valueState = it + onValueChanged(double) + } + }, modifier = modifier ) } @@ -38,6 +117,7 @@ fun EditTextPreference( title: String, value: String, enabled: Boolean, + isError: Boolean, keyboardOptions: KeyboardOptions, keyboardActions: KeyboardActions, onValueChanged: (String) -> Unit, @@ -48,10 +128,14 @@ fun EditTextPreference( singleLine = true, modifier = modifier.fillMaxWidth(), enabled = enabled, + isError = isError, onValueChange = onValueChanged, label = { Text(title) }, keyboardOptions = keyboardOptions, keyboardActions = keyboardActions, + trailingIcon = { + if (isError) Icon(Icons.TwoTone.Info, "Error", tint = MaterialTheme.colors.error) + } ) } @@ -60,7 +144,7 @@ fun EditTextPreference( private fun EditTextPreferencePreview() { EditTextPreference( title = "Advanced Settings", - value = "${UInt.MAX_VALUE}", + value = UInt.MAX_VALUE.toInt(), enabled = true, keyboardActions = KeyboardActions {}, onValueChanged = {} diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/RegularPreference.kt b/app/src/main/java/com/geeksville/mesh/ui/components/RegularPreference.kt index 48abbc34c..e0638cd0f 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/RegularPreference.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/RegularPreference.kt @@ -2,14 +2,19 @@ package com.geeksville.mesh.ui.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column +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.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 @@ -21,13 +26,15 @@ fun RegularPreference( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + trailingIcon: ImageVector? = null, ) { RegularPreference( title = title, subtitle = AnnotatedString(text = subtitle), onClick = onClick, modifier = modifier, - enabled = enabled + enabled = enabled, + trailingIcon = trailingIcon, ) } @@ -38,8 +45,9 @@ fun RegularPreference( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, + trailingIcon: ImageVector? = null, ) { - Column( + Row( modifier = modifier .fillMaxWidth() .clickable( @@ -47,17 +55,28 @@ fun RegularPreference( onClick = onClick, ) .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, - ) + Column { + Text( + text = title, + style = MaterialTheme.typography.body1, + color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified, + ) - Text( - text = subtitle, - style = MaterialTheme.typography.body2, - color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium), + Text( + text = subtitle, + style = MaterialTheme.typography.body2, + color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else MaterialTheme.colors.onSurface.copy( + alpha = ContentAlpha.medium + ), + ) + } + if (trailingIcon != null) Icon( + trailingIcon, "trailingIcon", + modifier = modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.End) ) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c7ad5bfe5..c31993196 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -162,4 +162,5 @@ Tile download estimate: Start Download Request position + Close