mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(settings): replace interval inputs with dropdowns (#3352)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
e5a28d6942
commit
c6be5be72f
29 changed files with 2343 additions and 2028 deletions
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.meshtastic.core.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
|
|
@ -32,7 +33,6 @@ 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.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.protobuf.ProtocolMessageEnum
|
||||
|
|
@ -84,49 +84,45 @@ fun <T> DropDownPreference(
|
|||
emptyList()
|
||||
}
|
||||
}
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
if (enabled) {
|
||||
expanded = !expanded
|
||||
}
|
||||
},
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled),
|
||||
readOnly = true,
|
||||
value = "",
|
||||
onValueChange = {},
|
||||
prefix = { Text(title) },
|
||||
suffix = { Text(items.firstOrNull { it.first == selectedItem }?.second ?: "") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors =
|
||||
ExposedDropdownMenuDefaults.outlinedTextFieldColors(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
disabledBorderColor = Color.Transparent,
|
||||
errorBorderColor = Color.Transparent,
|
||||
),
|
||||
enabled = enabled,
|
||||
supportingText =
|
||||
if (summary != null) {
|
||||
{ Text(text = summary, modifier = Modifier.padding(bottom = 8.dp)) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
items
|
||||
.filterNot { it.first in deprecatedItems }
|
||||
.forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(selectionOption.second) },
|
||||
onClick = {
|
||||
onItemSelected(selectionOption.first)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
Column(modifier = modifier.fillMaxWidth().padding(8.dp)) {
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = {
|
||||
if (enabled) {
|
||||
expanded = !expanded
|
||||
}
|
||||
},
|
||||
) {
|
||||
OutlinedTextField(
|
||||
label = { Text(text = title) },
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable, enabled),
|
||||
readOnly = true,
|
||||
value = items.firstOrNull { it.first == selectedItem }?.second ?: "",
|
||||
onValueChange = {},
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
colors = ExposedDropdownMenuDefaults.textFieldColors(),
|
||||
enabled = enabled,
|
||||
supportingText =
|
||||
if (summary != null) {
|
||||
{ Text(text = summary, modifier = Modifier.padding(bottom = 8.dp)) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
items
|
||||
.filterNot { it.first in deprecatedItems }
|
||||
.forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(selectionOption.second) },
|
||||
onClick = {
|
||||
onItemSelected(selectionOption.first)
|
||||
expanded = false
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,8 @@ import androidx.compose.foundation.text.KeyboardOptions
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Info
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -40,12 +38,10 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusState
|
||||
import androidx.compose.ui.focus.onFocusEvent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
|
|
@ -211,15 +207,11 @@ fun EditTextPreference(
|
|||
) {
|
||||
var isFocused by remember { mutableStateOf(false) }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
Column(modifier = modifier.fillMaxWidth().padding(8.dp)) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().onFocusEvent { onFocusChanged(it) },
|
||||
value = value,
|
||||
singleLine = true,
|
||||
modifier =
|
||||
Modifier.fillMaxWidth().onFocusEvent {
|
||||
isFocused = it.isFocused
|
||||
onFocusChanged(it)
|
||||
},
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
onValueChange = {
|
||||
|
|
@ -231,7 +223,7 @@ fun EditTextPreference(
|
|||
onValueChanged(it)
|
||||
}
|
||||
},
|
||||
prefix = { Text(title) },
|
||||
label = { Text(title) },
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
visualTransformation = visualTransformation,
|
||||
|
|
@ -249,14 +241,6 @@ fun EditTextPreference(
|
|||
} else {
|
||||
null
|
||||
},
|
||||
colors =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
disabledBorderColor = Color.Transparent,
|
||||
errorBorderColor = Color.Transparent,
|
||||
),
|
||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.End),
|
||||
)
|
||||
if (summary != null) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.core.ui.component
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun <T> SliderPreference(
|
||||
title: String,
|
||||
enabled: Boolean,
|
||||
items: List<Pair<T, String>>,
|
||||
selectedValue: T,
|
||||
onValueChange: (T) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
summary: String? = null,
|
||||
) {
|
||||
if (items.isEmpty()) return
|
||||
|
||||
val selectedIndex = items.indexOfFirst { it.first == selectedValue }.toFloat()
|
||||
val valueRange = 0f..(items.size - 1).toFloat()
|
||||
val steps = (items.size - 2).coerceAtLeast(0)
|
||||
|
||||
ListItem(
|
||||
modifier = modifier,
|
||||
headlineContent = {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.End,
|
||||
text = items.firstOrNull { it.first == selectedValue }?.second ?: items.first().second,
|
||||
)
|
||||
},
|
||||
overlineContent = { Text(text = title) },
|
||||
supportingContent = {
|
||||
Column {
|
||||
summary?.let { Text(text = it, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) }
|
||||
Slider(
|
||||
value = selectedIndex.coerceIn(valueRange),
|
||||
onValueChange = {
|
||||
val index = it.roundToInt()
|
||||
if (index in items.indices) {
|
||||
onValueChange(items[index].first)
|
||||
}
|
||||
},
|
||||
valueRange = valueRange,
|
||||
steps = steps,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SliderPreferencePreview() {
|
||||
val items = listOf(1L to "One", 2L to "Two", 3L to "Three", 4L to "Four", 5L to "Five")
|
||||
AppTheme {
|
||||
SliderPreference(
|
||||
title = "Slider",
|
||||
summary = "Select a value",
|
||||
enabled = true,
|
||||
items = items,
|
||||
selectedValue = 3L,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SliderPreferenceDisabledPreview() {
|
||||
val items = listOf(1L to "One", 2L to "Two", 3L to "Three", 4L to "Four", 5L to "Five")
|
||||
AppTheme {
|
||||
SliderPreference(
|
||||
title = "Slider",
|
||||
summary = "Select a value",
|
||||
enabled = false,
|
||||
items = items,
|
||||
selectedValue = 3L,
|
||||
onValueChange = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue