mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
parent
7e0cfff67b
commit
ddad40a969
4 changed files with 169 additions and 84 deletions
|
|
@ -1,17 +1,33 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Slider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.util.DistanceUnit
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
import java.util.Locale
|
||||
import kotlin.math.pow
|
||||
|
||||
private enum class PositionPrecision(val value: Int) {
|
||||
HIGH_PRECISION(32),
|
||||
MED_PRECISION(16),
|
||||
LOW_PRECISION(11),
|
||||
DISABLED(0),
|
||||
;
|
||||
}
|
||||
private const val PositionEnabled = 32
|
||||
private const val PositionDisabled = 0
|
||||
|
||||
const val PositionPrecisionMin = 10
|
||||
const val PositionPrecisionMax = 19
|
||||
const val PositionPrecisionDefault = 13
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun precisionBitsToMeters(bits: Int): Double = 23905787.925008 * 0.5.pow(bits.toDouble())
|
||||
|
||||
@Composable
|
||||
fun PositionPrecisionPreference(
|
||||
|
|
@ -21,24 +37,64 @@ fun PositionPrecisionPreference(
|
|||
onValueChanged: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
val useDropDown = PositionPrecision.entries.any { it.value == value }
|
||||
val unit = remember { DistanceUnit.getFromLocale(Locale.getDefault()) }
|
||||
|
||||
if (useDropDown) {
|
||||
DropDownPreference(
|
||||
Column(modifier = modifier) {
|
||||
SwitchPreference(
|
||||
title = title,
|
||||
checked = value != PositionDisabled,
|
||||
enabled = enabled,
|
||||
items = PositionPrecision.entries.map { it.value to it.name },
|
||||
selectedItem = value,
|
||||
onItemSelected = onValueChanged,
|
||||
)
|
||||
} else {
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = onValueChanged,
|
||||
onCheckedChange = { enabled ->
|
||||
val newValue = if (enabled) PositionEnabled else PositionDisabled
|
||||
onValueChanged(newValue)
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
)
|
||||
AnimatedVisibility(visible = value != PositionDisabled) {
|
||||
SwitchPreference(
|
||||
title = "Precise location",
|
||||
checked = value == PositionEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { enabled ->
|
||||
val newValue = if (enabled) PositionEnabled else PositionPrecisionDefault
|
||||
onValueChanged(newValue)
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = value in (PositionDisabled + 1)..<PositionEnabled) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Slider(
|
||||
value = value.toFloat(),
|
||||
onValueChange = { onValueChanged(it.toInt()) },
|
||||
enabled = enabled,
|
||||
valueRange = PositionPrecisionMin.toFloat()..PositionPrecisionMax.toFloat(),
|
||||
steps = PositionPrecisionMax - PositionPrecisionMin - 1,
|
||||
)
|
||||
|
||||
val precisionMeters = precisionBitsToMeters(value).toInt()
|
||||
Text(
|
||||
text = precisionMeters.toDistanceString(unit),
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
fontSize = MaterialTheme.typography.body1.fontSize,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PositionPrecisionPreferencePreview() {
|
||||
PositionPrecisionPreference(
|
||||
title = "Position enabled",
|
||||
value = PositionPrecisionDefault,
|
||||
enabled = true,
|
||||
onValueChanged = {},
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -26,12 +27,13 @@ fun SwitchPreference(
|
|||
enabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.size(48.dp)
|
||||
.padding(start = 16.dp, end = 16.dp),
|
||||
.padding(padding),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.geeksville.mesh.ui.components.config
|
|||
import android.util.Base64
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
|
|
@ -16,8 +18,8 @@ import androidx.compose.material.LocalContentAlpha
|
|||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.Close
|
||||
import androidx.compose.material.icons.twotone.Refresh
|
||||
|
|
@ -26,7 +28,6 @@ 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.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -41,11 +42,14 @@ import com.geeksville.mesh.copy
|
|||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.ui.components.EditTextPreference
|
||||
import com.geeksville.mesh.ui.components.PositionPrecisionPreference
|
||||
import com.geeksville.mesh.ui.components.SwitchPreference
|
||||
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
|
||||
import com.google.protobuf.ByteString
|
||||
import com.google.protobuf.kotlin.toByteString
|
||||
import java.security.SecureRandom
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun EditChannelDialog(
|
||||
channelSettings: ChannelProtos.ChannelSettings,
|
||||
|
|
@ -133,32 +137,28 @@ fun EditChannelDialog(
|
|||
},
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Uplink enabled", // TODO move to resource strings
|
||||
modifier = modifier.weight(1f)
|
||||
)
|
||||
Switch(
|
||||
checked = channelInput.uplinkEnabled,
|
||||
onCheckedChange = {
|
||||
channelInput = channelInput.copy { uplinkEnabled = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = "Uplink enabled",
|
||||
checked = channelInput.uplinkEnabled,
|
||||
enabled = true,
|
||||
onCheckedChange = {
|
||||
channelInput = channelInput.copy { uplinkEnabled = it }
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
)
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text("Downlink enabled", // TODO move to resource strings
|
||||
modifier = modifier.weight(1f)
|
||||
)
|
||||
Switch(
|
||||
checked = channelInput.downlinkEnabled,
|
||||
onCheckedChange = {
|
||||
channelInput = channelInput.copy { downlinkEnabled = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = "Downlink enabled",
|
||||
checked = channelInput.downlinkEnabled,
|
||||
enabled = true,
|
||||
onCheckedChange = {
|
||||
channelInput = channelInput.copy { downlinkEnabled = it }
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
)
|
||||
|
||||
PositionPrecisionPreference(
|
||||
title = "Position",
|
||||
title = "Position enabled",
|
||||
enabled = true,
|
||||
value = channelInput.moduleSettings.positionPrecision,
|
||||
onValueChanged = {
|
||||
|
|
@ -170,22 +170,21 @@ fun EditChannelDialog(
|
|||
}
|
||||
},
|
||||
buttons = {
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
FlowRow(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Button(
|
||||
TextButton(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp)
|
||||
.weight(1f),
|
||||
onClick = onDismissRequest
|
||||
) { Text(stringResource(R.string.cancel)) }
|
||||
Button(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 24.dp)
|
||||
.weight(1f),
|
||||
onClick = {
|
||||
onAddClick(channelInput.copy { name = channelInput.name.trim() })
|
||||
|
|
|
|||
|
|
@ -1,46 +1,74 @@
|
|||
package com.geeksville.mesh.util
|
||||
|
||||
import com.geeksville.mesh.ConfigProtos
|
||||
import android.icu.util.LocaleData
|
||||
import android.icu.util.ULocale
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
|
||||
import java.util.Locale
|
||||
|
||||
enum class DistanceUnit(
|
||||
val symbol: String,
|
||||
val multiplier: Float,
|
||||
val system: Int
|
||||
) {
|
||||
METERS("m", 1F, ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE),
|
||||
KILOMETERS("km", 0.001F, ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE),
|
||||
FEET("ft", 3.28084F, ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE),
|
||||
MILES("mi", 0.000621371F, ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE),
|
||||
METER("m", multiplier = 1F, DisplayConfig.DisplayUnits.METRIC_VALUE),
|
||||
KILOMETER("km", multiplier = 0.001F, DisplayConfig.DisplayUnits.METRIC_VALUE),
|
||||
FOOT("ft", multiplier = 3.28084F, DisplayConfig.DisplayUnits.IMPERIAL_VALUE),
|
||||
MILE("mi", multiplier = 0.000621371F, DisplayConfig.DisplayUnits.IMPERIAL_VALUE),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun getFromLocale(locale: Locale): DisplayConfig.DisplayUnits {
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
when (LocaleData.getMeasurementSystem(ULocale.forLocale(locale))) {
|
||||
LocaleData.MeasurementSystem.SI -> DisplayConfig.DisplayUnits.METRIC
|
||||
else -> DisplayConfig.DisplayUnits.IMPERIAL
|
||||
}
|
||||
} else {
|
||||
when (locale.country.uppercase(locale)) {
|
||||
"US", "LR", "MM", "GB" -> DisplayConfig.DisplayUnits.IMPERIAL
|
||||
else -> DisplayConfig.DisplayUnits.METRIC
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.metersIn(unit: DistanceUnit): Float {
|
||||
return this * unit.multiplier
|
||||
}
|
||||
|
||||
fun Int.metersIn(system: ConfigProtos.Config.DisplayConfig.DisplayUnits): Float {
|
||||
return this * when (system.number) {
|
||||
ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE -> DistanceUnit.METERS.multiplier
|
||||
ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE -> DistanceUnit.FEET.multiplier
|
||||
else -> throw IllegalArgumentException("Unknown distance system $system")
|
||||
fun Int.metersIn(system: DisplayConfig.DisplayUnits): Float {
|
||||
val unit = when (system.number) {
|
||||
DisplayConfig.DisplayUnits.IMPERIAL_VALUE -> DistanceUnit.FOOT
|
||||
else -> DistanceUnit.METER
|
||||
}
|
||||
return this.metersIn(unit)
|
||||
}
|
||||
|
||||
fun Float.toString(unit: DistanceUnit): String {
|
||||
return "%.1f %s".format(this, unit.symbol)
|
||||
return if (unit in setOf(DistanceUnit.METER, DistanceUnit.FOOT)) {
|
||||
"%.0f %s"
|
||||
} else {
|
||||
"%.1f %s"
|
||||
}.format(this, unit.symbol)
|
||||
}
|
||||
|
||||
fun Float.toString(
|
||||
system: ConfigProtos.Config.DisplayConfig.DisplayUnits
|
||||
): String {
|
||||
return "%.1f %s".format(this,
|
||||
when (system.number) {
|
||||
ConfigProtos.Config.DisplayConfig.DisplayUnits.METRIC_VALUE -> {
|
||||
DistanceUnit.METERS.symbol
|
||||
}
|
||||
ConfigProtos.Config.DisplayConfig.DisplayUnits.IMPERIAL_VALUE -> {
|
||||
DistanceUnit.FEET.symbol
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown distance system $system")
|
||||
},
|
||||
)
|
||||
}
|
||||
fun Float.toString(system: DisplayConfig.DisplayUnits): String {
|
||||
val unit = when (system.number) {
|
||||
DisplayConfig.DisplayUnits.IMPERIAL_VALUE -> DistanceUnit.FOOT
|
||||
else -> DistanceUnit.METER
|
||||
}
|
||||
return this.toString(unit)
|
||||
}
|
||||
|
||||
private const val KILOMETER_THRESHOLD = 1000
|
||||
private const val MILE_THRESHOLD = 1609
|
||||
fun Int.toDistanceString(system: DisplayConfig.DisplayUnits): String {
|
||||
val unit = if (system.number == DisplayConfig.DisplayUnits.METRIC_VALUE) {
|
||||
if (this < KILOMETER_THRESHOLD) DistanceUnit.METER else DistanceUnit.KILOMETER
|
||||
} else {
|
||||
if (this < MILE_THRESHOLD) DistanceUnit.FOOT else DistanceUnit.MILE
|
||||
}
|
||||
val valueInUnit = this * unit.multiplier
|
||||
return valueInUnit.toString(unit)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue