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

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

Merge "Revert "Expose startedPosition and pointersDown to NestedScrollHandler"" into main

parents a8334693 b21a315e
Loading
Loading
Loading
Loading
+24 −28
Original line number Diff line number Diff line
@@ -913,7 +913,6 @@ internal class NestedScrollHandlerImpl(
    private val topOrLeftBehavior: NestedScrollBehavior,
    private val bottomOrRightBehavior: NestedScrollBehavior,
    private val isExternalOverscrollGesture: () -> Boolean,
    private val pointersInfo: () -> PointersInfo,
) {
    private val layoutState = layoutImpl.state
    private val draggableHandler = layoutImpl.draggableHandler(orientation)
@@ -925,13 +924,6 @@ internal class NestedScrollHandlerImpl(
        // moving on to the next scene.
        var canChangeScene = false

        fun hasNextScene(amount: Float): Boolean {
            val transitionState = layoutState.transitionState
            val scene = transitionState.currentScene
            val fromScene = layoutImpl.scene(scene)
            val nextScene =
                when {
                    amount < 0f -> {
        val actionUpOrLeft =
            Swipe(
                direction =
@@ -939,11 +931,9 @@ internal class NestedScrollHandlerImpl(
                        Orientation.Horizontal -> SwipeDirection.Left
                        Orientation.Vertical -> SwipeDirection.Up
                    },
                                pointerCount = pointersInfo().pointersDown,
                pointerCount = 1,
            )
                        fromScene.userActions[actionUpOrLeft]
                    }
                    amount > 0f -> {

        val actionDownOrRight =
            Swipe(
                direction =
@@ -951,10 +941,17 @@ internal class NestedScrollHandlerImpl(
                        Orientation.Horizontal -> SwipeDirection.Right
                        Orientation.Vertical -> SwipeDirection.Down
                    },
                                pointerCount = pointersInfo().pointersDown,
                pointerCount = 1,
            )
                        fromScene.userActions[actionDownOrRight]
                    }

        fun hasNextScene(amount: Float): Boolean {
            val transitionState = layoutState.transitionState
            val scene = transitionState.currentScene
            val fromScene = layoutImpl.scene(scene)
            val nextScene =
                when {
                    amount < 0f -> fromScene.userActions[actionUpOrLeft]
                    amount > 0f -> fromScene.userActions[actionDownOrRight]
                    else -> null
                }
            if (nextScene != null) return true
@@ -1052,11 +1049,10 @@ internal class NestedScrollHandlerImpl(
            canContinueScroll = { true },
            canScrollOnFling = false,
            onStart = { offsetAvailable ->
                val pointers = pointersInfo()
                dragController =
                    draggableHandler.onDragStarted(
                        pointersDown = pointers.pointersDown,
                        startedPosition = pointers.startedPosition,
                        pointersDown = 1,
                        startedPosition = null,
                        overSlop = if (isIntercepting) 0f else offsetAvailable,
                    )
            },
+11 −53
Original line number Diff line number Diff line
@@ -18,21 +18,12 @@ package com.android.compose.animation.scene

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.util.fastMap
import androidx.compose.ui.util.fastReduce
import com.android.compose.nestedscroll.PriorityNestedScrollConnection
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.isActive

/**
 * Defines the behavior of the [SceneTransitionLayout] when a scrollable component is scrolled.
@@ -130,11 +121,6 @@ private data class NestedScrollToSceneElement(
    }
}

internal data class PointersInfo(
    val pointersDown: Int,
    val startedPosition: Offset,
)

private class NestedScrollToSceneNode(
    layoutImpl: SceneTransitionLayoutImpl,
    orientation: Orientation,
@@ -149,48 +135,22 @@ private class NestedScrollToSceneNode(
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
            pointersInfo = pointerInfo()
        )

    private var lastPointers: List<PointerInputChange>? = null

    private fun pointerInfo(): () -> PointersInfo = {
        val pointers =
            requireNotNull(lastPointers) { "NestedScroll API was called before PointerInput API" }
        PointersInfo(
            pointersDown = pointers.size,
            startedPosition = pointers.fastMap { it.position }.fastReduce { a, b -> (a + b) / 2f },
    private var nestedScrollNode: DelegatableNode =
        nestedScrollModifierNode(
            connection = priorityNestedScrollConnection,
            dispatcher = null,
        )
    }

    private val pointerInputHandler: suspend PointerInputScope.() -> Unit = {
        coroutineScope {
            awaitPointerEventScope {
                // Await this scope to guarantee that the PointerInput API receives touch events
                // before the NestedScroll API.
    override fun onAttach() {
        delegate(nestedScrollNode)

                try {
                    while (isActive) {
                        // During the initial phase, we receive the event after our ancestors.
                        lastPointers = awaitPointerEvent(PointerEventPass.Initial).changes
    }
                } finally {
                    // Clean up the nested scroll connection

    override fun onDetach() {
        // Make sure we reset the scroll connection when this modifier is removed from composition
        priorityNestedScrollConnection.reset()
                    undelegate(nestedScrollNode)
                }
    }
        }
    }

    private val pointerInputNode = delegate(SuspendingPointerInputModifierNode(pointerInputHandler))

    private var nestedScrollNode: DelegatableNode =
        nestedScrollModifierNode(
            connection = priorityNestedScrollConnection,
            dispatcher = null,
        )

    fun update(
        layoutImpl: SceneTransitionLayoutImpl,
@@ -201,7 +161,7 @@ private class NestedScrollToSceneNode(
    ) {
        // Clean up the old nested scroll connection
        priorityNestedScrollConnection.reset()
        pointerInputNode.resetPointerInputHandler()
        undelegate(nestedScrollNode)

        // Create a new nested scroll connection
        priorityNestedScrollConnection =
@@ -211,13 +171,13 @@ private class NestedScrollToSceneNode(
                topOrLeftBehavior = topOrLeftBehavior,
                bottomOrRightBehavior = bottomOrRightBehavior,
                isExternalOverscrollGesture = isExternalOverscrollGesture,
                pointersInfo = pointerInfo(),
            )
        nestedScrollNode =
            nestedScrollModifierNode(
                connection = priorityNestedScrollConnection,
                dispatcher = null,
            )
        delegate(nestedScrollNode)
    }
}

@@ -227,7 +187,6 @@ private fun scenePriorityNestedScrollConnection(
    topOrLeftBehavior: NestedScrollBehavior,
    bottomOrRightBehavior: NestedScrollBehavior,
    isExternalOverscrollGesture: () -> Boolean,
    pointersInfo: () -> PointersInfo,
) =
    NestedScrollHandlerImpl(
            layoutImpl = layoutImpl,
@@ -235,6 +194,5 @@ private fun scenePriorityNestedScrollConnection(
            topOrLeftBehavior = topOrLeftBehavior,
            bottomOrRightBehavior = bottomOrRightBehavior,
            isExternalOverscrollGesture = isExternalOverscrollGesture,
            pointersInfo = pointersInfo,
        )
        .connection
+1 −2
Original line number Diff line number Diff line
@@ -113,8 +113,7 @@ class DraggableHandlerTest {
                    orientation = draggableHandler.orientation,
                    topOrLeftBehavior = nestedScrollBehavior,
                    bottomOrRightBehavior = nestedScrollBehavior,
                    isExternalOverscrollGesture = { isExternalOverscrollGesture },
                    pointersInfo = { PointersInfo(pointersDown = 1, startedPosition = Offset.Zero) }
                    isExternalOverscrollGesture = { isExternalOverscrollGesture }
                )
                .connection

+0 −74
Original line number Diff line number Diff line
@@ -838,80 +838,6 @@ class ElementTest {
        fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
    }

    @Test
    fun elementTransitionDuringNestedScrollWith2Pointers() {
        // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
        // detected as a drag event.
        var touchSlop = 0f
        val translateY = 10.dp
        val layoutWidth = 200.dp
        val layoutHeight = 400.dp

        val state =
            rule.runOnUiThread {
                MutableSceneTransitionLayoutState(
                    initialScene = SceneA,
                    transitions =
                        transitions {
                            from(SceneA, to = SceneB) {
                                translate(TestElements.Foo, y = translateY)
                            }
                        },
                )
                    as MutableSceneTransitionLayoutStateImpl
            }

        rule.setContent {
            touchSlop = LocalViewConfiguration.current.touchSlop
            SceneTransitionLayout(
                state = state,
                modifier = Modifier.size(layoutWidth, layoutHeight)
            ) {
                scene(
                    SceneA,
                    userActions = mapOf(Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB)
                ) {
                    Box(
                        Modifier
                            // Unconsumed scroll gesture will be intercepted by STL
                            .verticalNestedScrollToScene()
                            // A scrollable that does not consume the scroll gesture
                            .scrollable(
                                rememberScrollableState(consumeScrollDelta = { 0f }),
                                Orientation.Vertical
                            )
                            .fillMaxSize()
                    ) {
                        Spacer(Modifier.element(TestElements.Foo).fillMaxSize())
                    }
                }
                scene(SceneB) { Spacer(Modifier.fillMaxSize()) }
            }
        }

        assertThat(state.transitionState).isIdle()
        val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag)
        fooElement.assertTopPositionInRootIsEqualTo(0.dp)

        // Swipe down with 2 pointers by half of verticalSwipeDistance.
        rule.onRoot().performTouchInput {
            val middleTop = Offset((layoutWidth / 2).toPx(), 0f)
            repeat(2) { i -> down(pointerId = i, middleTop) }
            repeat(2) { i ->
                // Scroll 50%
                moveBy(
                    pointerId = i,
                    delta = Offset(0f, touchSlop + layoutHeight.toPx() * 0.5f),
                    delayMillis = 1_000,
                )
            }
        }

        val transition = assertThat(state.transitionState).isTransition()
        assertThat(transition).hasProgress(0.5f)
        fooElement.assertTopPositionInRootIsEqualTo(translateY * 0.5f)
    }

    @Test
    fun elementTransitionWithDistanceDuringOverscroll() {
        val layoutWidth = 200.dp