fix(android): latch NowPlaying drag-dismiss to prevent back-stack underflow
android / Build + lint + test (push) Failing after 1m24s
android / Build + lint + test (push) Failing after 1m24s
Operator reproduced black-screen-on-resume by drag-down dismissing the full player. Root cause: the NestedScrollConnection accumulator crossed the dismiss threshold, called navController.popBackStack(), reset accumulated to 0 — but the user's finger was still down and the pop transition was still running. The next frame's onPostScroll re-accumulated and re-fired onDismiss(), popping the screen BENEATH NowPlaying. When that left the back stack empty the NavHost had no destination to draw, producing a black window until the process was killed and the activity was cold-launched. Add a `dismissed` latch that survives until the connection is disposed (which only happens when NowPlayingScreen leaves the composition, i.e. the pop completes). After the latch sets we consume the remaining drag (return `available`) so the underlying scrollable doesn't paint over-scroll while the pop transitions. onPreFling also bails after dismissal so the fling can't restart the accumulator.
This commit is contained in:
@@ -189,16 +189,29 @@ private fun rememberDragDismissConnection(
|
||||
object : NestedScrollConnection {
|
||||
private var accumulated = 0f
|
||||
|
||||
// One-shot latch. popBackStack is async — between the first
|
||||
// dismissal and the screen actually leaving the composition,
|
||||
// the user's finger is still down and more onPostScroll
|
||||
// frames arrive. Without this guard the accumulator rebuilds
|
||||
// and onDismiss() fires a second time, popping the screen
|
||||
// BENEATH NowPlaying. If that leaves the back stack empty
|
||||
// the NavHost renders nothing → black screen on resume.
|
||||
private var dismissed = false
|
||||
|
||||
override fun onPostScroll(
|
||||
consumed: Offset,
|
||||
available: Offset,
|
||||
source: NestedScrollSource,
|
||||
): Offset {
|
||||
// After dismissal, eat all remaining drag so the
|
||||
// scrollable doesn't paint over-scroll deltas during the
|
||||
// pop transition.
|
||||
if (dismissed) return available
|
||||
if (source != NestedScrollSource.UserInput) return Offset.Zero
|
||||
return if (available.y > 0f) {
|
||||
accumulated += available.y
|
||||
if (accumulated >= thresholdPx) {
|
||||
accumulated = 0f
|
||||
dismissed = true
|
||||
onDismiss()
|
||||
}
|
||||
Offset(0f, available.y)
|
||||
@@ -209,6 +222,7 @@ private fun rememberDragDismissConnection(
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
if (dismissed) return available
|
||||
accumulated = 0f
|
||||
return Velocity.Zero
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user