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

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

Merge changes I2fe88349,I0b0df745 into main

* changes:
  STL flingToScroll should return the consumed velocity
  STL add support for overlays in snapToIdleIfClose()
parents 5bc5d8e3 4fb46342
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -551,8 +551,8 @@ internal class MutableSceneTransitionLayoutStateImpl(
        }

        val shouldSnap =
            (isProgressCloseTo(0f) && transition.currentScene == transition.fromContent) ||
                (isProgressCloseTo(1f) && transition.currentScene == transition.toContent)
            (isProgressCloseTo(0f) && transition.isFromCurrentContent()) ||
                (isProgressCloseTo(1f) && transition.isToCurrentContent())
        return if (shouldSnap) {
            finishAllTransitions()
            true
+17 −2
Original line number Diff line number Diff line
@@ -130,7 +130,7 @@ sealed interface TransitionState {
             * starting a swipe transition to show [overlay] and will be `true` only once the swipe
             * transition is committed.
             */
            protected abstract val isEffectivelyShown: Boolean
            abstract val isEffectivelyShown: Boolean

            init {
                check(
@@ -165,7 +165,7 @@ sealed interface TransitionState {
             * [fromOverlay] by [toOverlay] and will [toOverlay] once the swipe transition is
             * committed.
             */
            protected abstract val effectivelyShownOverlay: OverlayKey
            abstract val effectivelyShownOverlay: OverlayKey

            init {
                check(fromOverlay != toOverlay)
@@ -344,6 +344,21 @@ sealed interface TransitionState {
            }
        }

        /** Whether [fromContent] is effectively the current content of the transition. */
        internal fun isFromCurrentContent() = isCurrentContent(expectedFrom = true)

        /** Whether [toContent] is effectively the current content of the transition. */
        internal fun isToCurrentContent() = isCurrentContent(expectedFrom = false)

        private fun isCurrentContent(expectedFrom: Boolean): Boolean {
            val expectedContent = if (expectedFrom) fromContent else toContent
            return when (this) {
                is ChangeScene -> currentScene == expectedContent
                is ReplaceOverlay -> effectivelyShownOverlay == expectedContent
                is ShowOrHideOverlay -> isEffectivelyShown == (expectedContent == overlay)
            }
        }

        /** Run this transition and return once it is finished. */
        protected abstract suspend fun run()

+9 −5
Original line number Diff line number Diff line
@@ -365,12 +365,16 @@ private class OnStopScopeImpl(private val controller: ScrollController) : OnStop
        flingBehavior: FlingBehavior,
    ): Float {
        return with(flingBehavior) {
            val remainingVelocity =
                object : ScrollScope {
                        override fun scrollBy(pixels: Float): Float {
                            return controller.onScroll(pixels, NestedScrollSource.SideEffect)
                        }
                    }
                    .performFling(initialVelocity)

            // returns the consumed velocity
            initialVelocity - remainingVelocity
        }
    }
}
+39 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestOverlays.OverlayA
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
@@ -187,6 +188,25 @@ class SceneTransitionLayoutStateTest {
        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
    }

    @Test
    fun snapToIdleIfClose_snapToStart_overlays() = runMonotonicClockTest {
        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
        state.startTransitionImmediately(
            animationScope = backgroundScope,
            transition(SceneA, OverlayA, isEffectivelyShown = { false }, progress = { 0.2f }),
        )
        assertThat(state.isTransitioning()).isTrue()

        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
        assertThat(state.isTransitioning()).isTrue()

        // Go to the initial scene if it is close to 0.
        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
        assertThat(state.isTransitioning()).isFalse()
        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA))
    }

    @Test
    fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
@@ -206,6 +226,25 @@ class SceneTransitionLayoutStateTest {
        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneB))
    }

    @Test
    fun snapToIdleIfClose_snapToEnd_overlays() = runMonotonicClockTest {
        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
        state.startTransitionImmediately(
            animationScope = backgroundScope,
            transition(SceneA, OverlayA, isEffectivelyShown = { true }, progress = { 0.8f }),
        )
        assertThat(state.isTransitioning()).isTrue()

        // Ignore the request if the progress is not close to 0 or 1, using the threshold.
        assertThat(state.snapToIdleIfClose(threshold = 0.1f)).isFalse()
        assertThat(state.isTransitioning()).isTrue()

        // Go to the final scene if it is close to 1.
        assertThat(state.snapToIdleIfClose(threshold = 0.2f)).isTrue()
        assertThat(state.isTransitioning()).isFalse()
        assertThat(state.transitionState).isEqualTo(TransitionState.Idle(SceneA, setOf(OverlayA)))
    }

    @Test
    fun snapToIdleIfClose_multipleTransitions() = runMonotonicClockTest {
        val state = MutableSceneTransitionLayoutStateImpl(SceneA, SceneTransitions.Empty)
+7 −4
Original line number Diff line number Diff line
@@ -53,7 +53,8 @@ class PriorityNestedScrollConnectionTest {
        object : FlingBehavior {
            override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
                scrollBy(initialVelocity)
                return initialVelocity / 2f
                // returns the remaining velocity: 1/3 remained + 2/3 consumed
                return initialVelocity / 3f
            }
        }

@@ -207,11 +208,13 @@ class PriorityNestedScrollConnectionTest {

        val consumed = scrollConnection.onPreFling(available = Velocity(2f, 2f))

        assertThat(lastStop).isEqualTo(2f)
        val initialVelocity = 2f
        assertThat(lastStop).isEqualTo(initialVelocity)
        // flingToScroll should try to scroll the content, customFlingBehavior uses the velocity.
        assertThat(lastScroll).isEqualTo(2f)
        // customFlingBehavior returns half of the vertical velocity.
        assertThat(consumed).isEqualTo(Velocity(0f, 1f))
        val remainingVelocity = initialVelocity / 3f
        // customFlingBehavior returns 2/3 of the vertical velocity.
        assertThat(consumed).isEqualTo(Velocity(0f, initialVelocity - remainingVelocity))
    }

    @Test