mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Replaced a few hardcoded string values with string resources (#2544)
This commit is contained in:
parent
fe97cbc0ac
commit
d02909df5e
5 changed files with 308 additions and 411 deletions
|
|
@ -28,27 +28,28 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.util.DistanceUnit
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val PositionEnabled = 32
|
||||
private const val PositionDisabled = 0
|
||||
private const val POSITION_ENABLED = 32
|
||||
private const val POSITION_DISABLED = 0
|
||||
|
||||
private const val PositionPrecisionMin = 10
|
||||
private const val PositionPrecisionMax = 19
|
||||
private const val PositionPrecisionDefault = 13
|
||||
private const val POSITION_PRECISION_MIN = 10
|
||||
private const val POSITION_PRECISION_MAX = 19
|
||||
private const val POSITION_PRECISION_DEFAULT = 13
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun precisionBitsToMeters(bits: Int): Double = 23905787.925008 * 0.5.pow(bits.toDouble())
|
||||
|
||||
@Composable
|
||||
fun PositionPrecisionPreference(
|
||||
title: String,
|
||||
value: Int,
|
||||
enabled: Boolean,
|
||||
onValueChanged: (Int) -> Unit,
|
||||
|
|
@ -58,37 +59,35 @@ fun PositionPrecisionPreference(
|
|||
|
||||
Column(modifier = modifier) {
|
||||
SwitchPreference(
|
||||
title = title,
|
||||
checked = value != PositionDisabled,
|
||||
title = stringResource(R.string.position_enabled),
|
||||
checked = value != POSITION_DISABLED,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { enabled ->
|
||||
val newValue = if (enabled) PositionEnabled else PositionDisabled
|
||||
val newValue = if (enabled) POSITION_ENABLED else POSITION_DISABLED
|
||||
onValueChanged(newValue)
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
AnimatedVisibility(visible = value != PositionDisabled) {
|
||||
AnimatedVisibility(visible = value != POSITION_DISABLED) {
|
||||
SwitchPreference(
|
||||
title = "Precise location",
|
||||
checked = value == PositionEnabled,
|
||||
title = stringResource(R.string.precise_location),
|
||||
checked = value == POSITION_ENABLED,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { enabled ->
|
||||
val newValue = if (enabled) PositionEnabled else PositionPrecisionDefault
|
||||
val newValue = if (enabled) POSITION_ENABLED else POSITION_PRECISION_DEFAULT
|
||||
onValueChanged(newValue)
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
}
|
||||
AnimatedVisibility(visible = value in (PositionDisabled + 1)..<PositionEnabled) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
AnimatedVisibility(visible = value in (POSITION_DISABLED + 1)..<POSITION_ENABLED) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Slider(
|
||||
value = value.toFloat(),
|
||||
onValueChange = { onValueChanged(it.roundToInt()) },
|
||||
enabled = enabled,
|
||||
valueRange = PositionPrecisionMin.toFloat()..PositionPrecisionMax.toFloat(),
|
||||
steps = PositionPrecisionMax - PositionPrecisionMin - 1,
|
||||
valueRange = POSITION_PRECISION_MIN.toFloat()..POSITION_PRECISION_MAX.toFloat(),
|
||||
steps = POSITION_PRECISION_MAX - POSITION_PRECISION_MIN - 1,
|
||||
)
|
||||
|
||||
val precisionMeters = precisionBitsToMeters(value).toInt()
|
||||
|
|
@ -108,10 +107,9 @@ fun PositionPrecisionPreference(
|
|||
@Composable
|
||||
private fun PositionPrecisionPreferencePreview() {
|
||||
PositionPrecisionPreference(
|
||||
title = "Position enabled",
|
||||
value = PositionPrecisionDefault,
|
||||
value = POSITION_PRECISION_DEFAULT,
|
||||
enabled = true,
|
||||
onValueChanged = {},
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,34 +82,29 @@ private fun RowScope.PositionText(text: String, weight: Float) {
|
|||
)
|
||||
}
|
||||
|
||||
private const val Weight10 = .10f
|
||||
private const val Weight15 = .15f
|
||||
private const val Weight20 = .20f
|
||||
private const val Weight40 = .40f
|
||||
private const val WEIGHT_10 = .10f
|
||||
private const val WEIGHT_15 = .15f
|
||||
private const val WEIGHT_20 = .20f
|
||||
private const val WEIGHT_40 = .40f
|
||||
|
||||
@Composable
|
||||
private fun HeaderItem(compactWidth: Boolean) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
PositionText(stringResource(R.string.latitude), Weight20)
|
||||
PositionText(stringResource(R.string.longitude), Weight20)
|
||||
PositionText(stringResource(R.string.sats), Weight10)
|
||||
PositionText(stringResource(R.string.alt), Weight15)
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
PositionText(stringResource(R.string.latitude), WEIGHT_20)
|
||||
PositionText(stringResource(R.string.longitude), WEIGHT_20)
|
||||
PositionText(stringResource(R.string.sats), WEIGHT_10)
|
||||
PositionText(stringResource(R.string.alt), WEIGHT_15)
|
||||
if (!compactWidth) {
|
||||
PositionText("Speed", Weight15)
|
||||
PositionText(stringResource(R.string.heading), Weight15)
|
||||
PositionText(stringResource(R.string.speed), WEIGHT_15)
|
||||
PositionText(stringResource(R.string.heading), WEIGHT_15)
|
||||
}
|
||||
PositionText(stringResource(R.string.timestamp), Weight40)
|
||||
PositionText(stringResource(R.string.timestamp), WEIGHT_40)
|
||||
}
|
||||
}
|
||||
|
||||
private const val DegD = 1e-7
|
||||
private const val HeadingDeg = 1e-5
|
||||
private const val SecondsToMillis = 1000L
|
||||
private const val DEG_D = 1e-7
|
||||
private const val HEADING_DEG = 1e-5
|
||||
private const val SECONDS_TO_MILLIS = 1000L
|
||||
|
||||
@Composable
|
||||
private fun PositionItem(
|
||||
|
|
@ -119,36 +114,32 @@ private fun PositionItem(
|
|||
system: DisplayUnits,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
PositionText("%.5f".format(position.latitudeI * DegD), Weight20)
|
||||
PositionText("%.5f".format(position.longitudeI * DegD), Weight20)
|
||||
PositionText(position.satsInView.toString(), Weight10)
|
||||
PositionText(position.altitude.metersIn(system).toString(system), Weight15)
|
||||
PositionText("%.5f".format(position.latitudeI * DEG_D), WEIGHT_20)
|
||||
PositionText("%.5f".format(position.longitudeI * DEG_D), WEIGHT_20)
|
||||
PositionText(position.satsInView.toString(), WEIGHT_10)
|
||||
PositionText(position.altitude.metersIn(system).toString(system), WEIGHT_15)
|
||||
if (!compactWidth) {
|
||||
PositionText("${position.groundSpeed} Km/h", Weight15)
|
||||
PositionText("%.0f°".format(position.groundTrack * HeadingDeg), Weight15)
|
||||
PositionText("${position.groundSpeed} Km/h", WEIGHT_15)
|
||||
PositionText("%.0f°".format(position.groundTrack * HEADING_DEG), WEIGHT_15)
|
||||
}
|
||||
PositionText(formatPositionTime(position, dateFormat), Weight40)
|
||||
PositionText(formatPositionTime(position, dateFormat), WEIGHT_40)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun formatPositionTime(
|
||||
position: MeshProtos.Position,
|
||||
dateFormat: DateFormat
|
||||
): String {
|
||||
private fun formatPositionTime(position: MeshProtos.Position, dateFormat: DateFormat): String {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
val sixMonthsAgo = currentTime - 180.days.inWholeMilliseconds
|
||||
val isOlderThanSixMonths = position.time * SecondsToMillis < sixMonthsAgo
|
||||
val timeText = if (isOlderThanSixMonths) {
|
||||
stringResource(id = R.string.unknown_age)
|
||||
} else {
|
||||
dateFormat.format(position.time * SecondsToMillis)
|
||||
}
|
||||
val isOlderThanSixMonths = position.time * SECONDS_TO_MILLIS < sixMonthsAgo
|
||||
val timeText =
|
||||
if (isOlderThanSixMonths) {
|
||||
stringResource(id = R.string.unknown_age)
|
||||
} else {
|
||||
dateFormat.format(position.time * SECONDS_TO_MILLIS)
|
||||
}
|
||||
return timeText
|
||||
}
|
||||
|
||||
|
|
@ -161,9 +152,7 @@ private fun ActionButtons(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
modifier = modifier.fillMaxWidth().padding(horizontal = 24.dp, vertical = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
|
|
@ -171,63 +160,43 @@ private fun ActionButtons(
|
|||
modifier = Modifier.weight(1f),
|
||||
onClick = onClear,
|
||||
enabled = clearButtonEnabled,
|
||||
colors = ButtonDefaults.outlinedButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
colors = ButtonDefaults.outlinedButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(id = R.string.clear),
|
||||
)
|
||||
Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(id = R.string.clear))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.clear),
|
||||
)
|
||||
Text(text = stringResource(id = R.string.clear))
|
||||
}
|
||||
|
||||
OutlinedButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = onSave,
|
||||
enabled = saveButtonEnabled,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Save,
|
||||
contentDescription = stringResource(id = R.string.save),
|
||||
)
|
||||
OutlinedButton(modifier = Modifier.weight(1f), onClick = onSave, enabled = saveButtonEnabled) {
|
||||
Icon(imageVector = Icons.Default.Save, contentDescription = stringResource(id = R.string.save))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.save),
|
||||
)
|
||||
Text(text = stringResource(id = R.string.save))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PositionLogScreen(
|
||||
viewModel: MetricsViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun PositionLogScreen(viewModel: MetricsViewModel = hiltViewModel()) {
|
||||
val state by viewModel.state.collectAsStateWithLifecycle()
|
||||
|
||||
val exportPositionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> viewModel.savePositionCSV(uri) }
|
||||
val exportPositionLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> viewModel.savePositionCSV(uri) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var clearButtonEnabled by rememberSaveable(state.positionLogs) {
|
||||
mutableStateOf(state.positionLogs.isNotEmpty())
|
||||
}
|
||||
var clearButtonEnabled by rememberSaveable(state.positionLogs) { mutableStateOf(state.positionLogs.isNotEmpty()) }
|
||||
|
||||
BoxWithConstraints {
|
||||
val compactWidth = maxWidth < 600.dp
|
||||
Column {
|
||||
val textStyle = if (compactWidth) {
|
||||
MaterialTheme.typography.bodySmall
|
||||
} else {
|
||||
LocalTextStyle.current
|
||||
}
|
||||
val textStyle =
|
||||
if (compactWidth) {
|
||||
MaterialTheme.typography.bodySmall
|
||||
} else {
|
||||
LocalTextStyle.current
|
||||
}
|
||||
CompositionLocalProvider(LocalTextStyle provides textStyle) {
|
||||
HeaderItem(compactWidth)
|
||||
PositionList(compactWidth, state.positionLogs, state.displayUnits)
|
||||
|
|
@ -241,11 +210,12 @@ fun PositionLogScreen(
|
|||
},
|
||||
saveButtonEnabled = state.hasPositionLogs(),
|
||||
onSave = {
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
putExtra(Intent.EXTRA_TITLE, "position.csv")
|
||||
}
|
||||
val intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
putExtra(Intent.EXTRA_TITLE, "position.csv")
|
||||
}
|
||||
exportPositionLauncher.launch(intent)
|
||||
},
|
||||
)
|
||||
|
|
@ -259,28 +229,24 @@ private fun ColumnScope.PositionList(
|
|||
positions: List<MeshProtos.Position>,
|
||||
displayUnits: DisplayUnits,
|
||||
) {
|
||||
val dateFormat = remember {
|
||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)
|
||||
}
|
||||
val dateFormat = remember { DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
items(positions) { position ->
|
||||
PositionItem(compactWidth, position, dateFormat, displayUnits)
|
||||
}
|
||||
LazyColumn(modifier = Modifier.weight(1f), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
items(positions) { position -> PositionItem(compactWidth, position, dateFormat, displayUnits) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private val testPosition = MeshProtos.Position.newBuilder().apply {
|
||||
latitudeI = 297604270
|
||||
longitudeI = -953698040
|
||||
altitude = 1230
|
||||
satsInView = 7
|
||||
time = (System.currentTimeMillis() / 1000).toInt()
|
||||
}.build()
|
||||
private val testPosition =
|
||||
MeshProtos.Position.newBuilder()
|
||||
.apply {
|
||||
latitudeI = 297604270
|
||||
longitudeI = -953698040
|
||||
altitude = 1230
|
||||
satsInView = 7
|
||||
time = (System.currentTimeMillis() / 1000).toInt()
|
||||
}
|
||||
.build()
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
|
|
@ -300,12 +266,7 @@ private fun PositionItemPreview() {
|
|||
private fun ActionButtonsPreview() {
|
||||
AppTheme {
|
||||
Column(Modifier.fillMaxSize(), Arrangement.Bottom) {
|
||||
ActionButtons(
|
||||
clearButtonEnabled = true,
|
||||
onClear = {},
|
||||
saveButtonEnabled = true,
|
||||
onSave = {},
|
||||
)
|
||||
ActionButtons(clearButtonEnabled = true, onClear = {}, saveButtonEnabled = true, onSave = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,15 +77,15 @@ fun EditChannelDialog(
|
|||
maxSize = 11, // name max_size:12
|
||||
enabled = true,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
channelInput = channelInput.copy {
|
||||
name = it.trim()
|
||||
if (psk == Channel.default.settings.psk) psk = Channel.getRandomKey()
|
||||
}
|
||||
channelInput =
|
||||
channelInput.copy {
|
||||
name = it.trim()
|
||||
if (psk == Channel.default.settings.psk) psk = Channel.getRandomKey()
|
||||
}
|
||||
},
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
)
|
||||
|
|
@ -101,33 +101,26 @@ fun EditChannelDialog(
|
|||
channelInput = channelInput.copy { psk = it }
|
||||
}
|
||||
},
|
||||
onGenerateKey = {
|
||||
channelInput = channelInput.copy { psk = Channel.getRandomKey() }
|
||||
},
|
||||
onGenerateKey = { channelInput = channelInput.copy { psk = Channel.getRandomKey() } },
|
||||
)
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.uplink_enabled),
|
||||
checked = channelInput.uplinkEnabled,
|
||||
enabled = true,
|
||||
onCheckedChange = {
|
||||
channelInput = channelInput.copy { uplinkEnabled = it }
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
onCheckedChange = { channelInput = channelInput.copy { uplinkEnabled = it } },
|
||||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.downlink_enabled),
|
||||
checked = channelInput.downlinkEnabled,
|
||||
enabled = true,
|
||||
onCheckedChange = {
|
||||
channelInput = channelInput.copy { downlinkEnabled = it }
|
||||
},
|
||||
padding = PaddingValues(0.dp)
|
||||
onCheckedChange = { channelInput = channelInput.copy { downlinkEnabled = it } },
|
||||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
|
||||
PositionPrecisionPreference(
|
||||
title = stringResource(R.string.position_enabled),
|
||||
enabled = true,
|
||||
value = channelInput.moduleSettings.positionPrecision,
|
||||
onValueChanged = {
|
||||
|
|
@ -139,24 +132,17 @@ fun EditChannelDialog(
|
|||
},
|
||||
confirmButton = {
|
||||
FlowRow(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
modifier = modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
TextButton(
|
||||
modifier = modifier.weight(1f),
|
||||
onClick = onDismissRequest
|
||||
) { Text(stringResource(R.string.cancel)) }
|
||||
Button(
|
||||
modifier = modifier.weight(1f),
|
||||
onClick = {
|
||||
onAddClick(channelInput)
|
||||
},
|
||||
enabled = true,
|
||||
) { Text(stringResource(R.string.save)) }
|
||||
TextButton(modifier = modifier.weight(1f), onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
Button(modifier = modifier.weight(1f), onClick = { onAddClick(channelInput) }, enabled = true) {
|
||||
Text(stringResource(R.string.save))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -164,11 +150,12 @@ fun EditChannelDialog(
|
|||
@Composable
|
||||
private fun EditChannelDialogPreview() {
|
||||
EditChannelDialog(
|
||||
channelSettings = channelSettings {
|
||||
channelSettings =
|
||||
channelSettings {
|
||||
psk = Channel.default.settings.psk
|
||||
name = Channel.default.name
|
||||
},
|
||||
onAddClick = { },
|
||||
onDismissRequest = { },
|
||||
onAddClick = {},
|
||||
onDismissRequest = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue