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
↓ peach-android-55352aec.tar.gzatlas at this save
files changed2 files
app/src/main/java/com/peachpasswords/ui/settings/SettingsScreen.kt+51 −8app/src/main/res/drawable/ic_launcher_background.xml+1 −1app/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 }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>