Replaced a few hardcoded string values with string resources (#2544)

This commit is contained in:
Tristan Waddington 2025-07-28 18:10:02 -07:00 committed by GitHub
parent fe97cbc0ac
commit d02909df5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 308 additions and 411 deletions

View file

@ -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),
)
}

View file

@ -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 = {})
}
}
}

View file

@ -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 = {},
)
}