mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-11 00:12:24 +01:00
Merge #63: a display density setting for car apps
# Conflicts: # gearslip/src/main/java/app/seb3thehacker/gearslip/ui/CarPreviewScreen.kt
This commit is contained in:
commit
71d8af5128
8 files changed
+145
-7
No files matched your search
@@ -310,7 +310,8 @@ class GearslipActivity : ComponentActivity(), Projection {
|
||||
val target = surface ?: return
|
||||
if (screenProjector != null) return
|
||||
val projector = ScreenProjector(this).also { screenProjector = it }
|
||||
projector.start(target, frameWidth, frameHeight, frameDensity) { CarUi() }
|
||||
// The frame's density, not frameDensity: that stays the car's own, for the phone mirror.
|
||||
projector.start(target, frameWidth, frameHeight, CarEnvironment.frame.value.densityDpi) { CarUi() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@@ -113,7 +114,11 @@ private fun LauncherScreen(onOpenGearslip: () -> Unit) {
|
||||
val (width, height) = px
|
||||
if (width <= 0 || height <= 0) return@BoxWithConstraints
|
||||
// Car-like text and buttons: 160dpi for every 480 pixels of height, as the car preview uses.
|
||||
val density = (160 * height / 480).coerceIn(120, 640)
|
||||
// This screen stands in for the car's, so hosted apps size to it and the density setting applies.
|
||||
val density = remember(width, height) {
|
||||
CarEnvironment.setDisplay(width, height, (160 * height / 480).coerceIn(120, 640))
|
||||
CarEnvironment.frame.value.densityDpi
|
||||
}
|
||||
// Before the projector starts, so the car UI's first screen already knows what to hide.
|
||||
DisposableEffect(Unit) {
|
||||
LauncherMode.setActive(true)
|
||||
|
||||
@@ -37,6 +37,9 @@ object CarEnvironment {
|
||||
private val frameFlow = MutableStateFlow(Frame(800, 480, 160))
|
||||
val frame: StateFlow<Frame> = frameFlow
|
||||
|
||||
/** The density the car reported; [frame] carries the one the car UI and its apps draw at. */
|
||||
private var carDensityDpi = 160
|
||||
|
||||
val phoneDark: StateFlow<Boolean> = phoneDarkFlow
|
||||
val darkOutside: StateFlow<Boolean> = darkOutsideFlow
|
||||
val battery: StateFlow<Battery> = batteryFlow
|
||||
@@ -95,8 +98,18 @@ object CarEnvironment {
|
||||
}
|
||||
|
||||
fun setDisplay(width: Int, height: Int, densityDpi: Int) {
|
||||
displayFlow.value = "${width}x$height @ ${densityDpi}dpi"
|
||||
frameFlow.value = Frame(width, height, densityDpi)
|
||||
carDensityDpi = densityDpi
|
||||
val dpi = CarSettings.effectiveDensity(densityDpi)
|
||||
displayFlow.value = "${width}x$height @ ${dpi}dpi"
|
||||
frameFlow.value = Frame(width, height, dpi)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes up the density setting. It's read only as a session starts, so the display and the
|
||||
* apps on it can't disagree mid-drive; a session with no car behind it, the preview, asks here.
|
||||
*/
|
||||
fun applyDensity() {
|
||||
frameFlow.value = frameFlow.value.copy(densityDpi = CarSettings.effectiveDensity(carDensityDpi))
|
||||
}
|
||||
|
||||
fun start(context: Context) {
|
||||
|
||||
@@ -53,6 +53,7 @@ enum class NavBarMode { SHOW, HIDE_NAVIGATING, HIDE }
|
||||
object CarSettings {
|
||||
|
||||
private const val KEY_SCALE = "car_scale"
|
||||
private const val KEY_DENSITY = "car_density"
|
||||
private const val KEY_OPEN = "car_open_on_connect"
|
||||
private const val KEY_NIGHT = "car_night_mode"
|
||||
private const val KEY_PIPE_AUDIO = "car_pipe_audio"
|
||||
@@ -99,6 +100,7 @@ private const val KEY_CLUSTER_VIDEO = "cluster_video_enabled"
|
||||
private lateinit var app: Context
|
||||
|
||||
private val scaleFlow = MutableStateFlow(1f)
|
||||
private val carDensityFlow = MutableStateFlow(DENSITY_FROM_CAR)
|
||||
private val openFlow = MutableStateFlow(HOME)
|
||||
private val nightFlow = MutableStateFlow(NightMode.AUTO)
|
||||
private val pipeAudioFlow = MutableStateFlow(true)
|
||||
@@ -245,6 +247,16 @@ private const val KEY_CLUSTER_VIDEO = "cluster_video_enabled"
|
||||
val moreLyrics: StateFlow<Boolean> = moreLyricsFlow
|
||||
|
||||
val scale: StateFlow<Float> = scaleFlow
|
||||
|
||||
/** [carDensity] when it leaves the choice to the car. */
|
||||
const val DENSITY_FROM_CAR = 0
|
||||
val DENSITIES = listOf(160, 180, 200, 220, 240)
|
||||
|
||||
/** An absolute DPI for the car UI and its apps; the video's resolution doesn't change with it. */
|
||||
val carDensity: StateFlow<Int> = carDensityFlow
|
||||
|
||||
/** The density to draw at, for a car or preview that would otherwise use [carDpi]. */
|
||||
fun effectiveDensity(carDpi: Int) = carDensityFlow.value.takeIf { it != DENSITY_FROM_CAR } ?: carDpi
|
||||
val openOnConnect: StateFlow<String> = openFlow
|
||||
val nightMode: StateFlow<NightMode> = nightFlow
|
||||
|
||||
@@ -336,6 +348,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
|
||||
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 }
|
||||
@@ -396,6 +409,11 @@ private const val KEY_CLUSTER_VIDEO = "cluster_video_enabled"
|
||||
AppSettings.putString(app, KEY_SCALE, value.toString())
|
||||
}
|
||||
|
||||
fun setCarDensity(dpi: Int) {
|
||||
carDensityFlow.value = dpi
|
||||
AppSettings.putString(app, KEY_DENSITY, dpi.toString())
|
||||
}
|
||||
|
||||
fun setOpenOnConnect(id: String) {
|
||||
openFlow.value = id
|
||||
AppSettings.putString(app, KEY_OPEN, id)
|
||||
|
||||
@@ -60,7 +60,7 @@ import app.seb3thehacker.gearslip.car.CarUi
|
||||
@Composable
|
||||
fun CarPreviewScreen(onBack: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val frame by CarEnvironment.frame.collectAsStateWithLifecycle()
|
||||
val frame by remember { CarEnvironment.frame.also { CarEnvironment.applyDensity() } }.collectAsStateWithLifecycle()
|
||||
// Tied to how the phone itself is held, not a button: turn it sideways and the preview goes
|
||||
// fullscreen, turn it back and the phone's own chrome (the Back/title above the frame) returns.
|
||||
val fullscreen = LocalConfiguration.current.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
@@ -291,7 +291,7 @@ private fun previewFrame(
|
||||
size: app.seb3thehacker.gearslip.car.PreviewSize,
|
||||
custom: app.seb3thehacker.gearslip.car.ScreenSize?,
|
||||
): Triple<Int, Int, Int> {
|
||||
fun carLike(w: Int, h: Int) = Triple(w, h, (160 * h / 480).coerceIn(120, 640))
|
||||
fun carLike(w: Int, h: Int) = Triple(w, h, app.seb3thehacker.gearslip.car.CarSettings.effectiveDensity((160 * h / 480).coerceIn(120, 640)))
|
||||
return when (size) {
|
||||
app.seb3thehacker.gearslip.car.PreviewSize.LAST_CAR -> Triple(lastCar.width, lastCar.height, lastCar.densityDpi)
|
||||
app.seb3thehacker.gearslip.car.PreviewSize.CUSTOM ->
|
||||
|
||||
@@ -66,6 +66,8 @@ 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))
|
||||
@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) }
|
||||
@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
|
||||
private fun darkChoices() = listOf(
|
||||
@@ -212,6 +214,7 @@ internal fun CarGroupScreen(group: CarGroup, onBack: () -> Unit) {
|
||||
@Composable
|
||||
private fun GroupRows(group: CarGroup) {
|
||||
val scale by CarSettings.scale.collectAsState()
|
||||
val density by CarSettings.carDensity.collectAsState()
|
||||
val open by CarSettings.openOnConnect.collectAsState()
|
||||
val night by CarSettings.nightMode.collectAsState()
|
||||
val units by CarSettings.units.collectAsState()
|
||||
@@ -241,6 +244,8 @@ 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))
|
||||
SettingsDivider()
|
||||
Choice(stringResource(R.string.car_app_theme), themeChoices(), theme, CarSettings::setAppTheme)
|
||||
SettingsDivider()
|
||||
if (theme != AppTheme.LIGHT) {
|
||||
@@ -358,11 +363,12 @@ private fun <T> List<Pair<T, String>>.label(value: T): String = firstOrNull { it
|
||||
|
||||
/** A setting with a few named choices: its current one shown, the rest a tap away. */
|
||||
@Composable
|
||||
private fun <T> Choice(title: String, choices: List<Pair<T, String>>, selected: T, onSelect: (T) -> Unit) {
|
||||
private fun <T> Choice(title: String, choices: List<Pair<T, String>>, selected: T, onSelect: (T) -> Unit, note: String? = null) {
|
||||
var menu by remember { mutableStateOf(false) }
|
||||
SettingsRow(
|
||||
title,
|
||||
Modifier.clickable { menu = true },
|
||||
subtitle = note,
|
||||
trailing = {
|
||||
// The menu hangs from the value it changes, at the row's end.
|
||||
Box {
|
||||
|
||||
@@ -192,6 +192,10 @@
|
||||
<string name="car_group_same">These are the same settings as Settings in the car. A change here shows there too.</string>
|
||||
<string name="car_open_on_connect">Open on connect</string>
|
||||
<string name="car_size">Size</string>
|
||||
<string name="car_density">Car display density</string>
|
||||
<string name="car_density_note">Larger text and controls, including in apps. Try 200 DPI with Size on Normal. Reconnect the car or reopen the preview to apply it. If controls clip, choose a lower DPI or From car.</string>
|
||||
<string name="choice_density_car">From car</string>
|
||||
<string name="choice_density_dpi">%1$d DPI</string>
|
||||
<string name="car_app_theme">App theme</string>
|
||||
<string name="car_dark_mode">Dark mode</string>
|
||||
<string name="car_buttons">Buttons</string>
|
||||
|
||||
Reference in new issue
Block a user