55352aec7f22

fix settings card two-way scroll gesture thrashing and bare launcher icon - expanding the nested scroll connection to also handle collapsed-card-scroll-expands (per direct spec: any scroll on a collapsed card expands it) caused a real regression: collapsing a card flips focused=false mid-drag, which for the same still-moving finger immediately satisfied the expand branch, thrashing collapse/expand back and forth every ~15ms (confirmed via temporary logcat instrumentation). Fixed with a latch that allows only one action per physical touch, unlatching on onPreFling (finger lift) rather than on state change; also moved focused/active off the remember() key list and onto rememberUpdatedState so the connection object (and its latch) survives the very recomposition its own actions trigger. Separately, ic_launcher_background.xml was a solid cream rectangle (#FFF4E9D2) that adaptive-icon masking renders as a visible colored circle behind the peach mark on the home screen and app drawer - made transparent for a bare-icon look, confirmed on-device in the app drawer dock.

time
2026-07-24 12:34 UTC · 3 months ago
machine
koh2
agent
claude-sonnet-5
session
critical-corrections-c1-c7
parent
a4ab61925a3e
2 files app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+51 −8app/src/main/res/drawable/ic_launcher_background.xml+1 −1
app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+51 −8
@@ -33,12 +33,14 @@3333 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection3434 import androidx.compose.ui.input.nestedscroll.NestedScrollSource3535 import androidx.compose.ui.input.nestedscroll.nestedScroll36+import androidx.compose.ui.unit.Velocity3637 import androidx.compose.runtime.Composable3738 import androidx.compose.runtime.LaunchedEffect3839 import androidx.compose.runtime.getValue3940 import androidx.compose.runtime.mutableStateOf4041 import androidx.compose.runtime.remember4142 import androidx.compose.runtime.rememberCoroutineScope43+import androidx.compose.runtime.rememberUpdatedState4244 import androidx.compose.runtime.setValue4345 import androidx.compose.runtime.collectAsState4446 import androidx.compose.ui.Alignment@@ -327,6 +329,7 @@327329                                 }328330                             },329331                             onCollapseRequest = { deckOpen = true },332+                            onExpandRequest = { active = card.id; deckOpen = false },330333                             enabled = !concealed,331334                         )332335                     }@@ -352,6 +355,7 @@352355     modifier: Modifier,353356     onClick: () -> Unit,354357     onCollapseRequest: () -> Unit,358+    onExpandRequest: () -> Unit,355359     enabled: Boolean,356360 ) {357361     val interaction = remember { MutableInteractionSource() }@@ -411,31 +415,70 @@411415                 )412416                 // Body scrolls independently; only revealed at full height in413417                 // focus mode, but kept mounted so deck-mode shows a preview.414-                // Collapse-by-scroll: once the body is already at its scroll415-                // top, a further downward drag has nowhere to go inside the416-                // scrollable — hand that leftover delta to the deck instead417-                // of letting it disappear as a no-op overscroll.418+                // Two-way scroll gesture, mirrored: scrolling a collapsed-to-419+                // preview card at all expands it straight away (a cramped420+                // preview has nothing worth scrolling, so intercept in421+                // onPreScroll before its own verticalScroll ever sees the422+                // drag); scrolling a focused card past its own top hands the423+                // leftover delta to the deck to collapse instead of letting424+                // it disappear as a no-op overscroll.418425                 val scrollState = rememberScrollState()419426                 val density = LocalDensity.current420-                val collapseThresholdPx = with(density) { DRAG_THRESHOLD.toPx() }421-                val nestedScrollConnection = remember(scrollState, focused) {427+                val gestureThresholdPx = with(density) { DRAG_THRESHOLD.toPx() }428+                // Read the latest focused/active through State rather than as429+                // remember() keys: firing onCollapseRequest/onExpandRequest changes430+                // `focused` mid-gesture, and recreating the connection object right431+                // then would reset `latched` and immediately re-trigger the other432+                // branch for the same still-moving finger.433+                val latestFocused = rememberUpdatedState(focused)434+                val latestActive = rememberUpdatedState(active)435+                val nestedScrollConnection = remember(scrollState) {422436                     object : NestedScrollConnection {437+                        var expandAccum = 0f423438                         var collapseAccum = 0f439+                        // A collapse flips `focused` to false mid-gesture, which would440+                        // otherwise immediately satisfy the expand branch below for the441+                        // *same* still-moving finger (and vice versa), thrashing back442+                        // and forth. Latch to one action per physical touch; unlatch443+                        // only once the finger lifts (onPreFling fires exactly once444+                        // per released gesture, fling or not).445+                        var latched = false446+                        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {447+                            val focused = latestFocused.value448+                            val active = latestActive.value449+                            if (latched || !active || focused || source != NestedScrollSource.Drag || available.y == 0f) {450+                                expandAccum = 0f451+                                return Offset.Zero452+                            }453+                            expandAccum += available.y454+                            if (kotlin.math.abs(expandAccum) >= gestureThresholdPx) {455+                                expandAccum = 0f456+                                latched = true457+                                onExpandRequest()458+                            }459+                            return available460+                        }424461                         override fun onPostScroll(425462                             consumed: Offset,426463                             available: Offset,427464                             source: NestedScrollSource,428465                         ): Offset {429-                            if (!focused || source != NestedScrollSource.Drag || scrollState.value != 0 || available.y <= 0f) {466+                            val focused = latestFocused.value467+                            if (latched || !focused || source != NestedScrollSource.Drag || scrollState.value != 0 || available.y <= 0f) {430468                                 collapseAccum = 0f431469                                 return Offset.Zero432470                             }433471                             collapseAccum += available.y434-                            if (collapseAccum >= collapseThresholdPx) {472+                            if (collapseAccum >= gestureThresholdPx) {435473                                 collapseAccum = 0f474+                                latched = true436475                                 onCollapseRequest()437476                             }438477                             return Offset(0f, available.y)478+                        }479+                        override suspend fun onPreFling(available: Velocity): Velocity {480+                            latched = false481+                            return Velocity.Zero439482                         }440483                     }441484                 }

view file

app/src/main/res/drawable/ic_launcher_background.xml+1 −1
@@ -1,4 +1,4 @@11 <?xml version="1.0" encoding="utf-8"?>22 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">3-    <solid android:color="#FFF4E9D2" />3+    <solid android:color="@android:color/transparent" />44 </shape>

view file