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

Commit 95d862ac authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes Ia1b0620f,I91163d53 into main

* changes:
  animateOffset can partially consume the fling velocity
  animateOffset returns the consumed velocity after the animation ends
parents 889ef471 408325d2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ fun NotificationScrimNestedScrollConnection(
            if (scrimOffset() < minScrimOffset()) {
                animateScrimOffset(minScrimOffset())
            }
            0f
            { 0f }
        },
    )
}
+1 −1
Original line number Diff line number Diff line
@@ -100,7 +100,7 @@ fun NotificationStackNestedScrollConnection(
        },
        onStop = { velocityAvailable ->
            onStop(velocityAvailable)
            velocityAvailable
            suspend { velocityAvailable }
        },
    )
}
+8 −7
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ 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 interface DraggableHandler {
@@ -54,9 +55,9 @@ internal interface DragController {
    /**
     * Stop the current drag with the given [velocity].
     *
     * @return the consumed [velocity]
     * @return the consumed [velocity] when the animation complete
     */
    fun onStop(velocity: Float, canChangeContent: Boolean): Float
    fun onStop(velocity: Float, canChangeContent: Boolean): SuspendedValue<Float>
}

internal class DraggableHandlerImpl(
@@ -386,7 +387,7 @@ private class DragControllerImpl(
        return consumedDelta
    }

    override fun onStop(velocity: Float, canChangeContent: Boolean): Float {
    override fun onStop(velocity: Float, canChangeContent: Boolean): SuspendedValue<Float> {
        return onStop(velocity, canChangeContent, swipeAnimation)
    }

@@ -399,14 +400,14 @@ private class DragControllerImpl(
        // callbacks (like onAnimationCompleted()) might incorrectly finish a new transition that
        // replaced this one.
        swipeAnimation: SwipeAnimation<T>,
    ): Float {
    ): SuspendedValue<Float> {
        // The state was changed since the drag started; don't do anything.
        if (!isDrivingTransition || swipeAnimation.isAnimatingOffset()) {
            return 0f
            return { 0f }
        }

        val fromContent = swipeAnimation.fromContent
        val consumedVelocity: Float
        val consumedVelocity: SuspendedValue<Float>
        if (canChangeContent) {
            // If we are halfway between two contents, we check what the target will be based on the
            // velocity and offset of the transition, then we launch the animation.
@@ -738,5 +739,5 @@ internal const val OffsetVisibilityThreshold = 0.5f
private object NoOpDragController : DragController {
    override fun onDrag(delta: Float) = 0f

    override fun onStop(velocity: Float, canChangeContent: Boolean) = 0f
    override fun onStop(velocity: Float, canChangeContent: Boolean) = suspend { 0f }
}
+11 −4
Original line number Diff line number Diff line
@@ -325,13 +325,17 @@ internal class MultiPointerDraggableNode(
                                            velocityTracker.calculateVelocity(maxVelocity)
                                        }
                                        .toFloat(),
                                onFling = { controller.onStop(it, canChangeContent = true) },
                                onFling = {
                                    controller.onStop(it, canChangeContent = true).invoke()
                                },
                            )
                        },
                        onDragCancel = { controller ->
                            startFlingGesture(
                                initialVelocity = 0f,
                                onFling = { controller.onStop(it, canChangeContent = true) },
                                onFling = {
                                    controller.onStop(it, canChangeContent = true).invoke()
                                },
                            )
                        },
                        swipeDetector = swipeDetector,
@@ -353,7 +357,10 @@ internal class MultiPointerDraggableNode(
     *
     * Note: Inspired by [androidx.compose.foundation.gestures.ScrollableNode.onDragStopped]
     */
    private fun startFlingGesture(initialVelocity: Float, onFling: (velocity: Float) -> Float) {
    private fun startFlingGesture(
        initialVelocity: Float,
        onFling: suspend (velocity: Float) -> Float,
    ) {
        // Note: [AwaitPointerEventScope] is annotated as @RestrictsSuspension, we need another
        // CoroutineScope to run the fling gestures.
        // We do not need to cancel this [Job], the source will take care of emitting an
@@ -415,7 +422,7 @@ internal class MultiPointerDraggableNode(
     */
    private suspend inline fun dispatchFlingEvents(
        availableOnPreFling: Float,
        onFling: (velocity: Float) -> Float,
        onFling: suspend (velocity: Float) -> Float,
    ): Float {
        // PreFling phase
        val consumedByPreFling =
+16 −6
Original line number Diff line number Diff line
@@ -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

@@ -321,7 +322,7 @@ internal class SwipeAnimation<T : ContentKey>(
        initialVelocity: Float,
        targetContent: T,
        spec: AnimationSpec<Float>? = null,
    ): Float {
    ): SuspendedValue<Float> {
        check(!isAnimatingOffset()) { "SwipeAnimation.animateOffset() can only be called once" }

        val initialProgress = progress
@@ -360,7 +361,7 @@ internal class SwipeAnimation<T : ContentKey>(
            currentContent = targetContent
        }

        val startProgress =
        val initialOffset =
            if (contentTransition.previewTransformationSpec != null && targetContent == toContent) {
                0f
            } else {
@@ -368,7 +369,7 @@ internal class SwipeAnimation<T : ContentKey>(
            }

        val animatable =
            Animatable(startProgress, OffsetVisibilityThreshold).also { offsetAnimation = it }
            Animatable(initialOffset, OffsetVisibilityThreshold).also { offsetAnimation = it }

        check(isAnimatingOffset())

@@ -379,7 +380,7 @@ internal class SwipeAnimation<T : ContentKey>(
        if (skipAnimation) {
            // Unblock the job.
            offsetAnimationRunnable.complete(null)
            return 0f
            return { 0f }
        }

        val isTargetGreater = targetOffset > animatable.value
@@ -391,6 +392,8 @@ internal class SwipeAnimation<T : ContentKey>(
                ?: contentTransition.transformationSpec.swipeSpec
                ?: layoutState.transitions.defaultSwipeSpec

        val velocityConsumed = CompletableDeferred<Float>()

        offsetAnimationRunnable.complete {
            try {
                animatable.animateTo(
@@ -420,6 +423,9 @@ internal class SwipeAnimation<T : ContentKey>(
                            // Immediately stop this transition if we are bouncing on a content that
                            // does not bounce.
                            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()
                            }
                        }
@@ -427,11 +433,15 @@ internal class SwipeAnimation<T : ContentKey>(
                }
            } catch (_: SnapException) {
                /* Ignore. */
            } finally {
                if (!velocityConsumed.isCompleted) {
                    // The animation consumed the whole available velocity
                    velocityConsumed.complete(initialVelocity)
                }
            }
        }

        // This animation always consumes the whole available velocity
        return initialVelocity
        return { velocityConsumed.await() }
    }

    /** An exception thrown during the animation to stop it immediately. */
Loading