ca9a1c6b0197

fix: settings deck fills down to just above the build label (v2.9.27)

The real fix for the Fold 7 'dead space below the stack + Pro card clips its copy/reveal buttons' report (v2.9.24 only removed a cap that never engaged on the Fold — wrong diagnosis, no visible change). Root cause: the deck reserved a FIXED 36dp gutter for the build label, but the label is pinned with navigationBarsPadding, whose inset varies (gesture vs three-button nav). Wherever the fixed gutter and the live inset didn't line up, a band of dead space was stranded below the fanned cards. - settingsDeckHeight now takes the nav-bar inset, measured live via WindowInsets.navigationBars at the call site, and reserves exactly that plus the label's own padding/line height and a 6dp gap — so the lowest fanned card sits cleanly above the label and the Pro card gets its full height, on any screen or nav mode - Replaced DECK_BOTTOM_GUTTER with VERSION_LABEL_FOOTPRINT + DECK_TO_VERSION_GAP - Updated SettingsStackTest to the new signature (fill-to-label, no cap, nav-inset-aware); all green

time
2026-08-01 22:11 UTC · 2 months ago
machine
koh2
parent
469044a2b623
3 files VERSION+1 −1app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+27 −13app/src/test/java/com/peachpasswords/ui/settings/SettingsStackTest.kt+11 −10
VERSION+1 −1
@@ -1 +1 @@1-2.9.261+2.9.27

view file

app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+27 −13
@@ -22,6 +22,9 @@2222 import androidx.compose.foundation.layout.Column2323 import androidx.compose.foundation.layout.Row2424 import androidx.compose.foundation.layout.Spacer25+import androidx.compose.foundation.layout.WindowInsets26+import androidx.compose.foundation.layout.asPaddingValues27+import androidx.compose.foundation.layout.navigationBars2528 import androidx.compose.foundation.layout.fillMaxSize2629 import androidx.compose.foundation.layout.fillMaxWidth2730 import androidx.compose.foundation.layout.height@@ -92,8 +95,17 @@9295 private val TAB_H = 46.dp9396 private val TAB_OVERLAP = 14.dp9497 private val TAB_CARD_H = TAB_H + 4.dp95-private val DECK_BOTTOM_GUTTER = 36.dp9698 99+/**100+ * Vertical room the build label needs at the bottom corner, excluding its101+ * navigation-bar inset (measured live, since it varies with gesture vs102+ * three-button nav): its bottom padding plus roughly one line of 9sp mono.103+ */104+private val VERSION_LABEL_FOOTPRINT = PeachSpacing.xs + 12.dp105+106+/** Clean gap left between the lowest fanned card and the build label. */107+private val DECK_TO_VERSION_GAP = 6.dp108+97109 /** Drag distance past which a stack card flips between focused and deck states. */98110 private val DRAG_THRESHOLD = 60.dp99111 @@ -129,18 +141,19 @@129141     expandedH - TAB_OVERLAP + COLLAPSED_TAB_STRIDE * distance130142 131143 /**132- * The deck fills the available screen height minus a bottom gutter on every133- * screen size; the gutter reserves room for the build label pinned to the134- * bottom corner. An earlier version capped this at a multiple of a constant135- * ported from the browser's small popup (`StackDeck.tsx`), but on tall panels136- * like the Galaxy Z Fold 7 the available height exceeded that cap — stranding137- * a band of dead space below the stack and clipping the Pro card's copy/reveal138- * buttons. Filling to the gutter is how phones already behave, so this just139- * makes tall screens consistent with them.144+ * The deck fills the available height down to just above the build label pinned145+ * to the bottom corner. [navBarBottom] is the label's navigation-bar inset,146+ * measured live by the caller (it varies with gesture vs three-button nav); here147+ * we additionally reserve the label's own padding/line height148+ * ([VERSION_LABEL_FOOTPRINT]) and a small visual gap ([DECK_TO_VERSION_GAP]) so149+ * the lowest fanned card sits cleanly above the label on every screen — phone or150+ * Fold. A previous fixed gutter ignored the nav inset, so it stranded a band of151+ * dead space below the stack (and clipped the Pro card's copy/reveal buttons)152+ * wherever the two didn't happen to line up.140153  */141-internal fun settingsDeckHeight(availableHeight: Dp): Dp {142-    val constrained = availableHeight - DECK_BOTTOM_GUTTER143-    return if (constrained < 0.dp) 0.dp else constrained154+internal fun settingsDeckHeight(availableHeight: Dp, navBarBottom: Dp): Dp {155+    val reserved = navBarBottom + VERSION_LABEL_FOOTPRINT + DECK_TO_VERSION_GAP156+    return (availableHeight - reserved).coerceAtLeast(0.dp)144157 }145158 146159 @Composable@@ -206,7 +219,8 @@206219             Spacer(Modifier.height(PeachSpacing.sm))207220 208221             BoxWithConstraints(Modifier.fillMaxSize()) {209-                val deckHeight = settingsDeckHeight(maxHeight)222+                val navBarBottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()223+                val deckHeight = settingsDeckHeight(maxHeight, navBarBottom)210224                 val focusHeight = deckHeight - 8.dp211225                 // The deckOpen "pick a card" fan used a fixed EXPANDED_H (a literal212226                 // browser-popup constant) for the active card's preview height,

view file

app/src/test/java/com/peachpasswords/ui/settings/SettingsStackTest.kt+11 −10
@@ -25,16 +25,17 @@2525         assertEquals(collapsedTabTop(5) - collapsedTabTop(4), stride)2626     }2727 28-    @Test fun settings_deck_fills_available_height_instead_of_the_browsers_small_popup_cap() {29-        // The deck used to hard-cap (a literal port of the browser's small popup,30-        // later a MAX_DECK_H*2 bound), which stranded dead space below the stack and31-        // clipped the Pro card's buttons on tall panels like the Galaxy Z Fold 7. It32-        // now fills the available height minus the bottom gutter on every screen size.33-        assertEquals(864.dp, settingsDeckHeight(900.dp))34-        assertEquals(444.dp, settingsDeckHeight(480.dp))35-        assertEquals(0.dp, settingsDeckHeight(24.dp))36-        // Taller than the old 924dp cap allowed: still fills to the gutter, no cap.37-        assertEquals(1164.dp, settingsDeckHeight(1200.dp))28+    @Test fun settings_deck_fills_down_to_just_above_the_build_label() {29+        // Reserves the label's nav-bar inset (passed in) + its padding/line height30+        // (xs 4dp + 12dp = 16dp) + a 6dp gap = navBarBottom + 22dp. No hard cap, so31+        // tall panels (Galaxy Z Fold 7) fill to the label instead of stranding dead32+        // space below the stack and clipping the Pro card's copy/reveal buttons.33+        assertEquals(878.dp, settingsDeckHeight(900.dp, 0.dp))34+        assertEquals(458.dp, settingsDeckHeight(480.dp, 0.dp))35+        assertEquals(2.dp, settingsDeckHeight(24.dp, 0.dp))36+        assertEquals(1178.dp, settingsDeckHeight(1200.dp, 0.dp))37+        // A larger nav-bar inset (three-button nav) simply reserves more at the bottom.38+        assertEquals(830.dp, settingsDeckHeight(900.dp, 48.dp))3839     }3940 4041     // ── Drag gesture resolution ───────────────────────────────────────────────

view file