summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt28
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt21
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt28
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt1
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt21
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt173
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt1
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt1
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt65
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt11
10 files changed, 132 insertions, 218 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
index e4c60e166fd5..8b9e9274b448 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt
@@ -18,8 +18,6 @@ package com.android.systemui.notifications.ui.composable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.util.fastCoerceAtLeast
-import androidx.compose.ui.util.fastCoerceAtMost
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
/**
@@ -46,7 +44,7 @@ fun NotificationScrimNestedScrollConnection(
orientation = Orientation.Vertical,
// scrolling up and inner content is taller than the scrim, so scrim needs to
// expand; content can scroll once scrim is at the minScrimOffset.
- canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
offsetAvailable < 0 &&
offsetBeforeStart == 0f &&
contentHeight() > minVisibleScrimHeight() &&
@@ -54,38 +52,36 @@ fun NotificationScrimNestedScrollConnection(
},
// scrolling down and content is done scrolling to top. After that, the scrim
// needs to collapse; collapse the scrim until it is at the maxScrimOffset.
- canStartPostScroll = { offsetAvailable, _, _ ->
+ canStartPostScroll = { offsetAvailable, _ ->
offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
},
canStartPostFling = { false },
- canStopOnPreFling = { false },
+ canContinueScroll = {
+ val currentHeight = scrimOffset()
+ minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
+ },
+ canScrollOnFling = true,
onStart = { offsetAvailable -> onStart(offsetAvailable) },
- onScroll = { offsetAvailable, _ ->
+ onScroll = { offsetAvailable ->
val currentHeight = scrimOffset()
val amountConsumed =
if (offsetAvailable > 0) {
val amountLeft = maxScrimOffset - currentHeight
- offsetAvailable.fastCoerceAtMost(amountLeft)
+ offsetAvailable.coerceAtMost(amountLeft)
} else {
val amountLeft = minScrimOffset() - currentHeight
- offsetAvailable.fastCoerceAtLeast(amountLeft)
+ offsetAvailable.coerceAtLeast(amountLeft)
}
snapScrimOffset(currentHeight + amountConsumed)
amountConsumed
},
+ // Don't consume the velocity on pre/post fling
onStop = { velocityAvailable ->
onStop(velocityAvailable)
if (scrimOffset() < minScrimOffset()) {
animateScrimOffset(minScrimOffset())
}
- // Don't consume the velocity on pre/post fling
- 0f
- },
- onCancel = {
- onStop(0f)
- if (scrimOffset() < minScrimOffset()) {
- animateScrimOffset(minScrimOffset())
- }
+ { 0f }
},
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
index edb05ebd77d1..a706585deebc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
@@ -28,7 +28,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastCoerceAtLeast
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.max
import kotlin.math.roundToInt
@@ -87,25 +86,21 @@ fun NotificationStackNestedScrollConnection(
): PriorityNestedScrollConnection {
return PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
- canStartPreScroll = { _, _, _ -> false },
- canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ canStartPreScroll = { _, _ -> false },
+ canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
},
canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
- canStopOnPreFling = { false },
+ canContinueScroll = { stackOffset() > 0f },
+ canScrollOnFling = true,
onStart = { offsetAvailable -> onStart(offsetAvailable) },
- onScroll = { offsetAvailable, _ ->
- val minOffset = 0f
- val consumed = offsetAvailable.fastCoerceAtLeast(minOffset - stackOffset())
- if (consumed != 0f) {
- onScroll(consumed)
- }
- consumed
+ onScroll = { offsetAvailable ->
+ onScroll(offsetAvailable)
+ offsetAvailable
},
onStop = { velocityAvailable ->
onStop(velocityAvailable)
- velocityAvailable
+ suspend { velocityAvailable }
},
- onCancel = { onStop(0f) },
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 9f99c372e2a8..e5ef79b37b88 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -27,10 +27,9 @@ import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
+import com.android.compose.nestedscroll.SuspendedValue
import kotlin.math.absoluteValue
-internal typealias SuspendedValue<T> = suspend () -> T
-
internal interface DraggableHandler {
/**
* Start a drag in the given [startedPosition], with the given [overSlop] and number of
@@ -613,7 +612,7 @@ internal class NestedScrollHandlerImpl(
return PriorityNestedScrollConnection(
orientation = orientation,
- canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
canChangeScene =
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
@@ -645,7 +644,7 @@ internal class NestedScrollHandlerImpl(
isIntercepting = true
true
},
- canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
val behavior: NestedScrollBehavior =
when {
offsetAvailable > 0f -> topOrLeftBehavior
@@ -710,7 +709,8 @@ internal class NestedScrollHandlerImpl(
canStart
},
- canStopOnPreFling = { true },
+ canContinueScroll = { true },
+ canScrollOnFling = false,
onStart = { offsetAvailable ->
val pointersInfo = pointersInfo()
dragController =
@@ -720,7 +720,7 @@ internal class NestedScrollHandlerImpl(
overSlop = if (isIntercepting) 0f else offsetAvailable,
)
},
- onScroll = { offsetAvailable, _ ->
+ onScroll = { offsetAvailable ->
val controller = dragController ?: error("Should be called after onStart")
val pointersInfo = pointersInfoOwner.pointersInfo()
@@ -735,18 +735,10 @@ internal class NestedScrollHandlerImpl(
},
onStop = { velocityAvailable ->
val controller = dragController ?: error("Should be called after onStart")
- try {
- controller
- .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene)
- .invoke()
- } finally {
- dragController = null
- }
- },
- onCancel = {
- val controller = dragController ?: error("Should be called after onStart")
- controller.onStop(velocity = 0f, canChangeContent = canChangeScene)
- dragController = null
+
+ controller
+ .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene)
+ .also { dragController = null }
},
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index f0043e1e89b0..205267da151a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -27,6 +27,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
+import com.android.compose.nestedscroll.SuspendedValue
import kotlin.math.absoluteValue
import kotlinx.coroutines.CompletableDeferred
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index ecf64b771d1f..4ae323517b26 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -18,8 +18,6 @@ package com.android.compose.nestedscroll
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.util.fastCoerceAtLeast
-import androidx.compose.ui.util.fastCoerceAtMost
/**
* A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
@@ -45,32 +43,35 @@ fun LargeTopAppBarNestedScrollConnection(
orientation = Orientation.Vertical,
// When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
// expand. Then, you can then scroll down the content.
- canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
},
// When swiping down, the content will scroll up until it reaches the top. Then, the
// LargeTopAppBar will expand until it reaches its [maxHeight].
- canStartPostScroll = { offsetAvailable, _, _ ->
+ canStartPostScroll = { offsetAvailable, _ ->
offsetAvailable > 0 && height() < maxHeight()
},
canStartPostFling = { false },
- canStopOnPreFling = { false },
+ canContinueScroll = {
+ val currentHeight = height()
+ minHeight() < currentHeight && currentHeight < maxHeight()
+ },
+ canScrollOnFling = true,
onStart = { /* do nothing */ },
- onScroll = { offsetAvailable, _ ->
+ onScroll = { offsetAvailable ->
val currentHeight = height()
val amountConsumed =
if (offsetAvailable > 0) {
val amountLeft = maxHeight() - currentHeight
- offsetAvailable.fastCoerceAtMost(amountLeft)
+ offsetAvailable.coerceAtMost(amountLeft)
} else {
val amountLeft = minHeight() - currentHeight
- offsetAvailable.fastCoerceAtLeast(amountLeft)
+ offsetAvailable.coerceAtLeast(amountLeft)
}
onHeightChanged(currentHeight + amountConsumed)
amountConsumed
},
// Don't consume the velocity on pre/post fling
- onStop = { 0f },
- onCancel = { /* do nothing */ },
+ onStop = { { 0f } },
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 636c55799ff2..a3641e6635e7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -16,59 +16,37 @@
package com.android.compose.nestedscroll
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.DecayAnimationSpec
-import androidx.compose.animation.core.animateDecay
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.unit.Velocity
import com.android.compose.ui.util.SpaceVectorConverter
-import kotlin.math.abs
import kotlin.math.sign
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.async
-import kotlinx.coroutines.coroutineScope
+
+internal typealias SuspendedValue<T> = suspend () -> T
/**
- * A [NestedScrollConnection] that intercepts scroll events in priority mode.
+ * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and
+ * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling.
+ * If it does, it will scroll before its children, until [canContinueScroll] allows it.
*
- * Priority mode allows this connection to take control over scroll events within a nested scroll
- * hierarchy. When in priority mode, this connection consumes scroll events before its children,
- * enabling custom scrolling behaviors like sticky headers.
+ * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
+ * after [onStart].
*
- * @param orientation The orientation of the scroll.
- * @param canStartPreScroll lambda that returns true if the connection can start consuming scroll
- * events in pre-scroll mode.
- * @param canStartPostScroll lambda that returns true if the connection can start consuming scroll
- * events in post-scroll mode.
- * @param canStartPostFling lambda that returns true if the connection can start consuming scroll
- * events in post-fling mode.
- * @param canStopOnPreFling lambda that returns true if the connection can stop consuming scroll
- * events in pre-fling (i.e. as soon as the user lifts their fingers).
- * @param onStart lambda that is called when the connection starts consuming scroll events.
- * @param onScroll lambda that is called when the connection consumes a scroll event and returns the
- * consumed amount.
- * @param onStop lambda that is called when the connection stops consuming scroll events and returns
- * the consumed velocity.
- * @param onCancel lambda that is called when the connection is cancelled.
* @sample LargeTopAppBarNestedScrollConnection
* @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
*/
class PriorityNestedScrollConnection(
orientation: Orientation,
- private val canStartPreScroll:
- (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
- private val canStartPostScroll:
- (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
+ private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
+ private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
private val canStartPostFling: (velocityAvailable: Float) -> Boolean,
- private val canStopOnPreFling: () -> Boolean,
+ private val canContinueScroll: (source: NestedScrollSource) -> Boolean,
+ private val canScrollOnFling: Boolean,
private val onStart: (offsetAvailable: Float) -> Unit,
- private val onScroll: (offsetAvailable: Float, source: NestedScrollSource) -> Float,
- private val onStop: suspend (velocityAvailable: Float) -> Float,
- private val onCancel: () -> Unit,
+ private val onScroll: (offsetAvailable: Float) -> Float,
+ private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
/** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */
@@ -76,9 +54,6 @@ class PriorityNestedScrollConnection(
private var offsetScrolledBeforePriorityMode = 0f
- /** This job allows us to interrupt the onStop animation */
- private var onStopJob: Deferred<Float> = CompletableDeferred(0f)
-
override fun onPostScroll(
consumed: Offset,
available: Offset,
@@ -89,48 +64,62 @@ class PriorityNestedScrollConnection(
// the beginning or from the last fling gesture.
val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
- if (isPriorityMode || !canStartPostScroll(availableFloat, offsetBeforeStart, source)) {
+ if (
+ isPriorityMode ||
+ (source == NestedScrollSource.SideEffect && !canScrollOnFling) ||
+ !canStartPostScroll(availableFloat, offsetBeforeStart)
+ ) {
// The priority mode cannot start so we won't consume the available offset.
return Offset.Zero
}
- return start(availableFloat, source).toOffset()
+ return onPriorityStart(availableFloat).toOffset()
}
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
if (!isPriorityMode) {
- val availableFloat = available.toFloat()
- if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) {
- return start(availableFloat, source).toOffset()
+ if (source == NestedScrollSource.UserInput || canScrollOnFling) {
+ val availableFloat = available.toFloat()
+ if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) {
+ return onPriorityStart(availableFloat).toOffset()
+ }
+ // We want to track the amount of offset consumed before entering priority mode
+ offsetScrolledBeforePriorityMode += availableFloat
}
+
+ return Offset.Zero
+ }
+
+ val availableFloat = available.toFloat()
+ if (!canContinueScroll(source)) {
+ // Step 3a: We have lost priority and we no longer need to intercept scroll events.
+ onPriorityStop(velocity = 0f)
+
+ // We've just reset offsetScrolledBeforePriorityMode to 0f
// We want to track the amount of offset consumed before entering priority mode
offsetScrolledBeforePriorityMode += availableFloat
+
return Offset.Zero
}
- return scroll(available.toFloat(), source).toOffset()
+ // Step 2: We have the priority and can consume the scroll events.
+ return onScroll(availableFloat).toOffset()
}
override suspend fun onPreFling(available: Velocity): Velocity {
- if (!isPriorityMode) {
- resetOffsetTracker()
+ if (isPriorityMode && canScrollOnFling) {
+ // We don't want to consume the velocity, we prefer to continue receiving scroll events.
return Velocity.Zero
}
-
- if (canStopOnPreFling()) {
- // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the
- // velocity of the fling gesture.
- return stop(velocityAvailable = available.toFloat()).toVelocity()
- }
-
- // We don't want to consume the velocity, we prefer to continue receiving scroll events.
- return Velocity.Zero
+ // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed
+ // of the fling gesture.
+ return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity()
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
val availableFloat = available.toFloat()
if (isPriorityMode) {
- return stop(velocityAvailable = availableFloat).toVelocity()
+ return onPriorityStop(velocity = availableFloat).invoke().toVelocity()
}
if (!canStartPostFling(availableFloat)) {
@@ -142,14 +131,10 @@ class PriorityNestedScrollConnection(
// TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
// overscroll behavior on the Scene level.
val smallOffset = availableFloat.sign
- start(
- availableOffset = smallOffset,
- source = NestedScrollSource.SideEffect,
- skipScroll = true,
- )
+ onPriorityStart(availableOffset = smallOffset)
// This is the last event of a scroll gesture.
- return stop(availableFloat).toVelocity()
+ return onPriorityStop(availableFloat).invoke().toVelocity()
}
/**
@@ -158,76 +143,36 @@ class PriorityNestedScrollConnection(
* TODO(b/303224944) This method should be removed.
*/
fun reset() {
- if (isPriorityMode) {
- // Step 3c: To ensure that an onStop (or onCancel) is always called for every onStart.
- cancel()
- } else {
- resetOffsetTracker()
- }
- }
-
- private fun shouldStop(consumed: Float): Boolean {
- return consumed == 0f
+ // Step 3c: To ensure that an onStop is always called for every onStart.
+ onPriorityStop(velocity = 0f)
}
- private fun start(
- availableOffset: Float,
- source: NestedScrollSource,
- skipScroll: Boolean = false,
- ): Float {
- check(!isPriorityMode) {
- "This should never happen, start() was called when isPriorityMode"
+ private fun onPriorityStart(availableOffset: Float): Float {
+ if (isPriorityMode) {
+ error("This should never happen, onPriorityStart() was called when isPriorityMode")
}
// Step 1: It's our turn! We start capturing scroll events when one of our children has an
// available offset following a scroll event.
isPriorityMode = true
- onStopJob.cancel()
-
// Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is
// lifted (step 3b), or this object has been destroyed (step 3c).
onStart(availableOffset)
- return if (skipScroll) 0f else scroll(availableOffset, source)
+ return onScroll(availableOffset)
}
- private fun scroll(offsetAvailable: Float, source: NestedScrollSource): Float {
- // Step 2: We have the priority and can consume the scroll events.
- val consumedByScroll = onScroll(offsetAvailable, source)
-
- if (shouldStop(consumedByScroll)) {
- // Step 3a: We have lost priority and we no longer need to intercept scroll events.
- cancel()
-
- // We've just reset offsetScrolledBeforePriorityMode to 0f
- // We want to track the amount of offset consumed before entering priority mode
- offsetScrolledBeforePriorityMode += offsetAvailable - consumedByScroll
- }
-
- return consumedByScroll
- }
-
- /** Reset the tracking of consumed offsets before entering in priority mode. */
- private fun resetOffsetTracker() {
+ private fun onPriorityStop(velocity: Float): SuspendedValue<Float> {
+ // We can restart tracking the consumed offsets from scratch.
offsetScrolledBeforePriorityMode = 0f
- }
- private suspend fun stop(velocityAvailable: Float): Float {
- check(isPriorityMode) { "This should never happen, stop() was called before start()" }
- isPriorityMode = false
- resetOffsetTracker()
-
- return coroutineScope {
- onStopJob = async { onStop(velocityAvailable) }
- onStopJob.await()
+ if (!isPriorityMode) {
+ return { 0f }
}
- }
- private fun cancel() {
- check(isPriorityMode) { "This should never happen, cancel() was called before start()" }
isPriorityMode = false
- resetOffsetTracker()
- onCancel()
+
+ return onStop(velocity)
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index 339445e3483a..fd0214823eaf 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -39,6 +39,7 @@ import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.content.state.TransitionState.Transition
import com.android.compose.animation.scene.subjects.assertThat
+import com.android.compose.nestedscroll.SuspendedValue
import com.android.compose.test.MonotonicClockTestScope
import com.android.compose.test.runMonotonicClockTest
import com.android.compose.test.transition
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 3df608717414..c8f6e6d99933 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -46,6 +46,7 @@ import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Velocity
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.modifiers.thenIf
+import com.android.compose.nestedscroll.SuspendedValue
import com.google.common.truth.Truth.assertThat
import kotlin.properties.Delegates
import kotlinx.coroutines.coroutineScope
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
index 1a3b86b936df..badc43bd3e0f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt
@@ -34,31 +34,30 @@ class PriorityNestedScrollConnectionTest {
private var canStartPreScroll = false
private var canStartPostScroll = false
private var canStartPostFling = false
- private var canStopOnPreFling = true
+ private var canContinueScroll = false
private var isStarted = false
private var lastScroll: Float? = null
- private var consumeScroll = true
+ private var returnOnScroll = 0f
private var lastStop: Float? = null
- private var isCancelled: Boolean = false
- private var consumeStop = true
+ private var returnOnStop = 0f
private val scrollConnection =
PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
- canStartPreScroll = { _, _, _ -> canStartPreScroll },
- canStartPostScroll = { _, _, _ -> canStartPostScroll },
+ canStartPreScroll = { _, _ -> canStartPreScroll },
+ canStartPostScroll = { _, _ -> canStartPostScroll },
canStartPostFling = { canStartPostFling },
- canStopOnPreFling = { canStopOnPreFling },
+ canContinueScroll = { canContinueScroll },
+ canScrollOnFling = false,
onStart = { isStarted = true },
- onScroll = { offsetAvailable, _ ->
- lastScroll = offsetAvailable
- if (consumeScroll) offsetAvailable else 0f
+ onScroll = {
+ lastScroll = it
+ returnOnScroll
},
onStop = {
lastStop = it
- if (consumeStop) it else 0f
+ { returnOnStop }
},
- onCancel = { isCancelled = true },
)
@Test
@@ -86,7 +85,7 @@ class PriorityNestedScrollConnectionTest {
canStartPostScroll = true
scrollConnection.onPostScroll(
consumed = Offset.Zero,
- available = Offset(1f, 1f),
+ available = Offset.Zero,
source = UserInput,
)
}
@@ -137,55 +136,45 @@ class PriorityNestedScrollConnectionTest {
@Test
fun step2_onPriorityMode_shouldContinueIfAllowed() {
startPriorityModePostScroll()
+ canContinueScroll = true
- val scroll1 = scrollConnection.onPreScroll(available = Offset(0f, 1f), source = UserInput)
+ scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput)
assertThat(lastScroll).isEqualTo(1f)
- assertThat(scroll1.y).isEqualTo(1f)
- consumeScroll = false
- val scroll2 = scrollConnection.onPreScroll(available = Offset(0f, 2f), source = UserInput)
- assertThat(lastScroll).isEqualTo(2f)
- assertThat(scroll2.y).isEqualTo(0f)
+ canContinueScroll = false
+ scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput)
+ assertThat(lastScroll).isNotEqualTo(2f)
+ assertThat(lastScroll).isEqualTo(1f)
}
@Test
- fun step3a_onPriorityMode_shouldCancelIfCannotContinue() {
+ fun step3a_onPriorityMode_shouldStopIfCannotContinue() {
startPriorityModePostScroll()
- consumeScroll = false
+ canContinueScroll = false
- scrollConnection.onPreScroll(available = Offset(0f, 1f), source = UserInput)
+ scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput)
- assertThat(isCancelled).isTrue()
+ assertThat(lastStop).isNotNull()
}
@Test
fun step3b_onPriorityMode_shouldStopOnFling() = runTest {
startPriorityModePostScroll()
+ canContinueScroll = true
scrollConnection.onPreFling(available = Velocity.Zero)
- assertThat(lastStop).isEqualTo(0f)
- }
-
- @Test
- fun ifCannotStopOnPreFling_shouldStopOnPostFling() = runTest {
- startPriorityModePostScroll()
- canStopOnPreFling = false
-
- scrollConnection.onPreFling(available = Velocity.Zero)
- assertThat(lastStop).isNull()
-
- scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero)
- assertThat(lastStop).isEqualTo(0f)
+ assertThat(lastStop).isNotNull()
}
@Test
- fun step3c_onPriorityMode_shouldCancelOnReset() {
+ fun step3c_onPriorityMode_shouldStopOnReset() {
startPriorityModePostScroll()
+ canContinueScroll = true
scrollConnection.reset()
- assertThat(isCancelled).isTrue()
+ assertThat(lastStop).isNotNull()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
index 75479ad35725..1f1680e0c273 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt
@@ -31,7 +31,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
private var isStarted = false
- private var wasStarted = false
private var scrimOffset = 0f
private var contentHeight = 0f
private var isCurrentGestureOverscroll = false
@@ -47,10 +46,7 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT },
isCurrentGestureOverscroll = { isCurrentGestureOverscroll },
onStart = { isStarted = true },
- onStop = {
- wasStarted = true
- isStarted = false
- },
+ onStop = { isStarted = false },
)
@Test
@@ -169,7 +165,6 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
)
assertThat(offsetConsumed).isEqualTo(Offset.Zero)
- assertThat(wasStarted).isEqualTo(false)
assertThat(isStarted).isEqualTo(false)
}
@@ -186,9 +181,7 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() {
)
assertThat(offsetConsumed).isEqualTo(Offset.Zero)
- // Returning 0 offset will immediately stop the connection
- assertThat(wasStarted).isEqualTo(true)
- assertThat(isStarted).isEqualTo(false)
+ assertThat(isStarted).isEqualTo(true)
}
@Test