8c13ab49ceaa
feat: add adaptive Appearance scaling controls (v2.14.54)
- time
- 2026-08-15 00:23 UTC · 2 months ago
- machine
- koh2
- parent
- 9ac9634b621e
↓ peach-android-8c13ab49.tar.gzatlas at this save
files changed6 files
VERSION+1 −1app/src/main/java/com/peachpasswords/MainActivity.kt+24 −5app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+10 −0app/src/main/java/com/peachpasswords/ui/settings/SettingsViewModel.kt+1 −0app/src/main/java/com/peachpasswords/vault/VaultSettings.kt+2 −0app/src/test/java/com/peachpasswords/MainActivityTest.kt+23 −0app/src/main/java/com/peachpasswords/MainActivity.kt+24 −5
@@ -9,6 +9,7 @@99 import androidx.compose.runtime.SideEffect1010 import androidx.compose.ui.unit.dp1111 import androidx.compose.ui.unit.Density12+import androidx.compose.ui.unit.Dp1213 import androidx.compose.ui.platform.LocalDensity1314 import androidx.compose.ui.Modifier1415 import androidx.compose.runtime.CompositionLocalProvider@@ -128,7 +129,7 @@128129 reduceMotion = settings.reduceMotion,129130 textSize = settings.textSize,130131 ) {131- WideScreenScale {132+ WideScreenScale(scalePreference = settings.uiScale) {132133 PeachAppNavHost(startDestination = start, isUnlocked = vault.isUnlocked, uiGateLocked = vault.uiGateLocked)133134 }134135 }@@ -181,15 +182,18 @@181182 * grows every dp and sp together, so the same single-column layout simply reads182183 * as a bigger screen — no per-screen breakpoints to keep in sync.183184 *184- * Ceiling: a uniform scale, not real adaptive layout. If a screen ever wants a185- * genuinely different wide-screen arrangement it needs its own breakpoint.185+ * Auto mode keeps the existing foldable treatment but leaves tablet-like186+ * windows at phone density; Compact and Large are explicit overrides.186187 * No-op at or below [BASE_WIDTH], so phones are untouched.187188 */188189 @Composable189-private fun WideScreenScale(content: @Composable () -> Unit) {190+private fun WideScreenScale(191+ scalePreference: String = "auto",192+ content: @Composable () -> Unit,193+) {190194 BoxWithConstraints(Modifier.fillMaxSize()) {191195 val base = LocalDensity.current192- val scale = (maxWidth / BASE_WIDTH).coerceIn(1f, MAX_SCALE)196+ val scale = wideScreenScaleFor(scalePreference, maxWidth, maxHeight)193197 if (scale == 1f) {194198 content()195199 } else {@@ -199,4 +203,19 @@199203 )200204 }201205 }206+}207+208+internal fun wideScreenScaleFor(preference: String, width: Dp, height: Dp): Float {209+ val phoneScale = (width / BASE_WIDTH).coerceIn(1f, MAX_SCALE)210+ return when (preference) {211+ "compact" -> 1f212+ "large" -> phoneScale213+ else -> if (isTabletLike(width, height)) 1f else phoneScale214+ }215+}216+217+private fun isTabletLike(width: Dp, height: Dp): Boolean {218+ val shortest = minOf(width.value, height.value)219+ val longest = maxOf(width.value, height.value)220+ return shortest >= 720f || (shortest >= 600f && longest / shortest >= 1.45f)202221 }app/src/main/java/com/peachpasswords/ui/settings/SettingsCardBodies.kt+10 −0
@@ -331,6 +331,16 @@331331 Pill("large", s.textSize == "large", fg) { vm.setTextSize("large") }332332 }333333 }334+ SubLabel("UI SCALE", fg)335+ RowLine(fg) {336+ Name("Large-screen sizing", fg)337+ Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {338+ Pill("compact", s.uiScale == "compact", fg) { vm.setUiScale("compact") }339+ Pill("auto", s.uiScale == "auto", fg) { vm.setUiScale("auto") }340+ Pill("large", s.uiScale == "large", fg) { vm.setUiScale("large") }341+ }342+ }343+ Hint("Auto keeps foldables roomy while preventing tablet controls from inflating.", fg)334344 SubLabel("ICONS", fg)335345 RowLine(fg) {336346 Name("Site icons", fg)app/src/main/java/com/peachpasswords/ui/settings/SettingsViewModel.kt+1 −0
@@ -285,6 +285,7 @@285285 fun setAccent(name: String) = mutate { it.copy(accentColor = name) }286286 fun setReduceMotion(on: Boolean) = mutate { it.copy(reduceMotion = on) }287287 fun setTextSize(size: String) = mutate { it.copy(textSize = size) }288+ fun setUiScale(scale: String) = mutate { it.copy(uiScale = scale) }288289 fun setFaviconsEnabled(on: Boolean) = mutate { it.copy(faviconsEnabled = on) }289290 fun setBreachMonitoring(on: Boolean) = mutate { it.copy(breachMonitoring = on) }290291 fun setBiometrics(on: Boolean) = mutate { it.copy(biometricsEnabled = on) }app/src/main/java/com/peachpasswords/vault/VaultSettings.kt+2 −0
@@ -10,6 +10,8 @@1010 val reduceMotion: Boolean = false,1111 /** Browser-parity text size: "small" | "medium" | "large". */1212 val textSize: String = "medium",13+ /** Large-screen layout sizing: "compact" | "auto" | "large". */14+ val uiScale: String = "auto",1315 /** U1 site icons in lists; off (default) keeps the cleaner initials-only look. */1416 val faviconsEnabled: Boolean = false,1517 val biometricsEnabled: Boolean = false,app/src/test/java/com/peachpasswords/MainActivityTest.kt+23 −0
@@ -0,0 +1,23 @@1+package com.peachpasswords2+3+import androidx.compose.ui.unit.dp4+import org.junit.Assert.assertEquals5+import org.junit.Test6+7+class MainActivityTest {8+9+ @Test fun auto_scaling_keeps_phone_and_foldable_behavior() {10+ assertEquals(1f, wideScreenScaleFor("auto", 411.dp, 800.dp), 0f)11+ assertEquals(1.35f, wideScreenScaleFor("auto", 673.dp, 841.dp), 0f)12+ }13+14+ @Test fun auto_scaling_does_not_inflate_tablet_controls() {15+ assertEquals(1f, wideScreenScaleFor("auto", 800.dp, 1280.dp), 0f)16+ assertEquals(1f, wideScreenScaleFor("auto", 600.dp, 960.dp), 0f)17+ }18+19+ @Test fun scale_presets_override_auto_layout_choice() {20+ assertEquals(1f, wideScreenScaleFor("compact", 673.dp, 841.dp), 0f)21+ assertEquals(1.35f, wideScreenScaleFor("large", 800.dp, 1280.dp), 0f)22+ }23+}