mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(settings): align config screens copy and order with iOS (#3144)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
8fb41aab74
commit
00ee0db78a
17 changed files with 899 additions and 724 deletions
|
|
@ -219,6 +219,7 @@ private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController
|
|||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
|
||||
else -> Unit // Should not happen if ConfigRoute enum is exhaustive for this context
|
||||
}
|
||||
}
|
||||
|
|
@ -374,7 +375,7 @@ enum class ConfigRoute(
|
|||
;
|
||||
|
||||
companion object {
|
||||
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
|
||||
private fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
|
||||
when {
|
||||
metadata == null -> true // Include all routes if metadata is null
|
||||
it == BLUETOOTH -> metadata.hasBluetooth
|
||||
|
|
@ -382,6 +383,11 @@ enum class ConfigRoute(
|
|||
else -> true // Include all other routes by default
|
||||
}
|
||||
}
|
||||
|
||||
val radioConfigRoutes = listOf(LORA, CHANNELS, SECURITY)
|
||||
|
||||
fun deviceConfigRoutes(metadata: DeviceMetadata?): List<ConfigRoute> =
|
||||
filterExcludedFrom(metadata) - radioConfigRoutes
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,15 +17,16 @@
|
|||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.twotone.KeyboardArrowUp
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -36,8 +37,10 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BitwisePreference(
|
||||
title: String,
|
||||
|
|
@ -46,44 +49,43 @@ fun BitwisePreference(
|
|||
items: List<Pair<Int, String>>,
|
||||
onItemSelected: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
var dropDownExpanded by remember { mutableStateOf(value = false) }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
RegularPreference(
|
||||
title = title,
|
||||
subtitle = value.toString(),
|
||||
onClick = { dropDownExpanded = !dropDownExpanded },
|
||||
enabled = enabled,
|
||||
trailingIcon = if (dropDownExpanded) {
|
||||
Icons.TwoTone.KeyboardArrowUp
|
||||
} else {
|
||||
Icons.TwoTone.KeyboardArrowDown
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
if (enabled) {
|
||||
expanded = !expanded
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
Box {
|
||||
DropdownMenu(
|
||||
expanded = dropDownExpanded,
|
||||
onDismissRequest = { dropDownExpanded = !dropDownExpanded },
|
||||
) {
|
||||
modifier = modifier.padding(vertical = 8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled),
|
||||
readOnly = true,
|
||||
value = value.toString(),
|
||||
onValueChange = {},
|
||||
label = { Text(title) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
enabled = enabled,
|
||||
supportingText = { if (summary != null) Text(text = summary) },
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
items.forEach { item ->
|
||||
DropdownMenuItem(
|
||||
onClick = { onItemSelected(value xor item.first) },
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
text = {
|
||||
Text(
|
||||
text = item.second,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(text = item.second, overflow = TextOverflow.Ellipsis)
|
||||
Checkbox(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentWidth(Alignment.End),
|
||||
modifier = Modifier.fillMaxWidth().wrapContentWidth(Alignment.End),
|
||||
checked = value and item.first != 0,
|
||||
onCheckedChange = { onItemSelected(value xor item.first) },
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = { onItemSelected(value xor item.first) },
|
||||
)
|
||||
}
|
||||
PreferenceFooter(
|
||||
|
|
@ -91,7 +93,7 @@ fun BitwisePreference(
|
|||
negativeText = R.string.clear,
|
||||
onNegativeClicked = { onItemSelected(0) },
|
||||
positiveText = R.string.close,
|
||||
onPositiveClicked = { dropDownExpanded = false },
|
||||
onPositiveClicked = { expanded = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -103,8 +105,9 @@ private fun BitwisePreferencePreview() {
|
|||
BitwisePreference(
|
||||
title = "Settings",
|
||||
value = 3,
|
||||
summary = "This is a summary",
|
||||
enabled = true,
|
||||
items = listOf(1 to "TEST1", 2 to "TEST2"),
|
||||
onItemSelected = {}
|
||||
onItemSelected = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,14 @@
|
|||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.twotone.KeyboardArrowUp
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -31,11 +32,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
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 com.geeksville.mesh.R
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.protobuf.ProtocolMessageEnum
|
||||
|
||||
@Composable
|
||||
|
|
@ -50,8 +48,9 @@ fun <T : Enum<T>> DropDownPreference(
|
|||
DropDownPreference(
|
||||
title = title,
|
||||
enabled = enabled,
|
||||
items = selectedItem.declaringJavaClass.enumConstants
|
||||
?.filter { it.name != "UNRECOGNIZED" }?.map { it to it.name } ?: emptyList(),
|
||||
items =
|
||||
selectedItem.declaringJavaClass.enumConstants?.filter { it.name != "UNRECOGNIZED" }?.map { it to it.name }
|
||||
?: emptyList(),
|
||||
selectedItem = selectedItem,
|
||||
onItemSelected = onItemSelected,
|
||||
modifier = modifier,
|
||||
|
|
@ -59,6 +58,7 @@ fun <T : Enum<T>> DropDownPreference(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun <T> DropDownPreference(
|
||||
title: String,
|
||||
|
|
@ -69,7 +69,7 @@ fun <T> DropDownPreference(
|
|||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
var dropDownExpanded by remember { mutableStateOf(value = false) }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
val deprecatedItems: List<T> = remember {
|
||||
if (selectedItem is ProtocolMessageEnum) {
|
||||
|
|
@ -77,58 +77,46 @@ fun <T> DropDownPreference(
|
|||
val descriptor = (selectedItem as ProtocolMessageEnum).descriptorForType
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
enum?.filter { entries ->
|
||||
descriptor.values.any { it.name == entries.name && it.options.deprecated }
|
||||
} as? List<T> ?: emptyList() // Safe cast to List<T> or return emptyList if cast fails
|
||||
enum?.filter { entries -> descriptor.values.any { it.name == entries.name && it.options.deprecated } }
|
||||
as? List<T> ?: emptyList() // Safe cast to List<T> or return emptyList if cast fails
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
RegularPreference(
|
||||
title = title,
|
||||
subtitle = items.find { it.first == selectedItem }?.second
|
||||
?: stringResource(id = R.string.unrecognized),
|
||||
onClick = {
|
||||
dropDownExpanded = true
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
if (enabled) {
|
||||
expanded = !expanded
|
||||
}
|
||||
},
|
||||
enabled = enabled,
|
||||
trailingIcon = if (dropDownExpanded) {
|
||||
Icons.TwoTone.KeyboardArrowUp
|
||||
} else {
|
||||
Icons.TwoTone.KeyboardArrowDown
|
||||
},
|
||||
summary = summary,
|
||||
dropdownMenu = {
|
||||
DropdownMenu(
|
||||
expanded = dropDownExpanded,
|
||||
onDismissRequest = { dropDownExpanded = !dropDownExpanded },
|
||||
) {
|
||||
items.filterNot { it.first in deprecatedItems }.forEach { item ->
|
||||
modifier = modifier.padding(vertical = 8.dp),
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled),
|
||||
readOnly = true,
|
||||
value = items.firstOrNull { it.first == selectedItem }?.second ?: "",
|
||||
onValueChange = {},
|
||||
label = { Text(title) },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
enabled = enabled,
|
||||
supportingText = { if (summary != null) Text(text = summary) },
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
items
|
||||
.filterNot { it.first in deprecatedItems }
|
||||
.forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(selectionOption.second) },
|
||||
onClick = {
|
||||
dropDownExpanded = false
|
||||
onItemSelected(item.first)
|
||||
onItemSelected(selectionOption.first)
|
||||
expanded = false
|
||||
},
|
||||
modifier = modifier
|
||||
.background(
|
||||
color = if (selectedItem == item.first) {
|
||||
MaterialTheme.colorScheme.primary.copy(alpha = 0.3f)
|
||||
} else {
|
||||
Color.Unspecified
|
||||
},
|
||||
),
|
||||
text = {
|
||||
Text(
|
||||
text = item.second,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
|
@ -140,6 +128,6 @@ private fun DropDownPreferencePreview() {
|
|||
enabled = true,
|
||||
items = listOf("TEST1" to "text1", "TEST2" to "text2"),
|
||||
selectedItem = "TEST2",
|
||||
onItemSelected = {}
|
||||
onItemSelected = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
|
|
@ -54,6 +55,7 @@ import org.meshtastic.core.model.Channel
|
|||
fun EditBase64Preference(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
summary: String? = null,
|
||||
value: ByteString,
|
||||
enabled: Boolean,
|
||||
readOnly: Boolean = false,
|
||||
|
|
@ -79,50 +81,59 @@ fun EditBase64Preference(
|
|||
onGenerateKey != null && !isFocused -> Icons.TwoTone.Refresh to stringResource(R.string.reset)
|
||||
else -> null to null
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
value = valueState,
|
||||
onValueChange = {
|
||||
valueState = it
|
||||
runCatching { it.toByteString() }.onSuccess(onValueChange)
|
||||
},
|
||||
modifier = modifier.fillMaxWidth().onFocusChanged { focusState -> isFocused = focusState.isFocused },
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
label = { Text(text = title) },
|
||||
isError = isError,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
trailingIcon = {
|
||||
if (icon != null) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (isError) {
|
||||
valueState = value.encodeToString()
|
||||
onValueChange(value)
|
||||
} else if (onGenerateKey != null && !isFocused) {
|
||||
onGenerateKey()
|
||||
}
|
||||
},
|
||||
enabled = enabled,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = description,
|
||||
tint =
|
||||
if (isError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
LocalContentColor.current
|
||||
Column(modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = valueState,
|
||||
onValueChange = {
|
||||
valueState = it
|
||||
runCatching { it.toByteString() }.onSuccess(onValueChange)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().onFocusChanged { focusState -> isFocused = focusState.isFocused },
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
label = { Text(text = title) },
|
||||
isError = isError,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Password, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
trailingIcon = {
|
||||
if (icon != null) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (isError) {
|
||||
valueState = value.encodeToString()
|
||||
onValueChange(value)
|
||||
} else if (onGenerateKey != null && !isFocused) {
|
||||
onGenerateKey()
|
||||
}
|
||||
},
|
||||
)
|
||||
enabled = enabled,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = description,
|
||||
tint =
|
||||
if (isError) {
|
||||
MaterialTheme.colorScheme.error
|
||||
} else {
|
||||
LocalContentColor.current
|
||||
},
|
||||
)
|
||||
}
|
||||
} else if (trailingIcon != null) {
|
||||
trailingIcon()
|
||||
}
|
||||
} else if (trailingIcon != null) {
|
||||
trailingIcon()
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
|
@ -130,6 +141,7 @@ fun EditBase64Preference(
|
|||
private fun EditBase64PreferencePreview() {
|
||||
EditBase64Preference(
|
||||
title = "Title",
|
||||
summary = "This is a summary",
|
||||
value = Channel.getRandomKey(),
|
||||
enabled = true,
|
||||
keyboardActions = KeyboardActions {},
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import com.geeksville.mesh.copy
|
|||
import com.geeksville.mesh.remoteHardwarePin
|
||||
import com.google.protobuf.ByteString
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
inline fun <reified T> EditListPreference(
|
||||
title: String,
|
||||
|
|
@ -56,12 +57,21 @@ inline fun <reified T> EditListPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
crossinline onValuesChanged: (List<T>) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val listState = remember(list) { mutableStateListOf<T>().apply { addAll(list) } }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Text(modifier = modifier.padding(16.dp), text = title, style = MaterialTheme.typography.bodyMedium)
|
||||
Column(modifier = modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
||||
Text(text = title, style = MaterialTheme.typography.titleLarge)
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
listState.forEachIndexed { index, value ->
|
||||
val trailingIcon =
|
||||
@Composable {
|
||||
|
|
@ -80,80 +90,75 @@ inline fun <reified T> EditListPreference(
|
|||
}
|
||||
}
|
||||
|
||||
// handle lora.ignoreIncoming: List<Int>
|
||||
if (value is Int) {
|
||||
EditTextPreference(
|
||||
title = "${index + 1}/$maxCount",
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
listState[index] = it as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
// handle security.adminKey: List<ByteString>
|
||||
if (value is ByteString) {
|
||||
EditBase64Preference(
|
||||
title = "${index + 1}/$maxCount",
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChange = {
|
||||
listState[index] = it as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
// handle remoteHardware.availablePins: List<RemoteHardwarePin>
|
||||
if (value is RemoteHardwarePin) {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin),
|
||||
value = value.gpioPin,
|
||||
enabled = enabled,
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it in 0..255) {
|
||||
listState[index] = value.copy { gpioPin = it } as T
|
||||
when (value) {
|
||||
is Int -> {
|
||||
EditTextPreference(
|
||||
title = "${index + 1}/$maxCount",
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
listState[index] = it as T
|
||||
onValuesChanged(listState)
|
||||
}
|
||||
},
|
||||
)
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.name),
|
||||
value = value.name,
|
||||
maxSize = 14, // name max_size:15
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
listState[index] = value.copy { name = it } as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.type),
|
||||
enabled = enabled,
|
||||
items =
|
||||
RemoteHardwarePinType.entries
|
||||
.filter { it != RemoteHardwarePinType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = value.type,
|
||||
onItemSelected = {
|
||||
listState[index] = value.copy { type = it } as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
)
|
||||
},
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
is ByteString -> {
|
||||
EditBase64Preference(
|
||||
title = "${index + 1}/$maxCount",
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChange = {
|
||||
listState[index] = it as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
is RemoteHardwarePin -> {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gpio_pin),
|
||||
value = value.gpioPin,
|
||||
enabled = enabled,
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it in 0..255) {
|
||||
listState[index] = value.copy { gpioPin = it } as T
|
||||
onValuesChanged(listState)
|
||||
}
|
||||
},
|
||||
)
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.name),
|
||||
value = value.name,
|
||||
maxSize = 14, // name max_size:15
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
listState[index] = value.copy { name = it } as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.type),
|
||||
enabled = enabled,
|
||||
items =
|
||||
RemoteHardwarePinType.entries
|
||||
.filter { it != RemoteHardwarePinType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = value.type,
|
||||
onItemSelected = {
|
||||
listState[index] = value.copy { type = it } as T
|
||||
onValuesChanged(listState)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
OutlinedButton(
|
||||
|
|
@ -182,6 +187,7 @@ private fun EditListPreferencePreview() {
|
|||
Column {
|
||||
EditListPreference(
|
||||
title = stringResource(R.string.ignore_incoming),
|
||||
summary = "This is a summary",
|
||||
list = listOf(12345, 67890),
|
||||
maxCount = 4,
|
||||
enabled = true,
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.twotone.Info
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -54,6 +54,7 @@ fun SignedIntegerEditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
|
|
@ -63,20 +64,17 @@ fun SignedIntegerEditTextPreference(
|
|||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = valueState.toIntOrNull() == null,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
valueState = it
|
||||
it.toIntOrNull()?.let { int ->
|
||||
onValueChanged(int)
|
||||
}
|
||||
it.toIntOrNull()?.let { int -> onValueChanged(int) }
|
||||
},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +87,7 @@ fun EditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
) {
|
||||
|
|
@ -98,21 +97,23 @@ fun EditTextPreference(
|
|||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = value.toUInt().toString() != valueState || isError,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it.isEmpty()) valueState = it
|
||||
else it.toUIntOrNull()?.toInt()?.let { int ->
|
||||
if (it.isEmpty()) {
|
||||
valueState = it
|
||||
onValueChanged(int)
|
||||
} else {
|
||||
it.toUIntOrNull()?.toInt()?.let { int ->
|
||||
valueState = it
|
||||
onValueChanged(int)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier,
|
||||
trailingIcon = trailingIcon
|
||||
trailingIcon = trailingIcon,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -124,28 +125,31 @@ fun EditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
) {
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toString()) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = value.toString() != valueState,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it.isEmpty()) valueState = it
|
||||
else it.toFloatOrNull()?.let { float ->
|
||||
if (it.isEmpty()) {
|
||||
valueState = it
|
||||
onValueChanged(float)
|
||||
} else {
|
||||
it.toFloatOrNull()?.let { float ->
|
||||
valueState = it
|
||||
onValueChanged(float)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusChanged = onFocusChanged,
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +161,7 @@ fun EditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (Double) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
var valueState by remember(value) { mutableStateOf(value.toString()) }
|
||||
val decimalSeparators = setOf('.', ',', '٫', '、', '·') // set of possible decimal separators
|
||||
|
|
@ -165,20 +170,22 @@ fun EditTextPreference(
|
|||
title = title,
|
||||
value = valueState,
|
||||
enabled = enabled,
|
||||
summary = summary,
|
||||
isError = value.toString() != valueState,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
|
||||
keyboardActions = keyboardActions,
|
||||
onValueChanged = {
|
||||
if (it.length <= 1 || it.first() in decimalSeparators) valueState = it
|
||||
else it.toDoubleOrNull()?.let { double ->
|
||||
if (it.length <= 1 || it.first() in decimalSeparators) {
|
||||
valueState = it
|
||||
onValueChanged(double)
|
||||
} else {
|
||||
it.toDoubleOrNull()?.let { double ->
|
||||
valueState = it
|
||||
onValueChanged(double)
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocusChanged = {},
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +199,7 @@ fun EditTextPreference(
|
|||
keyboardActions: KeyboardActions,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
maxSize: Int = 0, // max_size - 1 (in bytes)
|
||||
onFocusChanged: (FocusState) -> Unit = {},
|
||||
trailingIcon: (@Composable () -> Unit)? = null,
|
||||
|
|
@ -199,49 +207,60 @@ fun EditTextPreference(
|
|||
) {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
|
||||
TextField(
|
||||
value = value,
|
||||
singleLine = true,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.onFocusEvent { isFocused = it.isFocused; onFocusChanged(it) },
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
onValueChange = {
|
||||
if (maxSize > 0) {
|
||||
if (it.toByteArray().size <= maxSize) {
|
||||
Column(modifier = modifier.padding(vertical = 8.dp)) {
|
||||
OutlinedTextField(
|
||||
value = value,
|
||||
singleLine = true,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().onFocusEvent {
|
||||
isFocused = it.isFocused
|
||||
onFocusChanged(it)
|
||||
},
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
onValueChange = {
|
||||
if (maxSize > 0) {
|
||||
if (it.toByteArray().size <= maxSize) {
|
||||
onValueChanged(it)
|
||||
}
|
||||
} else {
|
||||
onValueChanged(it)
|
||||
}
|
||||
} else onValueChanged(it)
|
||||
},
|
||||
label = { Text(title) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
visualTransformation = visualTransformation,
|
||||
trailingIcon = {
|
||||
if (trailingIcon != null) {
|
||||
trailingIcon()
|
||||
} else if (isError) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Info,
|
||||
contentDescription = stringResource(id = R.string.error),
|
||||
tint = MaterialTheme.colorScheme.error
|
||||
},
|
||||
label = { Text(title) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
visualTransformation = visualTransformation,
|
||||
trailingIcon = {
|
||||
if (trailingIcon != null) {
|
||||
trailingIcon()
|
||||
} else if (isError) {
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.Info,
|
||||
contentDescription = stringResource(id = R.string.error),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
if (summary != null) {
|
||||
Text(
|
||||
text = summary,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(top = 4.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (maxSize > 0 && isFocused) {
|
||||
Box(contentAlignment = Alignment.BottomEnd, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = "${value.toByteArray().size}/$maxSize",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.padding(end = 8.dp, bottom = 4.dp),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (maxSize > 0 && isFocused) {
|
||||
Box(
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = "${value.toByteArray().size}/$maxSize",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (isError) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.padding(end = 8.dp, bottom = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -253,6 +272,7 @@ private fun EditTextPreferencePreview() {
|
|||
EditTextPreference(
|
||||
title = "String",
|
||||
value = "Meshtastic",
|
||||
summary = "This is a summary",
|
||||
maxSize = 39,
|
||||
enabled = true,
|
||||
isError = false,
|
||||
|
|
@ -265,7 +285,7 @@ private fun EditTextPreferencePreview() {
|
|||
value = UInt.MAX_VALUE.toInt(),
|
||||
enabled = true,
|
||||
keyboardActions = KeyboardActions {},
|
||||
onValueChanged = {}
|
||||
onValueChanged = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,8 +80,18 @@ fun RadioConfigItemList(
|
|||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
ConfigRoute.radioConfigRoutes.forEach {
|
||||
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
|
||||
onRouteClick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConfigRoute.filterExcludedFrom(state.metadata).forEach {
|
||||
TitledCard(title = stringResource(R.string.device_configuration), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
ConfigRoute.deviceConfigRoutes(state.metadata).forEach {
|
||||
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
|
||||
onRouteClick(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,33 +60,33 @@ import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
|||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
private val DeviceConfig.Role.stringRes: Int
|
||||
private val DeviceConfig.Role.description: Int
|
||||
get() =
|
||||
when (this) {
|
||||
DeviceConfig.Role.CLIENT -> R.string.role_client
|
||||
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute
|
||||
DeviceConfig.Role.ROUTER -> R.string.role_router
|
||||
DeviceConfig.Role.ROUTER_CLIENT -> R.string.role_router_client
|
||||
DeviceConfig.Role.REPEATER -> R.string.role_repeater
|
||||
DeviceConfig.Role.TRACKER -> R.string.role_tracker
|
||||
DeviceConfig.Role.SENSOR -> R.string.role_sensor
|
||||
DeviceConfig.Role.TAK -> R.string.role_tak
|
||||
DeviceConfig.Role.CLIENT_HIDDEN -> R.string.role_client_hidden
|
||||
DeviceConfig.Role.LOST_AND_FOUND -> R.string.role_lost_and_found
|
||||
DeviceConfig.Role.TAK_TRACKER -> R.string.role_tak_tracker
|
||||
DeviceConfig.Role.ROUTER_LATE -> R.string.role_router_late
|
||||
DeviceConfig.Role.CLIENT -> R.string.role_client_desc
|
||||
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute_desc
|
||||
DeviceConfig.Role.ROUTER -> R.string.role_router_desc
|
||||
DeviceConfig.Role.ROUTER_CLIENT -> R.string.role_router_client_desc
|
||||
DeviceConfig.Role.REPEATER -> R.string.role_repeater_desc
|
||||
DeviceConfig.Role.TRACKER -> R.string.role_tracker_desc
|
||||
DeviceConfig.Role.SENSOR -> R.string.role_sensor_desc
|
||||
DeviceConfig.Role.TAK -> R.string.role_tak_desc
|
||||
DeviceConfig.Role.CLIENT_HIDDEN -> R.string.role_client_hidden_desc
|
||||
DeviceConfig.Role.LOST_AND_FOUND -> R.string.role_lost_and_found_desc
|
||||
DeviceConfig.Role.TAK_TRACKER -> R.string.role_tak_tracker_desc
|
||||
DeviceConfig.Role.ROUTER_LATE -> R.string.role_router_late_desc
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
|
||||
private val DeviceConfig.RebroadcastMode.stringRes: Int
|
||||
private val DeviceConfig.RebroadcastMode.description: Int
|
||||
get() =
|
||||
when (this) {
|
||||
DeviceConfig.RebroadcastMode.ALL -> R.string.rebroadcast_mode_all
|
||||
DeviceConfig.RebroadcastMode.ALL_SKIP_DECODING -> R.string.rebroadcast_mode_all_skip_decoding
|
||||
DeviceConfig.RebroadcastMode.LOCAL_ONLY -> R.string.rebroadcast_mode_local_only
|
||||
DeviceConfig.RebroadcastMode.KNOWN_ONLY -> R.string.rebroadcast_mode_known_only
|
||||
DeviceConfig.RebroadcastMode.NONE -> R.string.rebroadcast_mode_none
|
||||
DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY -> R.string.rebroadcast_mode_core_portnums_only
|
||||
DeviceConfig.RebroadcastMode.ALL -> R.string.rebroadcast_mode_all_desc
|
||||
DeviceConfig.RebroadcastMode.ALL_SKIP_DECODING -> R.string.rebroadcast_mode_all_skip_decoding_desc
|
||||
DeviceConfig.RebroadcastMode.LOCAL_ONLY -> R.string.rebroadcast_mode_local_only_desc
|
||||
DeviceConfig.RebroadcastMode.KNOWN_ONLY -> R.string.rebroadcast_mode_known_only_desc
|
||||
DeviceConfig.RebroadcastMode.NONE -> R.string.rebroadcast_mode_none_desc
|
||||
DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY -> R.string.rebroadcast_mode_core_portnums_only_desc
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
|
||||
|
|
@ -160,60 +160,37 @@ fun DeviceConfigItemList(deviceConfig: DeviceConfig, enabled: Boolean, onSaveCli
|
|||
}
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.device_config)) }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.role),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.role,
|
||||
onItemSelected = { selectedRole = it },
|
||||
summary = stringResource(id = deviceInput.role.stringRes),
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.redefine_pin_button),
|
||||
value = deviceInput.buttonGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buttonGpio = it } },
|
||||
summary = stringResource(id = deviceInput.role.description),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.redefine_pin_buzzer),
|
||||
value = deviceInput.buzzerGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buzzerGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.rebroadcast_mode),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.rebroadcastMode,
|
||||
onItemSelected = { deviceInput = deviceInput.copy { rebroadcastMode = it } },
|
||||
summary = stringResource(id = deviceInput.rebroadcastMode.stringRes),
|
||||
summary = stringResource(id = deviceInput.rebroadcastMode.description),
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.nodeinfo_broadcast_interval_seconds),
|
||||
title = stringResource(R.string.nodeinfo_broadcast_interval),
|
||||
value = deviceInput.nodeInfoBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.hardware)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.double_tap_as_button_press),
|
||||
|
|
@ -222,24 +199,35 @@ fun DeviceConfigItemList(deviceConfig: DeviceConfig, enabled: Boolean, onSaveCli
|
|||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.disable_triple_click),
|
||||
summary = stringResource(id = R.string.config_device_disableTripleClick_summary),
|
||||
checked = deviceInput.disableTripleClick,
|
||||
title = stringResource(R.string.triple_click_adhoc_ping),
|
||||
summary = stringResource(id = R.string.config_device_tripleClickAsAdHocPing_summary),
|
||||
checked = !deviceInput.disableTripleClick,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = it } },
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = !it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.led_heartbeat),
|
||||
summary = stringResource(id = R.string.config_device_ledHeartbeatEnabled_summary),
|
||||
checked = !deviceInput.ledHeartbeatDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = !it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.debug)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.posix_timezone),
|
||||
title = stringResource(R.string.time_zone),
|
||||
value = deviceInput.tzdef,
|
||||
summary = stringResource(id = R.string.config_device_tzdef_summary),
|
||||
maxSize = 64, // tzdef max_size:65
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
|
|
@ -250,17 +238,25 @@ fun DeviceConfigItemList(deviceConfig: DeviceConfig, enabled: Boolean, onSaveCli
|
|||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.gpio)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.disable_led_heartbeat),
|
||||
summary = stringResource(id = R.string.config_device_ledHeartbeatDisabled_summary),
|
||||
checked = deviceInput.ledHeartbeatDisabled,
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.button_gpio),
|
||||
value = deviceInput.buttonGpio,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = it } },
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buttonGpio = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.buzzer_gpio),
|
||||
value = deviceInput.buzzerGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buzzerGpio = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && deviceInput != deviceConfig,
|
||||
|
|
|
|||
|
|
@ -69,52 +69,40 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.display_config)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.screen_timeout_seconds),
|
||||
value = displayInput.screenOnSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.auto_screen_carousel_seconds),
|
||||
value = displayInput.autoScreenCarouselSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { autoScreenCarouselSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.compass_north_top),
|
||||
title = stringResource(R.string.always_point_north),
|
||||
summary = stringResource(id = R.string.config_display_compass_north_top_summary),
|
||||
checked = displayInput.compassNorthTop,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { compassNorthTop = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.flip_screen),
|
||||
checked = displayInput.flipScreen,
|
||||
title = stringResource(R.string.use_12h_format),
|
||||
summary = stringResource(R.string.display_time_in_12h_format),
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } },
|
||||
checked = displayInput.use12HClock,
|
||||
onCheckedChange = { displayInput = displayInput.copy { use12HClock = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.bold_heading),
|
||||
summary = stringResource(id = R.string.config_display_heading_bold_summary),
|
||||
checked = displayInput.headingBold,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { headingBold = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.display_units),
|
||||
summary = stringResource(id = R.string.config_display_units_summary),
|
||||
enabled = enabled,
|
||||
items =
|
||||
DisplayConfig.DisplayUnits.entries
|
||||
|
|
@ -126,23 +114,54 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.advanced)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.override_oled_auto_detect),
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.screen_on_for),
|
||||
summary = stringResource(id = R.string.config_display_screen_on_secs_summary),
|
||||
value = displayInput.screenOnSecs,
|
||||
enabled = enabled,
|
||||
items =
|
||||
DisplayConfig.OledType.entries
|
||||
.filter { it != DisplayConfig.OledType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.oled,
|
||||
onItemSelected = { displayInput = displayInput.copy { oled = it } },
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { screenOnSecs = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.carousel_interval),
|
||||
summary = stringResource(id = R.string.config_display_auto_screen_carousel_secs_summary),
|
||||
value = displayInput.autoScreenCarouselSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { displayInput = displayInput.copy { autoScreenCarouselSecs = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wake_on_tap_or_motion),
|
||||
summary = stringResource(id = R.string.config_display_wake_on_tap_or_motion_summary),
|
||||
checked = displayInput.wakeOnTapOrMotion,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { wakeOnTapOrMotion = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.flip_screen),
|
||||
summary = stringResource(id = R.string.config_display_flip_screen_summary),
|
||||
checked = displayInput.flipScreen,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { flipScreen = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.display_mode),
|
||||
summary = stringResource(id = R.string.config_display_displaymode_summary),
|
||||
enabled = enabled,
|
||||
items =
|
||||
DisplayConfig.DisplayMode.entries
|
||||
|
|
@ -153,27 +172,20 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.heading_bold),
|
||||
checked = displayInput.headingBold,
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.oled_type),
|
||||
summary = stringResource(id = R.string.config_display_oled_summary),
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { headingBold = it } },
|
||||
items =
|
||||
DisplayConfig.OledType.entries
|
||||
.filter { it != DisplayConfig.OledType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = displayInput.oled,
|
||||
onItemSelected = { displayInput = displayInput.copy { oled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wake_screen_on_tap_or_motion),
|
||||
checked = displayInput.wakeOnTapOrMotion,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { displayInput = displayInput.copy { wakeOnTapOrMotion = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.compass_orientation),
|
||||
|
|
@ -188,17 +200,6 @@ fun DisplayConfigItemList(displayConfig: DisplayConfig, enabled: Boolean, onSave
|
|||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_12h_format),
|
||||
summary = stringResource(R.string.display_time_in_12h_format),
|
||||
enabled = enabled,
|
||||
checked = displayInput.use12HClock,
|
||||
onCheckedChange = { displayInput = displayInput.copy { use12HClock = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && displayInput != displayConfig,
|
||||
|
|
|
|||
|
|
@ -83,8 +83,18 @@ fun LoRaConfigItemList(
|
|||
val primaryChannel by remember(loraInput) { mutableStateOf(Channel(primarySettings, loraInput)) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.lora_config)) }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.options)) }
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.region_frequency_plan),
|
||||
summary = stringResource(id = R.string.config_lora_region_summary),
|
||||
enabled = enabled,
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = loraInput.region,
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_modem_preset),
|
||||
|
|
@ -99,6 +109,7 @@ fun LoRaConfigItemList(
|
|||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.modem_preset),
|
||||
summary = stringResource(id = R.string.config_lora_modem_preset_summary),
|
||||
enabled = enabled && loraInput.usePreset,
|
||||
items =
|
||||
LoRaConfig.ModemPreset.entries
|
||||
|
|
@ -140,37 +151,26 @@ fun LoRaConfigItemList(
|
|||
)
|
||||
}
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.advanced)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.frequency_offset_mhz),
|
||||
value = loraInput.frequencyOffset,
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = loraInput.ignoreMqtt,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
DropDownPreference(
|
||||
title = stringResource(R.string.region_frequency_plan),
|
||||
enabled = enabled,
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = loraInput.region,
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } },
|
||||
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.hop_limit),
|
||||
value = loraInput.hopLimit,
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = loraInput.configOkToMqtt,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } },
|
||||
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
|
|
@ -181,21 +181,23 @@ fun LoRaConfigItemList(
|
|||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.tx_power_dbm),
|
||||
value = loraInput.txPower,
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.hop_limit),
|
||||
summary = stringResource(id = R.string.config_lora_hop_limit_summary),
|
||||
value = loraInput.hopLimit,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { txPower = it } },
|
||||
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.frequency_slot),
|
||||
summary = stringResource(id = R.string.config_lora_frequency_slot_summary),
|
||||
value = if (isFocused || loraInput.channelNum != 0) loraInput.channelNum else primaryChannel.channelNum,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
@ -207,17 +209,7 @@ fun LoRaConfigItemList(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.override_duty_cycle),
|
||||
checked = loraInput.overrideDutyCycle,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.sx126x_rx_boosted_gain),
|
||||
|
|
@ -227,7 +219,6 @@ fun LoRaConfigItemList(
|
|||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
|
|
@ -244,6 +235,16 @@ fun LoRaConfigItemList(
|
|||
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item {
|
||||
SignedIntegerEditTextPreference(
|
||||
title = stringResource(R.string.tx_power_dbm),
|
||||
value = loraInput.txPower,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { txPower = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (hasPaFan) {
|
||||
item {
|
||||
|
|
@ -257,26 +258,6 @@ fun LoRaConfigItemList(
|
|||
item { HorizontalDivider() }
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = loraInput.ignoreMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = loraInput.configOkToMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && loraInput != loraConfig,
|
||||
|
|
|
|||
|
|
@ -131,63 +131,87 @@ fun NetworkConfigItemList(
|
|||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.network_config)) }
|
||||
if (hasWifi) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.wifi_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wifi_enabled),
|
||||
summary = stringResource(id = R.string.config_network_wifi_enabled_summary),
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = enabled && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.wifi_enabled),
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = enabled && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ssid),
|
||||
value = networkInput.wifiSsid,
|
||||
maxSize = 32, // wifi_ssid max_size:33
|
||||
enabled = enabled && 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 {
|
||||
EditPasswordPreference(
|
||||
title = stringResource(R.string.password),
|
||||
value = networkInput.wifiPsk,
|
||||
maxSize = 64, // wifi_psk max_size:65
|
||||
enabled = enabled && hasWifi,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Button(
|
||||
onClick = { zxingScan() },
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).height(48.dp),
|
||||
enabled = enabled && hasWifi,
|
||||
) {
|
||||
Text(text = stringResource(R.string.wifi_qr_code_scan))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ssid),
|
||||
value = networkInput.wifiSsid,
|
||||
maxSize = 32, // wifi_ssid max_size:33
|
||||
enabled = enabled && 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 {
|
||||
EditPasswordPreference(
|
||||
title = stringResource(R.string.psk),
|
||||
value = networkInput.wifiPsk,
|
||||
maxSize = 64, // wifi_psk max_size:65
|
||||
enabled = enabled && hasWifi,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Button(
|
||||
onClick = { zxingScan() },
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).height(48.dp),
|
||||
enabled = enabled && hasWifi,
|
||||
) {
|
||||
Text(text = stringResource(R.string.wifi_qr_code_scan))
|
||||
if (hasEthernet) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.ethernet_config)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ethernet_enabled),
|
||||
summary = stringResource(id = R.string.config_network_eth_enabled_summary),
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled && hasEthernet,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.ethernet_enabled),
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled && hasEthernet,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
if (hasEthernet || hasWifi) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.udp_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.udp_enabled),
|
||||
summary = stringResource(id = R.string.config_network_udp_enabled_summary),
|
||||
checked = networkInput.enabledProtocols == 1,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
networkInput = networkInput.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.advanced)) }
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.ntp_server),
|
||||
|
|
@ -282,22 +306,7 @@ fun NetworkConfigItemList(
|
|||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
if (hasEthernet || hasWifi) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.udp_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.mesh_via_udp_enabled),
|
||||
checked = networkInput.enabledProtocols == 1,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
networkInput = networkInput.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
}
|
||||
item {
|
||||
PreferenceFooter(
|
||||
enabled = enabled && networkInput != networkConfig,
|
||||
|
|
|
|||
|
|
@ -144,11 +144,12 @@ fun PositionConfigItemList(
|
|||
}
|
||||
}
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_config)) }
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_packet)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.position_broadcast_interval_seconds),
|
||||
title = stringResource(R.string.broadcast_interval),
|
||||
summary = stringResource(id = R.string.config_position_broadcast_secs_summary),
|
||||
value = positionInput.positionBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
@ -158,7 +159,7 @@ fun PositionConfigItemList(
|
|||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.smart_position_enabled),
|
||||
title = stringResource(R.string.smart_position),
|
||||
checked = positionInput.positionBroadcastSmartEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { positionInput = positionInput.copy { positionBroadcastSmartEnabled = it } },
|
||||
|
|
@ -169,28 +170,30 @@ fun PositionConfigItemList(
|
|||
if (positionInput.positionBroadcastSmartEnabled) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.smart_broadcast_minimum_distance_meters),
|
||||
value = positionInput.broadcastSmartMinimumDistance,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumDistance = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.smart_broadcast_minimum_interval_seconds),
|
||||
title = stringResource(R.string.minimum_interval),
|
||||
summary =
|
||||
stringResource(id = R.string.config_position_broadcast_smart_minimum_interval_secs_summary),
|
||||
value = positionInput.broadcastSmartMinimumIntervalSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumIntervalSecs = it } },
|
||||
)
|
||||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.minimum_distance),
|
||||
summary = stringResource(id = R.string.config_position_broadcast_smart_minimum_distance_summary),
|
||||
value = positionInput.broadcastSmartMinimumDistance,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { broadcastSmartMinimumDistance = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.device_gps)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_fixed_position),
|
||||
title = stringResource(R.string.fixed_position),
|
||||
checked = positionInput.fixedPosition,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { positionInput = positionInput.copy { fixedPosition = it } },
|
||||
|
|
@ -227,7 +230,7 @@ fun PositionConfigItemList(
|
|||
}
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.altitude_meters),
|
||||
title = stringResource(R.string.altitude),
|
||||
value = locationInput.altitude,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
@ -260,17 +263,19 @@ fun PositionConfigItemList(
|
|||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.gps_update_interval_seconds),
|
||||
title = stringResource(R.string.update_interval),
|
||||
summary = stringResource(id = R.string.config_position_gps_update_interval_summary),
|
||||
value = positionInput.gpsUpdateInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { positionInput = positionInput.copy { gpsUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.position_flags)) }
|
||||
item {
|
||||
BitwisePreference(
|
||||
title = stringResource(R.string.position_flags),
|
||||
summary = stringResource(id = R.string.config_position_flags_summary),
|
||||
value = positionInput.positionFlags,
|
||||
enabled = enabled,
|
||||
items =
|
||||
|
|
@ -283,10 +288,11 @@ fun PositionConfigItemList(
|
|||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item { PreferenceCategory(text = stringResource(R.string.advanced_device_gps)) }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.redefine_gps_rx_pin),
|
||||
title = stringResource(R.string.gps_receive_gpio),
|
||||
value = positionInput.rxGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
@ -296,7 +302,7 @@ fun PositionConfigItemList(
|
|||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.redefine_gps_tx_pin),
|
||||
title = stringResource(R.string.gps_transmit_gpio),
|
||||
value = positionInput.txGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
@ -306,7 +312,7 @@ fun PositionConfigItemList(
|
|||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.redefine_pin_gps_en),
|
||||
title = stringResource(R.string.gps_en_gpio),
|
||||
value = positionInput.gpsEnGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
|||
fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicked: (PowerConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var powerInput by rememberSaveable { mutableStateOf(powerConfig) }
|
||||
var shutdownOnPowerLoss by rememberSaveable { mutableStateOf(powerConfig.onBatteryShutdownAfterSecs > 0) }
|
||||
var adcOverride by rememberSaveable { mutableStateOf(powerConfig.adcMultiplierOverride > 0f) }
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.power_config)) }
|
||||
|
|
@ -72,6 +74,7 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.enable_power_saving_mode),
|
||||
summary = stringResource(id = R.string.config_power_is_power_saving_summary),
|
||||
checked = powerInput.isPowerSaving,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { powerInput = powerInput.copy { isPowerSaving = it } },
|
||||
|
|
@ -80,25 +83,57 @@ fun PowerConfigItemList(powerConfig: PowerConfig, enabled: Boolean, onSaveClicke
|
|||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.shutdown_on_battery_delay_seconds),
|
||||
value = powerInput.onBatteryShutdownAfterSecs,
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.shutdown_on_power_loss),
|
||||
checked = shutdownOnPowerLoss,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } },
|
||||
onCheckedChange = {
|
||||
shutdownOnPowerLoss = it
|
||||
if (!it) powerInput = powerInput.copy { onBatteryShutdownAfterSecs = 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (shutdownOnPowerLoss) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.shutdown_on_battery_delay_seconds),
|
||||
value = powerInput.onBatteryShutdownAfterSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { onBatteryShutdownAfterSecs = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override_ratio),
|
||||
value = powerInput.adcMultiplierOverride,
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override),
|
||||
checked = adcOverride,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } },
|
||||
onCheckedChange = {
|
||||
adcOverride = it
|
||||
if (!it) powerInput = powerInput.copy { adcMultiplierOverride = 0f }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (adcOverride) {
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.adc_multiplier_override_ratio),
|
||||
value = powerInput.adcMultiplierOverride,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { powerInput = powerInput.copy { adcMultiplierOverride = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.wait_for_bluetooth_duration_seconds),
|
||||
|
|
|
|||
|
|
@ -154,18 +154,19 @@ fun SecurityConfigItemList(
|
|||
}
|
||||
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.security_config)) }
|
||||
item { PreferenceCategory(text = stringResource(R.string.direct_message_key)) }
|
||||
|
||||
item {
|
||||
EditBase64Preference(
|
||||
title = stringResource(R.string.public_key),
|
||||
summary = stringResource(id = R.string.config_security_public_key),
|
||||
value = publicKey,
|
||||
enabled = enabled,
|
||||
readOnly = true,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChange = {
|
||||
if (it.size() == 32) {
|
||||
securityInput = securityInput.copy { publicKey = it }
|
||||
securityInput = securityInput.copy { this.publicKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.publicKey.encodeToString()) },
|
||||
|
|
@ -175,6 +176,7 @@ fun SecurityConfigItemList(
|
|||
item {
|
||||
EditBase64Preference(
|
||||
title = stringResource(R.string.private_key),
|
||||
summary = stringResource(id = R.string.config_security_private_key),
|
||||
value = securityInput.privateKey,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
|
|
@ -206,10 +208,11 @@ fun SecurityConfigItemList(
|
|||
onClick = { showEditSecurityConfigDialog = true },
|
||||
)
|
||||
}
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.admin_keys)) }
|
||||
item {
|
||||
EditListPreference(
|
||||
title = stringResource(R.string.admin_key),
|
||||
summary = stringResource(id = R.string.config_security_admin_key),
|
||||
list = securityInput.adminKeyList,
|
||||
maxCount = 3,
|
||||
enabled = enabled,
|
||||
|
|
@ -223,20 +226,11 @@ fun SecurityConfigItemList(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.managed_mode),
|
||||
checked = securityInput.isManaged,
|
||||
enabled = enabled && securityInput.adminKeyCount > 0,
|
||||
onCheckedChange = { securityInput = securityInput.copy { isManaged = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item { PreferenceCategory(text = stringResource(R.string.logs)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.serial_console),
|
||||
summary = stringResource(id = R.string.config_security_serial_enabled),
|
||||
checked = securityInput.serialEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } },
|
||||
|
|
@ -247,12 +241,24 @@ fun SecurityConfigItemList(
|
|||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.debug_log_api_enabled),
|
||||
summary = stringResource(id = R.string.config_security_debug_log_api_enabled),
|
||||
checked = securityInput.debugLogApiEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { debugLogApiEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
item { PreferenceCategory(text = stringResource(R.string.administration)) }
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.managed_mode),
|
||||
summary = stringResource(id = R.string.config_security_is_managed),
|
||||
checked = securityInput.isManaged,
|
||||
enabled = enabled && securityInput.adminKeyCount > 0,
|
||||
onCheckedChange = { securityInput = securityInput.copy { isManaged = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue