8af15cf08c20

keep the wide-screen scale in one composition slot (v2.14.78)

WideScreenScale branched between a bare content() and a CompositionLocalProvider-wrapped one depending on whether the scale was 1f. Crossing that boundary changes the composition's structure, so everything inside is disposed and rebuilt — including PeachAppNavHost and its rememberNavController() — dropping the user back on the lock screen to authenticate a second time. VaultRepository's settings flow starts at the default VaultSettings() with uiScale "auto" and only later carries the persisted value, so the scale resolves twice. That is why the bug tracks the Large preset exactly: on a tablet-like window "auto" yields 1f and "large" yields 1.35f, so the persisted value flips the branch. On a phone-width window both presets land on 1f, which is why the cover screen was never affected, and "auto" on the same wide window resolves to 1f both times. The density is now always provided through the same slot, held in a remember so a recomposition with unchanged inputs hands back the same instance. Behaviour at 1f is unchanged: base.density * 1f is the system density. Verification: new MainActivityTest case pins that every preset agrees on a phone-width window; the existing case already pins the 1f-versus-1.35f split on a tablet-like one. Full JVM suite 1016 tests / 0 failures / 0 errors / 2 skipped; lintDebug clean; assembleDebug and debug androidTest sources compile. On-device confirmation is still pending — the phone is at its secure lock screen, so the reproduce loop could not be run.

time
2026-08-31 16:42 UTC · 1 month ago
machine
koh2
agent
hy4-preview
session
unfolded double lock screen
parent
77ad8cf7bf55
3 files VERSION+1 −1app/src/main/java/com/peachpasswords/MainActivity.kt+11 −8app/src/test/java/com/peachpasswords/MainActivityTest.kt+15 −0
VERSION+1 −1
@@ -1 +1 @@1-2.14.771+2.14.78

view file

app/src/main/java/com/peachpasswords/MainActivity.kt+11 −8
@@ -14,6 +14,7 @@1414 import androidx.compose.ui.Modifier1515 import androidx.compose.runtime.CompositionLocalProvider1616 import androidx.compose.runtime.Composable17+import androidx.compose.runtime.remember1718 import androidx.compose.foundation.layout.fillMaxSize1819 import androidx.compose.foundation.layout.BoxWithConstraints1920 import androidx.compose.runtime.collectAsState@@ -203,14 +204,16 @@203204     BoxWithConstraints(Modifier.fillMaxSize()) {204205         val base = LocalDensity.current205206         val scale = wideScreenScaleFor(scalePreference, maxWidth, maxHeight)206-        if (scale == 1f) {207-            content()208-        } else {209-            CompositionLocalProvider(210-                LocalDensity provides Density(base.density * scale, base.fontScale),211-                content = content,212-            )213-        }207+        // Always go through the same slot. Branching between a bare content()208+        // and a wrapped one changes the composition's structure whenever the209+        // scale crosses 1f, which disposes and rebuilds everything inside —210+        // including the nav host and its remembered controller. The settings211+        // flow starts at the default "auto" and only later carries the212+        // persisted value, so a device set to "large" on a tablet-like screen213+        // resolves 1f first and 1.35f a moment later, dropping the user back214+        // on the lock screen to authenticate a second time.215+        val density = remember(base, scale) { Density(base.density * scale, base.fontScale) }216+        CompositionLocalProvider(LocalDensity provides density, content = content)214217     }215218 }216219 

view file

app/src/test/java/com/peachpasswords/MainActivityTest.kt+15 −0
@@ -20,4 +20,19 @@2020         assertEquals(1f, wideScreenScaleFor("compact", 673.dp, 841.dp), 0f)2121         assertEquals(1.35f, wideScreenScaleFor("large", 800.dp, 1280.dp), 0f)2222     }23+24+    /**25+     * On a phone-width window every preset lands on the same scale, so the26+     * persisted preference arriving after the default cannot change it. Only a27+     * tablet-like window makes "auto" and "large" disagree, which is what let28+     * the re-scale tear down the nav host on a foldable's inner screen.29+     */30+    @Test fun every_preset_agrees_on_a_phone_screen() {31+        assertEquals(32+            wideScreenScaleFor("auto", 385.dp, 914.dp),33+            wideScreenScaleFor("large", 385.dp, 914.dp),34+            0f,35+        )36+        assertEquals(1f, wideScreenScaleFor("large", 385.dp, 914.dp), 0f)37+    }2338 }

view file