Car display density takes any value from 60 to 480 DPI, typed in under Custom

This commit is contained in:
Seb3thehacker committed 2026-11-06 13:30:29 -08:00
1 parent bb7f2b93ea
commit f139ac48c4
20 files changed
+129 -3

No files matched your search

@@ -249,6 +249,8 @@ private const val KEY_CLUSTER_VIDEO = "cluster_video_enabled"
/** [carDensity] when it leaves the choice to the car. */
const val DENSITY_FROM_CAR = 0
val DENSITIES = listOf(160, 180, 200, 220, 240)
/** The range a typed-in density may take (issue #82): small enough for more on screen, big enough to read. */
val CUSTOM_DENSITIES = 60..480
/** An absolute DPI for the car UI and its apps; the video's resolution doesn't change with it. */
val carDensity: StateFlow<Int> = carDensityFlow
@@ -381,7 +383,7 @@ private const val KEY_CLUSTER_VIDEO = "cluster_video_enabled"
fun init(context: Context) {
app = context.applicationContext
scaleFlow.value = AppSettings.getString(app, KEY_SCALE, "1.0").toFloatOrNull() ?: 1f
carDensityFlow.value = AppSettings.getString(app, KEY_DENSITY, "").toIntOrNull()?.takeIf { it in DENSITIES } ?: DENSITY_FROM_CAR
carDensityFlow.value = AppSettings.getString(app, KEY_DENSITY, "").toIntOrNull()?.takeIf { it in DENSITIES || it in CUSTOM_DENSITIES } ?: DENSITY_FROM_CAR
openFlow.value = AppSettings.getString(app, KEY_OPEN, HOME)
lastNavFlow.value = AppSettings.getString(app, KEY_LAST_NAV, "").ifEmpty { null }
lastMediaFlow.value = AppSettings.getString(app, KEY_LAST_MEDIA, "").ifEmpty { null }
@@ -21,6 +21,11 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Checkbox
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.TextButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
@@ -65,8 +70,44 @@ enum class CarGroup(@param:StringRes val title: Int) {
@Composable
private fun sizeChoices() = listOf(1f to stringResource(R.string.choice_size_normal), 1.25f to stringResource(R.string.choice_size_large), 1.5f to stringResource(R.string.choice_size_xlarge))
/** Marks the menu's Custom entry, which opens [CustomDensityDialog] instead of choosing. */
private const val DENSITY_CUSTOM = -1
/** The presets, a typed-in value when that's what's set, and Custom to type one. */
@Composable
private fun densityChoices() = listOf(CarSettings.DENSITY_FROM_CAR to stringResource(R.string.choice_density_car)) + CarSettings.DENSITIES.map { it to stringResource(R.string.choice_density_dpi, it) }
private fun densityChoices(current: Int): List<Pair<Int, String>> {
val values = CarSettings.DENSITIES.let { if (current != CarSettings.DENSITY_FROM_CAR && current !in it) (it + current).sorted() else it }
return listOf(CarSettings.DENSITY_FROM_CAR to stringResource(R.string.choice_density_car)) +
values.map { it to stringResource(R.string.choice_density_dpi, it) } +
(DENSITY_CUSTOM to stringResource(R.string.choice_density_custom))
}
/** Types in any density in [CarSettings.CUSTOM_DENSITIES] (issue #82). */
@Composable
private fun CustomDensityDialog(current: Int, onDismiss: () -> Unit, onSave: (Int) -> Unit) {
var text by remember { mutableStateOf(current.takeIf { it != CarSettings.DENSITY_FROM_CAR }?.toString().orEmpty()) }
val value = text.toIntOrNull()?.takeIf { it in CarSettings.CUSTOM_DENSITIES }
val range = CarSettings.CUSTOM_DENSITIES
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.density_custom_title)) },
text = {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(stringResource(R.string.density_custom_note, range.first, range.last))
OutlinedTextField(
text, { text = it.filter(Char::isDigit).take(3) },
label = { Text(stringResource(R.string.density_custom_label)) },
singleLine = true,
isError = text.isNotEmpty() && value == null,
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)
}
},
confirmButton = { TextButton(onClick = { value?.let(onSave) }, enabled = value != null) { Text(stringResource(R.string.save)) } },
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
)
}
@Composable
private fun themeChoices() = listOf(AppTheme.PHONE to stringResource(R.string.choice_theme_phone), AppTheme.LIGHT to stringResource(R.string.choice_theme_light), AppTheme.DARK to stringResource(R.string.choice_theme_dark))
@Composable
@@ -243,7 +284,18 @@ private fun GroupRows(group: CarGroup) {
SettingsDivider()
Choice(stringResource(R.string.car_size), sizeChoices(), scale, CarSettings::setScale)
SettingsDivider()
Choice(stringResource(R.string.car_density), densityChoices(), density, CarSettings::setCarDensity, note = stringResource(R.string.car_density_note))
var customDensity by remember { mutableStateOf(false) }
Choice(
stringResource(R.string.car_density), densityChoices(density), density,
{ if (it == DENSITY_CUSTOM) customDensity = true else CarSettings.setCarDensity(it) },
note = stringResource(R.string.car_density_note),
)
if (customDensity) {
CustomDensityDialog(density, onDismiss = { customDensity = false }) {
CarSettings.setCarDensity(it)
customDensity = false
}
}
SettingsDivider()
Choice(stringResource(R.string.car_app_theme), themeChoices(), theme, CarSettings::setAppTheme)
SettingsDivider()