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

Commit 7f60633a authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Revert "Simplify PriorityNestedScrollConnection"

This reverts commit 40c5c231.

Reason for revert: Causes weird gesture issues; single swipes could now
trigger multiple vertical + horizontal transitions.

Change-Id: I16f37ace7b436fb2502f476ea83c1421933d5860
parent 47f6e909
Loading
Loading
Loading
Loading
+10 −8
Original line number Original line Diff line number Diff line
@@ -18,8 +18,6 @@ package com.android.systemui.notifications.ui.composable


import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
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
import com.android.compose.nestedscroll.PriorityNestedScrollConnection


/**
/**
@@ -46,7 +44,7 @@ fun NotificationScrimNestedScrollConnection(
        orientation = Orientation.Vertical,
        orientation = Orientation.Vertical,
        // scrolling up and inner content is taller than the scrim, so scrim needs to
        // scrolling up and inner content is taller than the scrim, so scrim needs to
        // expand; content can scroll once scrim is at the minScrimOffset.
        // expand; content can scroll once scrim is at the minScrimOffset.
        canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
        canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
            offsetAvailable < 0 &&
            offsetAvailable < 0 &&
                offsetBeforeStart == 0f &&
                offsetBeforeStart == 0f &&
                contentHeight() > minVisibleScrimHeight() &&
                contentHeight() > minVisibleScrimHeight() &&
@@ -54,21 +52,25 @@ fun NotificationScrimNestedScrollConnection(
        },
        },
        // scrolling down and content is done scrolling to top. After that, the scrim
        // 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.
        // needs to collapse; collapse the scrim until it is at the maxScrimOffset.
        canStartPostScroll = { offsetAvailable, _, _ ->
        canStartPostScroll = { offsetAvailable, _ ->
            offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
            offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll())
        },
        },
        canStartPostFling = { false },
        canStartPostFling = { false },
        canStopOnPreFling = { false },
        canContinueScroll = {
            val currentHeight = scrimOffset()
            minScrimOffset() < currentHeight && currentHeight < maxScrimOffset
        },
        canScrollOnFling = true,
        onStart = { offsetAvailable -> onStart(offsetAvailable) },
        onStart = { offsetAvailable -> onStart(offsetAvailable) },
        onScroll = { offsetAvailable, _ ->
        onScroll = { offsetAvailable ->
            val currentHeight = scrimOffset()
            val currentHeight = scrimOffset()
            val amountConsumed =
            val amountConsumed =
                if (offsetAvailable > 0) {
                if (offsetAvailable > 0) {
                    val amountLeft = maxScrimOffset - currentHeight
                    val amountLeft = maxScrimOffset - currentHeight
                    offsetAvailable.fastCoerceAtMost(amountLeft)
                    offsetAvailable.coerceAtMost(amountLeft)
                } else {
                } else {
                    val amountLeft = minScrimOffset() - currentHeight
                    val amountLeft = minScrimOffset() - currentHeight
                    offsetAvailable.fastCoerceAtLeast(amountLeft)
                    offsetAvailable.coerceAtLeast(amountLeft)
                }
                }
            snapScrimOffset(currentHeight + amountConsumed)
            snapScrimOffset(currentHeight + amountConsumed)
            amountConsumed
            amountConsumed
+7 −11
Original line number Original line Diff line number Diff line
@@ -28,7 +28,6 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.util.fastCoerceAtLeast
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlin.math.max
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.roundToInt
@@ -87,20 +86,17 @@ fun NotificationStackNestedScrollConnection(
): PriorityNestedScrollConnection {
): PriorityNestedScrollConnection {
    return PriorityNestedScrollConnection(
    return PriorityNestedScrollConnection(
        orientation = Orientation.Vertical,
        orientation = Orientation.Vertical,
        canStartPreScroll = { _, _, _ -> false },
        canStartPreScroll = { _, _ -> false },
        canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
        canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
            offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
            offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
        },
        },
        canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
        canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() },
        canStopOnPreFling = { false },
        canContinueScroll = { stackOffset() > 0f },
        canScrollOnFling = true,
        onStart = { offsetAvailable -> onStart(offsetAvailable) },
        onStart = { offsetAvailable -> onStart(offsetAvailable) },
        onScroll = { offsetAvailable, _ ->
        onScroll = { offsetAvailable ->
            val minOffset = 0f
            onScroll(offsetAvailable)
            val consumed = offsetAvailable.fastCoerceAtLeast(minOffset - stackOffset())
            offsetAvailable
            if (consumed != 0f) {
                onScroll(consumed)
            }
            consumed
        },
        },
        onStop = { velocityAvailable ->
        onStop = { velocityAvailable ->
            onStop(velocityAvailable)
            onStop(velocityAvailable)
+5 −4
Original line number Original line Diff line number Diff line
@@ -612,7 +612,7 @@ internal class NestedScrollHandlerImpl(


        return PriorityNestedScrollConnection(
        return PriorityNestedScrollConnection(
            orientation = orientation,
            orientation = orientation,
            canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
            canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
                canChangeScene =
                canChangeScene =
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
                    if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f


@@ -644,7 +644,7 @@ internal class NestedScrollHandlerImpl(
                isIntercepting = true
                isIntercepting = true
                true
                true
            },
            },
            canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
            canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                val behavior: NestedScrollBehavior =
                val behavior: NestedScrollBehavior =
                    when {
                    when {
                        offsetAvailable > 0f -> topOrLeftBehavior
                        offsetAvailable > 0f -> topOrLeftBehavior
@@ -709,7 +709,8 @@ internal class NestedScrollHandlerImpl(


                canStart
                canStart
            },
            },
            canStopOnPreFling = { true },
            canContinueScroll = { true },
            canScrollOnFling = false,
            onStart = { offsetAvailable ->
            onStart = { offsetAvailable ->
                val pointersInfo = pointersInfo()
                val pointersInfo = pointersInfo()
                dragController =
                dragController =
@@ -719,7 +720,7 @@ internal class NestedScrollHandlerImpl(
                        overSlop = if (isIntercepting) 0f else offsetAvailable,
                        overSlop = if (isIntercepting) 0f else offsetAvailable,
                    )
                    )
            },
            },
            onScroll = { offsetAvailable, _ ->
            onScroll = { offsetAvailable ->
                val controller = dragController ?: error("Should be called after onStart")
                val controller = dragController ?: error("Should be called after onStart")


                val pointersInfo = pointersInfoOwner.pointersInfo()
                val pointersInfo = pointersInfoOwner.pointersInfo()
+10 −8
Original line number Original line Diff line number Diff line
@@ -18,8 +18,6 @@ package com.android.compose.nestedscroll


import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
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
 * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the
@@ -45,26 +43,30 @@ fun LargeTopAppBarNestedScrollConnection(
        orientation = Orientation.Vertical,
        orientation = Orientation.Vertical,
        // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
        // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
        // expand. Then, you can then scroll down the content.
        // expand. Then, you can then scroll down the content.
        canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
        canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
            offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
        },
        },
        // When swiping down, the content will scroll up until it reaches the top. Then, the
        // When swiping down, the content will scroll up until it reaches the top. Then, the
        // LargeTopAppBar will expand until it reaches its [maxHeight].
        // LargeTopAppBar will expand until it reaches its [maxHeight].
        canStartPostScroll = { offsetAvailable, _, _ ->
        canStartPostScroll = { offsetAvailable, _ ->
            offsetAvailable > 0 && height() < maxHeight()
            offsetAvailable > 0 && height() < maxHeight()
        },
        },
        canStartPostFling = { false },
        canStartPostFling = { false },
        canStopOnPreFling = { false },
        canContinueScroll = {
            val currentHeight = height()
            minHeight() < currentHeight && currentHeight < maxHeight()
        },
        canScrollOnFling = true,
        onStart = { /* do nothing */ },
        onStart = { /* do nothing */ },
        onScroll = { offsetAvailable, _ ->
        onScroll = { offsetAvailable ->
            val currentHeight = height()
            val currentHeight = height()
            val amountConsumed =
            val amountConsumed =
                if (offsetAvailable > 0) {
                if (offsetAvailable > 0) {
                    val amountLeft = maxHeight() - currentHeight
                    val amountLeft = maxHeight() - currentHeight
                    offsetAvailable.fastCoerceAtMost(amountLeft)
                    offsetAvailable.coerceAtMost(amountLeft)
                } else {
                } else {
                    val amountLeft = minHeight() - currentHeight
                    val amountLeft = minHeight() - currentHeight
                    offsetAvailable.fastCoerceAtLeast(amountLeft)
                    offsetAvailable.coerceAtLeast(amountLeft)
                }
                }
            onHeightChanged(currentHeight + amountConsumed)
            onHeightChanged(currentHeight + amountConsumed)
            amountConsumed
            amountConsumed
+56 −94
Original line number Original line Diff line number Diff line
@@ -27,39 +27,25 @@ import kotlin.math.sign
internal typealias SuspendedValue<T> = suspend () -> T
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
 * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop]
 * hierarchy. When in priority mode, this connection consumes scroll events before its children,
 * after [onStart].
 * enabling custom scrolling behaviors like sticky headers.
 *
 *
 * @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.
 * @sample LargeTopAppBarNestedScrollConnection
 * @sample LargeTopAppBarNestedScrollConnection
 * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
 * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection
 */
 */
class PriorityNestedScrollConnection(
class PriorityNestedScrollConnection(
    orientation: Orientation,
    orientation: Orientation,
    private val canStartPreScroll:
    private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
        (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
    private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean,
    private val canStartPostScroll:
        (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean,
    private val canStartPostFling: (velocityAvailable: 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 onStart: (offsetAvailable: Float) -> Unit,
    private val onScroll: (offsetAvailable: Float, source: NestedScrollSource) -> Float,
    private val onScroll: (offsetAvailable: Float) -> Float,
    private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
    private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>,
) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {
) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) {


@@ -78,48 +64,62 @@ class PriorityNestedScrollConnection(
        // the beginning or from the last fling gesture.
        // the beginning or from the last fling gesture.
        val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat
        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.
            // The priority mode cannot start so we won't consume the available offset.
            return Offset.Zero
            return Offset.Zero
        }
        }


        return start(availableFloat, source).toOffset()
        return onPriorityStart(availableFloat).toOffset()
    }
    }


    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
        if (!isPriorityMode) {
        if (!isPriorityMode) {
            if (source == NestedScrollSource.UserInput || canScrollOnFling) {
                val availableFloat = available.toFloat()
                val availableFloat = available.toFloat()
            if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) {
                if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) {
                return start(availableFloat, source).toOffset()
                    return onPriorityStart(availableFloat).toOffset()
                }
                }
                // We want to track the amount of offset consumed before entering priority mode
                // We want to track the amount of offset consumed before entering priority mode
                offsetScrolledBeforePriorityMode += availableFloat
                offsetScrolledBeforePriorityMode += availableFloat
            return Offset.Zero
            }
            }


        return scroll(available.toFloat(), source).toOffset()
            return Offset.Zero
        }
        }


    override suspend fun onPreFling(available: Velocity): Velocity {
        val availableFloat = available.toFloat()
        if (!isPriorityMode) {
        if (!canContinueScroll(source)) {
            resetOffsetTracker()
            // Step 3a: We have lost priority and we no longer need to intercept scroll events.
            return Velocity.Zero
            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
        }
        }


        if (canStopOnPreFling()) {
        // Step 2: We have the priority and can consume the scroll events.
            // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the
        return onScroll(availableFloat).toOffset()
            // velocity of the fling gesture.
            return stop(velocityAvailable = available.toFloat()).toVelocity()
    }
    }


    override suspend fun onPreFling(available: Velocity): Velocity {
        if (isPriorityMode && canScrollOnFling) {
            // We don't want to consume the velocity, we prefer to continue receiving scroll events.
            // We don't want to consume the velocity, we prefer to continue receiving scroll events.
            return Velocity.Zero
            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 {
    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
        val availableFloat = available.toFloat()
        val availableFloat = available.toFloat()
        if (isPriorityMode) {
        if (isPriorityMode) {
            return stop(velocityAvailable = availableFloat).toVelocity()
            return onPriorityStop(velocity = availableFloat).invoke().toVelocity()
        }
        }


        if (!canStartPostFling(availableFloat)) {
        if (!canStartPostFling(availableFloat)) {
@@ -131,14 +131,10 @@ class PriorityNestedScrollConnection(
        // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
        // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the
        // overscroll behavior on the Scene level.
        // overscroll behavior on the Scene level.
        val smallOffset = availableFloat.sign
        val smallOffset = availableFloat.sign
        start(
        onPriorityStart(availableOffset = smallOffset)
            availableOffset = smallOffset,
            source = NestedScrollSource.SideEffect,
            skipScroll = true,
        )


        // This is the last event of a scroll gesture.
        // This is the last event of a scroll gesture.
        return stop(availableFloat).toVelocity()
        return onPriorityStop(availableFloat).invoke().toVelocity()
    }
    }


    /**
    /**
@@ -147,25 +143,13 @@ class PriorityNestedScrollConnection(
     * TODO(b/303224944) This method should be removed.
     * TODO(b/303224944) This method should be removed.
     */
     */
    fun reset() {
    fun reset() {
        if (isPriorityMode) {
        // Step 3c: To ensure that an onStop is always called for every onStart.
        // Step 3c: To ensure that an onStop is always called for every onStart.
            cancel()
        onPriorityStop(velocity = 0f)
        } else {
            resetOffsetTracker()
        }
    }
    }


    private fun shouldStop(consumed: Float): Boolean {
    private fun onPriorityStart(availableOffset: Float): Float {
        return consumed == 0f
        if (isPriorityMode) {
    }
            error("This should never happen, onPriorityStart() was called when isPriorityMode")

    private fun start(
        availableOffset: Float,
        source: NestedScrollSource,
        skipScroll: Boolean = false,
    ): Float {
        check(!isPriorityMode) {
            "This should never happen, start() was called when isPriorityMode"
        }
        }


        // Step 1: It's our turn! We start capturing scroll events when one of our children has an
        // Step 1: It's our turn! We start capturing scroll events when one of our children has an
@@ -176,41 +160,19 @@ class PriorityNestedScrollConnection(
        // lifted (step 3b), or this object has been destroyed (step 3c).
        // lifted (step 3b), or this object has been destroyed (step 3c).
        onStart(availableOffset)
        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 onPriorityStop(velocity: Float): SuspendedValue<Float> {
    private fun resetOffsetTracker() {
        // We can restart tracking the consumed offsets from scratch.
        offsetScrolledBeforePriorityMode = 0f
        offsetScrolledBeforePriorityMode = 0f
    }


    private suspend fun stop(velocityAvailable: Float): Float {
        if (!isPriorityMode) {
        check(isPriorityMode) { "This should never happen, stop() was called before start()" }
            return { 0f }
        isPriorityMode = false
        resetOffsetTracker()
        return onStop(velocityAvailable).invoke()
        }
        }


    private fun cancel() {
        check(isPriorityMode) { "This should never happen, cancel() was called before start()" }
        isPriorityMode = false
        isPriorityMode = false
        resetOffsetTracker()

        onStop(0f)
        return onStop(velocity)
    }
    }
}
}
Loading