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

Commit cff3bc53 authored by omarmt's avatar omarmt
Browse files

Add snapToIdleIfClose in BaseSceneTransitionLayoutState

Check if a transition is in progress. If the progress value is near 0 or
 1, immediately snap to the closest scene.
We ignore the currentScene, assuming the user wants to go to the closest
 scene.

Test: atest SceneTransitionLayoutStateTest
Bug: 317063114
Flag: NA
Change-Id: I874adb96813a3071fa0a782bb9b1b355d6ef0f2c
parent d9797f55
Loading
Loading
Loading
Loading
+10 −18
Original line number Diff line number Diff line
@@ -701,27 +701,19 @@ internal class SceneNestedScrollHandler(
                        gestureHandler.shouldImmediatelyIntercept(startedPosition = null)
                if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false

                val swipeTransition = gestureHandler.swipeTransition
                val progress = swipeTransition.progress
                val threshold = layoutImpl.transitionInterceptionThreshold
                fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold

                // The transition is always between 0 and 1. If it is close to either of these
                // intervals, we want to go directly to the TransitionState.Idle.
                // The progress value can go beyond this range in the case of overscroll.
                val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
                if (shouldSnapToIdle) {
                    swipeTransition.cancelOffsetAnimation()
                    layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
                val hasSnappedToIdle = layoutState.snapToIdleIfClose(threshold)
                if (hasSnappedToIdle) {
                    // If the current swipe transition is closed to 0f or 1f, then we want to
                    // interrupt the transition (snapping it to Idle) and scroll the list.
                    return@PriorityNestedScrollConnection false
                }

                // Start only if we cannot consume this event
                val canStart = !shouldSnapToIdle
                if (canStart) {
                // If the current swipe transition is *not* closed to 0f or 1f, then we want the
                // scroll events to intercept the current transition to continue the scene
                // transition.
                isIntercepting = true
                }

                canStart
                true
            },
            canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                val behavior: NestedScrollBehavior =
+25 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import kotlin.math.absoluteValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel

@@ -236,6 +237,30 @@ internal abstract class BaseSceneTransitionLayoutState(initialScene: SceneKey) :
            transitionState = TransitionState.Idle(idleScene)
        }
    }

    /**
     * Check if a transition is in progress. If the progress value is near 0 or 1, immediately snap
     * to the closest scene.
     *
     * @return true if snapped to the closest scene.
     */
    internal fun snapToIdleIfClose(threshold: Float): Boolean {
        val transition = currentTransition ?: return false
        val progress = transition.progress
        fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold

        return when {
            isProgressCloseTo(0f) -> {
                finishTransition(transition, transition.fromScene)
                true
            }
            isProgressCloseTo(1f) -> {
                finishTransition(transition, transition.toScene)
                true
            }
            else -> false
        }
    }
}

/**
+38 −0
Original line number Diff line number Diff line
@@ -158,4 +158,42 @@ class SceneTransitionLayoutStateTest {
            .isNotNull()
        assertThat(state.transformationSpec.transformations).hasSize(2)
    }

    @Test
    fun snapToIdleIfClose_snapToStart() = runMonotonicClockTest {
        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
        state.startTransition(
            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.2f }),
            transitionKey = null
        )
        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(TestScenes.SceneA))
    }

    @Test
    fun snapToIdleIfClose_snapToEnd() = runMonotonicClockTest {
        val state = MutableSceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
        state.startTransition(
            transition(from = TestScenes.SceneA, to = TestScenes.SceneB, progress = { 0.8f }),
            transitionKey = null
        )
        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(TestScenes.SceneB))
    }
}