Loading packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt +1 −1 Original line number Diff line number Diff line Loading @@ -81,7 +81,7 @@ fun NotificationScrimNestedScrollConnection( if (scrimOffset() < minScrimOffset()) { animateScrimOffset(minScrimOffset()) } 0f { 0f } }, ) } packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +1 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,7 @@ fun NotificationStackNestedScrollConnection( }, onStop = { velocityAvailable -> onStop(velocityAvailable) velocityAvailable suspend { velocityAvailable } }, ) } packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +8 −7 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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( Loading Loading @@ -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) } Loading @@ -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. Loading Loading @@ -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 } } packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +11 −4 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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 Loading Loading @@ -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 = Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +16 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -360,7 +361,7 @@ internal class SwipeAnimation<T : ContentKey>( currentContent = targetContent } val startProgress = val initialOffset = if (contentTransition.previewTransformationSpec != null && targetContent == toContent) { 0f } else { Loading @@ -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()) Loading @@ -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 Loading @@ -391,6 +392,8 @@ internal class SwipeAnimation<T : ContentKey>( ?: contentTransition.transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec val velocityConsumed = CompletableDeferred<Float>() offsetAnimationRunnable.complete { try { animatable.animateTo( Loading Loading @@ -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() } } Loading @@ -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 Loading
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt +1 −1 Original line number Diff line number Diff line Loading @@ -81,7 +81,7 @@ fun NotificationScrimNestedScrollConnection( if (scrimOffset() < minScrimOffset()) { animateScrimOffset(minScrimOffset()) } 0f { 0f } }, ) }
packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +1 −1 Original line number Diff line number Diff line Loading @@ -100,7 +100,7 @@ fun NotificationStackNestedScrollConnection( }, onStop = { velocityAvailable -> onStop(velocityAvailable) velocityAvailable suspend { velocityAvailable } }, ) }
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +8 −7 Original line number Diff line number Diff line Loading @@ -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 { Loading Loading @@ -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( Loading Loading @@ -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) } Loading @@ -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. Loading Loading @@ -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 } }
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +11 −4 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -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 Loading Loading @@ -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 = Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +16 −6 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -360,7 +361,7 @@ internal class SwipeAnimation<T : ContentKey>( currentContent = targetContent } val startProgress = val initialOffset = if (contentTransition.previewTransformationSpec != null && targetContent == toContent) { 0f } else { Loading @@ -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()) Loading @@ -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 Loading @@ -391,6 +392,8 @@ internal class SwipeAnimation<T : ContentKey>( ?: contentTransition.transformationSpec.swipeSpec ?: layoutState.transitions.defaultSwipeSpec val velocityConsumed = CompletableDeferred<Float>() offsetAnimationRunnable.complete { try { animatable.animateTo( Loading Loading @@ -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() } } Loading @@ -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