Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 408325d2 authored by omarmt's avatar omarmt
Browse files

animateOffset can partially consume the fling velocity

When a scene is configured to disallow overscrolling
(`overscrollDisabled()` is enabled), a fling gesture on that scene may
not use up all of the velocity. The unused velocity can then be passed
on to the ancestor of the component.

Test: atest DraggableHandlerTest
Bug: 336710600
Flag: com.android.systemui.scene_container
Change-Id: Ia1b0620fe37cfaf5b339d1e87c38ba5c22be95e8
parent 78388235
Loading
Loading
Loading
Loading
+7 −4
Original line number Original line Diff line number Diff line
@@ -28,10 +28,8 @@ import androidx.compose.ui.unit.IntSize
import com.android.compose.animation.scene.content.state.TransitionState
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.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified
import com.android.compose.nestedscroll.SuspendedValue
import com.android.compose.nestedscroll.SuspendedValue
import kotlinx.coroutines.CancellationException
import kotlin.math.absoluteValue
import kotlin.math.absoluteValue
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withTimeout


internal fun createSwipeAnimation(
internal fun createSwipeAnimation(
    layoutState: MutableSceneTransitionLayoutStateImpl,
    layoutState: MutableSceneTransitionLayoutStateImpl,
@@ -425,6 +423,9 @@ internal class SwipeAnimation<T : ContentKey>(
                            // Immediately stop this transition if we are bouncing on a content that
                            // Immediately stop this transition if we are bouncing on a content that
                            // does not bounce.
                            // does not bounce.
                            if (!contentTransition.isWithinProgressRange(progress)) {
                            if (!contentTransition.isWithinProgressRange(progress)) {
                                // We are no longer able to consume the velocity, the rest can be
                                // consumed by another component in the hierarchy.
                                velocityConsumed.complete(initialVelocity - velocity)
                                throw SnapException()
                                throw SnapException()
                            }
                            }
                        }
                        }
@@ -433,10 +434,12 @@ internal class SwipeAnimation<T : ContentKey>(
            } catch (_: SnapException) {
            } catch (_: SnapException) {
                /* Ignore. */
                /* Ignore. */
            } finally {
            } finally {
                if (!velocityConsumed.isCompleted) {
                    // The animation consumed the whole available velocity
                    // The animation consumed the whole available velocity
                    velocityConsumed.complete(initialVelocity)
                    velocityConsumed.complete(initialVelocity)
                }
                }
            }
            }
        }


        return { velocityConsumed.await() }
        return { velocityConsumed.await() }
    }
    }
+23 −0
Original line number Original line Diff line number Diff line
@@ -1176,6 +1176,29 @@ class DraggableHandlerTest {
        assertIdle(SceneB)
        assertIdle(SceneB)
    }
    }


    @Test
    fun emptyOverscrollAbortsSettleAnimationAndExposeTheConsumedVelocity() = runGestureTest {
        // Overscrolling on scene B does nothing.
        layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }

        // Swipe up to scene B at progress = 200%.
        val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
        val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.99f))
        assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f)

        // Release the finger.
        dragController.onDragStoppedAnimateNow(
            velocity = -velocityThreshold,
            onAnimationStart = { assertTransition(fromScene = SceneA, toScene = SceneB) },
            onAnimationEnd = { consumedVelocity ->
                // Our progress value was 0.99f and it is coerced in `[0..1]` (overscrollDisabled).
                // Some of the velocity will be used for animation, but not all of it.
                assertThat(consumedVelocity).isLessThan(0f)
                assertThat(consumedVelocity).isGreaterThan(-velocityThreshold)
            },
        )
    }

    @Test
    @Test
    fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
    fun overscroll_releaseBetween0And100Percent_up() = runGestureTest {
        // Make scene B overscrollable.
        // Make scene B overscrollable.