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

Commit 0b1d06bd authored by omarmt's avatar omarmt
Browse files

OverscrollSpec could be used from TransitionState.Idle state

To ensure consistency, we continue to use the same definitions for
scenes with OverscrollSpec, even when the scene is in Idle state.

Test: atest SceneGestureHandlerTest
Test: atest ElementTest
Bug: 291053278
Flag: NA
Change-Id: Ie6babb06b2fd2bcf202e85c3baf78f261594a64d
parent 9afe2f0a
Loading
Loading
Loading
Loading
+9 −2
Original line number Diff line number Diff line
@@ -791,14 +791,21 @@ internal class SceneNestedScrollHandler(
            )

        fun hasNextScene(amount: Float): Boolean {
            val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene)
            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
                }
            return nextScene != null
            if (nextScene != null) return true

            if (transitionState !is TransitionState.Idle) return false

            val overscrollSpec = layoutImpl.state.transitions.overscrollSpec(scene, orientation)
            return overscrollSpec != null
        }

        val source = this
+78 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.compose.animation.scene
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -613,4 +615,80 @@ class ElementTest {
        assertThat(state.currentOverscrollSpec).isNotNull()
        fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
    }

    @Test
    fun elementTransitionDuringNestedScrollOverscroll() {
        // 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 overscrollTranslateY = 10.dp
        val layoutWidth = 200.dp
        val layoutHeight = 400.dp

        val state =
            MutableSceneTransitionLayoutState(
                initialScene = TestScenes.SceneB,
                transitions =
                    transitions {
                        overscroll(TestScenes.SceneB, Orientation.Vertical) {
                            translate(TestElements.Foo, y = overscrollTranslateY)
                        }
                    }
            )
                as MutableSceneTransitionLayoutStateImpl

        rule.setContent {
            touchSlop = LocalViewConfiguration.current.touchSlop
            SceneTransitionLayout(
                state = state,
                modifier = Modifier.size(layoutWidth, layoutHeight)
            ) {
                scene(TestScenes.SceneA) { Spacer(Modifier.fillMaxSize()) }
                scene(TestScenes.SceneB, userActions = mapOf(Swipe.Up to TestScenes.SceneA)) {
                    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())
                    }
                }
            }
        }

        assertThat(state.currentTransition).isNull()
        assertThat(state.currentOverscrollSpec).isNull()
        val fooElement = rule.onNodeWithTag(TestElements.Foo.testTag, useUnmergedTree = true)
        fooElement.assertTopPositionInRootIsEqualTo(0.dp)

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

        val transition = state.currentTransition
        assertThat(state.currentOverscrollSpec).isNotNull()
        assertThat(transition).isNotNull()
        assertThat(transition!!.progress).isEqualTo(-0.5f)
        fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 0.5f)

        rule.onRoot().performTouchInput {
            // Scroll another 100%
            moveBy(Offset(0f, layoutHeight.toPx()), delayMillis = 1_000)
        }

        // Scroll 150% (Scene B overscroll by 50%)
        assertThat(transition.progress).isEqualTo(-1.5f)
        assertThat(state.currentOverscrollSpec).isNotNull()
        fooElement.assertTopPositionInRootIsEqualTo(overscrollTranslateY * 1.5f)
    }
}
+25 −1
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ class SceneGestureHandlerTest {
        private val testScope: MonotonicClockTestScope,
    ) {
        var canChangeScene: (SceneKey) -> Boolean = { true }
        private val layoutState =
        val layoutState =
            MutableSceneTransitionLayoutStateImpl(
                SceneA,
                EmptyTestTransitions,
@@ -932,4 +932,28 @@ class SceneGestureHandlerTest {
        advanceUntilIdle()
        assertIdle(SceneB)
    }

    @Test
    fun scrollFromIdleWithNoTargetScene_shouldUseOverscrollSpecIfAvailable() = runGestureTest {
        layoutState.transitions = transitions {
            overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
        }
        // Start at scene C.
        navigateToSceneC()

        val scene = layoutState.transitionState.currentScene
        // We should have overscroll spec for scene C
        assertThat(layoutState.transitions.overscrollSpec(scene, Orientation.Vertical)).isNotNull()
        assertThat(layoutState.currentOverscrollSpec).isNull()

        val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
        nestedScroll.scroll(available = downOffset(fractionOfScreen = 0.1f))

        // We scrolled down, under scene C there is nothing, so we can use the overscroll spec
        assertThat(layoutState.currentOverscrollSpec).isNotNull()
        assertThat(layoutState.currentOverscrollSpec?.scene).isEqualTo(SceneC)
        val transition = layoutState.currentTransition
        assertThat(transition).isNotNull()
        assertThat(transition!!.progress).isEqualTo(-0.1f)
    }
}