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

Commit aab63f88 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Expose the current Transition in TransitionBuilder

Bug: 373799480
Test: atest TransitionDslTest
Flag: com.android.systemui.scene_container
Change-Id: Ib815d4bb2e31cd29f2ae12448d27d6b8408f5772
parent 249f87d7
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -387,11 +387,11 @@ internal class MutableSceneTransitionLayoutStateImpl(
        transition.transformationSpec =
            transitions
                .transitionSpec(fromContent, toContent, key = transition.key)
                .transformationSpec()
                .transformationSpec(transition)
        transition.previewTransformationSpec =
            transitions
                .transitionSpec(fromContent, toContent, key = transition.key)
                .previewTransformationSpec()
                .previewTransformationSpec(transition)
        if (orientation != null) {
            transition.updateOverscrollSpecs(
                fromSpec = transitions.overscrollSpec(fromContent, orientation),
+22 −13
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.AnchoredSize
import com.android.compose.animation.scene.transformation.AnchoredTranslate
import com.android.compose.animation.scene.transformation.DrawScale
@@ -191,20 +192,21 @@ interface TransitionSpec {
    fun reversed(): TransitionSpec

    /**
     * The [TransformationSpec] associated to this [TransitionSpec].
     * The [TransformationSpec] associated to this [TransitionSpec] for the given [transition].
     *
     * Note that this is called once whenever a transition associated to this [TransitionSpec] is
     * started.
     */
    fun transformationSpec(): TransformationSpec
    fun transformationSpec(transition: TransitionState.Transition): TransformationSpec

    /**
     * The preview [TransformationSpec] associated to this [TransitionSpec].
     * The preview [TransformationSpec] associated to this [TransitionSpec] for the given
     * [transition].
     *
     * Note that this is called once whenever a transition associated to this [TransitionSpec] is
     * started.
     */
    fun previewTransformationSpec(): TransformationSpec?
    fun previewTransformationSpec(transition: TransitionState.Transition): TransformationSpec?
}

interface TransformationSpec {
@@ -241,7 +243,7 @@ interface TransformationSpec {
                distance = null,
                transformations = emptyList(),
            )
        internal val EmptyProvider = { Empty }
        internal val EmptyProvider = { _: TransitionState.Transition -> Empty }
    }
}

@@ -249,9 +251,13 @@ internal class TransitionSpecImpl(
    override val key: TransitionKey?,
    override val from: ContentKey?,
    override val to: ContentKey?,
    private val previewTransformationSpec: (() -> TransformationSpecImpl)? = null,
    private val reversePreviewTransformationSpec: (() -> TransformationSpecImpl)? = null,
    private val transformationSpec: () -> TransformationSpecImpl,
    private val previewTransformationSpec:
        ((TransitionState.Transition) -> TransformationSpecImpl)? =
        null,
    private val reversePreviewTransformationSpec:
        ((TransitionState.Transition) -> TransformationSpecImpl)? =
        null,
    private val transformationSpec: (TransitionState.Transition) -> TransformationSpecImpl,
) : TransitionSpec {
    override fun reversed(): TransitionSpecImpl {
        return TransitionSpecImpl(
@@ -260,8 +266,8 @@ internal class TransitionSpecImpl(
            to = from,
            previewTransformationSpec = reversePreviewTransformationSpec,
            reversePreviewTransformationSpec = previewTransformationSpec,
            transformationSpec = {
                val reverse = transformationSpec.invoke()
            transformationSpec = { transition ->
                val reverse = transformationSpec.invoke(transition)
                TransformationSpecImpl(
                    progressSpec = reverse.progressSpec,
                    swipeSpec = reverse.swipeSpec,
@@ -272,10 +278,13 @@ internal class TransitionSpecImpl(
        )
    }

    override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()
    override fun transformationSpec(
        transition: TransitionState.Transition
    ): TransformationSpecImpl = transformationSpec.invoke(transition)

    override fun previewTransformationSpec(): TransformationSpecImpl? =
        previewTransformationSpec?.invoke()
    override fun previewTransformationSpec(
        transition: TransitionState.Transition
    ): TransformationSpecImpl? = previewTransformationSpec?.invoke(transition)
}

/** The definition of the overscroll behavior of the [content]. */
+3 −0
Original line number Diff line number Diff line
@@ -156,6 +156,9 @@ interface BaseTransitionBuilder : PropertyTransformationBuilder {

@TransitionDsl
interface TransitionBuilder : BaseTransitionBuilder {
    /** The [TransitionState.Transition] for which we currently compute the transformations. */
    val transition: TransitionState.Transition

    /**
     * The [AnimationSpec] used to animate the associated transition progress from `0` to `1` when
     * the transition is triggered (i.e. it is not gesture-based).
+12 −9
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import androidx.compose.animation.core.spring
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Dp
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.AnchoredSize
import com.android.compose.animation.scene.transformation.AnchoredTranslate
import com.android.compose.animation.scene.transformation.DrawScale
@@ -128,8 +129,11 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
        reversePreview: (TransitionBuilder.() -> Unit)?,
        builder: TransitionBuilder.() -> Unit,
    ): TransitionSpec {
        fun transformationSpec(builder: TransitionBuilder.() -> Unit): TransformationSpecImpl {
            val impl = TransitionBuilderImpl().apply(builder)
        fun transformationSpec(
            transition: TransitionState.Transition,
            builder: TransitionBuilder.() -> Unit,
        ): TransformationSpecImpl {
            val impl = TransitionBuilderImpl(transition).apply(builder)
            return TransformationSpecImpl(
                progressSpec = impl.spec,
                swipeSpec = impl.swipeSpec,
@@ -138,17 +142,15 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
            )
        }

        val previewTransformationSpec = preview?.let { { transformationSpec(it) } }
        val reversePreviewTransformationSpec = reversePreview?.let { { transformationSpec(it) } }
        val transformationSpec = { transformationSpec(builder) }
        val spec =
            TransitionSpecImpl(
                key,
                from,
                to,
                previewTransformationSpec,
                reversePreviewTransformationSpec,
                transformationSpec,
                previewTransformationSpec = preview?.let { { t -> transformationSpec(t, it) } },
                reversePreviewTransformationSpec =
                    reversePreview?.let { { t -> transformationSpec(t, it) } },
                transformationSpec = { t -> transformationSpec(t, builder) },
            )
        transitionSpecs.add(spec)
        return spec
@@ -227,7 +229,8 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
    }
}

internal class TransitionBuilderImpl : BaseTransitionBuilderImpl(), TransitionBuilder {
internal class TransitionBuilderImpl(override val transition: TransitionState.Transition) :
    BaseTransitionBuilderImpl(), TransitionBuilder {
    override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
    override var swipeSpec: SpringSpec<Float>? = null
    override var distance: UserActionDistance? = null
+63 −48
Original line number Diff line number Diff line
@@ -23,11 +23,17 @@ import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.Orientation
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
import com.android.compose.test.transition
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,9 +49,9 @@ class TransitionDslTest {
    @Test
    fun manyTransitions() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB)
            from(TestScenes.SceneB, to = TestScenes.SceneC)
            from(TestScenes.SceneC, to = TestScenes.SceneA)
            from(SceneA, to = SceneB)
            from(SceneB, to = SceneC)
            from(SceneC, to = SceneA)
        }
        assertThat(transitions.transitionSpecs).hasSize(3)
    }
@@ -53,9 +59,9 @@ class TransitionDslTest {
    @Test
    fun toFromBuilders() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB)
            from(TestScenes.SceneB)
            to(TestScenes.SceneC)
            from(SceneA, to = SceneB)
            from(SceneB)
            to(SceneC)
        }

        assertThat(transitions.transitionSpecs)
@@ -65,38 +71,34 @@ class TransitionDslTest {
                    "has (from, to) equal to",
                )
            )
            .containsExactly(
                TestScenes.SceneA to TestScenes.SceneB,
                TestScenes.SceneB to null,
                null to TestScenes.SceneC,
            )
            .containsExactly(SceneA to SceneB, SceneB to null, null to SceneC)
    }

    private fun aToB() = transition(SceneA, SceneB)

    @Test
    fun defaultTransitionSpec() {
        val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) }
        val transformationSpec = transitions.transitionSpecs.single().transformationSpec()
        val transitions = transitions { from(SceneA, to = SceneB) }
        val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
        assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
    }

    @Test
    fun customTransitionSpec() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) }
            from(SceneA, to = SceneB) { spec = tween(durationMillis = 42) }
        }
        val transformationSpec = transitions.transitionSpecs.single().transformationSpec()
        val transformationSpec = transitions.transitionSpecs.single().transformationSpec(aToB())
        assertThat(transformationSpec.progressSpec).isInstanceOf(TweenSpec::class.java)
        assertThat((transformationSpec.progressSpec as TweenSpec).durationMillis).isEqualTo(42)
    }

    @Test
    fun defaultRange() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
        }
        val transitions = transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } }

        val transformations =
            transitions.transitionSpecs.single().transformationSpec().transformations
            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
        assertThat(transformations.size).isEqualTo(1)
        assertThat(transformations.single().range).isEqualTo(null)
    }
@@ -104,7 +106,7 @@ class TransitionDslTest {
    @Test
    fun fractionRange() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB) {
            from(SceneA, to = SceneB) {
                fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
                fractionRange(start = 0.2f) { fade(TestElements.Foo) }
                fractionRange(end = 0.9f) { fade(TestElements.Foo) }
@@ -119,7 +121,7 @@ class TransitionDslTest {
        }

        val transformations =
            transitions.transitionSpecs.single().transformationSpec().transformations
            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
        assertThat(transformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
            .containsExactly(
@@ -133,7 +135,7 @@ class TransitionDslTest {
    @Test
    fun timestampRange() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB) {
            from(SceneA, to = SceneB) {
                spec = tween(500)

                timestampRange(startMillis = 100, endMillis = 300) { fade(TestElements.Foo) }
@@ -150,7 +152,7 @@ class TransitionDslTest {
        }

        val transformations =
            transitions.transitionSpecs.single().transformationSpec().transformations
            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
        assertThat(transformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
            .containsExactly(
@@ -168,7 +170,7 @@ class TransitionDslTest {
    @Test
    fun reversed() {
        val transitions = transitions {
            from(TestScenes.SceneA, to = TestScenes.SceneB) {
            from(SceneA, to = SceneB) {
                spec = tween(500)
                reversed {
                    fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) }
@@ -178,7 +180,7 @@ class TransitionDslTest {
        }

        val transformations =
            transitions.transitionSpecs.single().transformationSpec().transformations
            transitions.transitionSpecs.single().transformationSpec(aToB()).transformations
        assertThat(transformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
            .containsExactly(
@@ -191,8 +193,8 @@ class TransitionDslTest {
    fun defaultReversed() {
        val transitions = transitions {
            from(
                TestScenes.SceneA,
                to = TestScenes.SceneB,
                SceneA,
                to = SceneB,
                preview = { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } },
                reversePreview = {
                    fractionRange(start = 0.5f, end = 0.6f) { fade(TestElements.Foo) }
@@ -206,10 +208,9 @@ class TransitionDslTest {

        // Fetch the transition from B to A, which will automatically reverse the transition from A
        // to B we defined.
        val transitionSpec =
            transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA, key = null)
        val transitionSpec = transitions.transitionSpec(from = SceneB, to = SceneA, key = null)

        val transformations = transitionSpec.transformationSpec().transformations
        val transformations = transitionSpec.transformationSpec(aToB()).transformations

        assertThat(transformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -218,7 +219,8 @@ class TransitionDslTest {
                TransformationRange(start = 1f - 300 / 500f, end = 1f - 100 / 500f),
            )

        val previewTransformations = transitionSpec.previewTransformationSpec()?.transformations
        val previewTransformations =
            transitionSpec.previewTransformationSpec(aToB())?.transformations

        assertThat(previewTransformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -229,8 +231,8 @@ class TransitionDslTest {
    fun defaultPredictiveBack() {
        val transitions = transitions {
            from(
                TestScenes.SceneA,
                to = TestScenes.SceneB,
                SceneA,
                to = SceneB,
                preview = { fractionRange(start = 0.1f, end = 0.8f) { fade(TestElements.Foo) } },
            ) {
                spec = tween(500)
@@ -243,12 +245,12 @@ class TransitionDslTest {
        // transition despite it not having the PredictiveBack key set.
        val transitionSpec =
            transitions.transitionSpec(
                from = TestScenes.SceneA,
                to = TestScenes.SceneB,
                from = SceneA,
                to = SceneB,
                key = TransitionKey.PredictiveBack,
            )

        val transformations = transitionSpec.transformationSpec().transformations
        val transformations = transitionSpec.transformationSpec(aToB()).transformations

        assertThat(transformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -257,7 +259,8 @@ class TransitionDslTest {
                TransformationRange(start = 100 / 500f, end = 300 / 500f),
            )

        val previewTransformations = transitionSpec.previewTransformationSpec()?.transformations
        val previewTransformations =
            transitionSpec.previewTransformationSpec(aToB())?.transformations

        assertThat(previewTransformations)
            .comparingElementsUsing(TRANSFORMATION_RANGE)
@@ -271,10 +274,10 @@ class TransitionDslTest {
        val transitions = transitions {
            defaultSwipeSpec = defaultSpec

            from(TestScenes.SceneA, to = TestScenes.SceneB) {
            from(SceneA, to = SceneB) {
                // Default swipe spec.
            }
            from(TestScenes.SceneA, to = TestScenes.SceneC) { swipeSpec = specFromAToC }
            from(SceneA, to = SceneC) { swipeSpec = specFromAToC }
        }

        assertThat(transitions.defaultSwipeSpec).isSameInstanceAs(defaultSpec)
@@ -282,8 +285,8 @@ class TransitionDslTest {
        // A => B does not have a custom spec.
        assertThat(
                transitions
                    .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneB, key = null)
                    .transformationSpec()
                    .transitionSpec(from = SceneA, to = SceneB, key = null)
                    .transformationSpec(aToB())
                    .swipeSpec
            )
            .isNull()
@@ -291,8 +294,8 @@ class TransitionDslTest {
        // A => C has a custom swipe spec.
        assertThat(
                transitions
                    .transitionSpec(from = TestScenes.SceneA, to = TestScenes.SceneC, key = null)
                    .transformationSpec()
                    .transitionSpec(from = SceneA, to = SceneC, key = null)
                    .transformationSpec(transition(from = SceneA, to = SceneC))
                    .swipeSpec
            )
            .isSameInstanceAs(specFromAToC)
@@ -301,7 +304,7 @@ class TransitionDslTest {
    @Test
    fun overscrollSpec() {
        val transitions = transitions {
            overscroll(TestScenes.SceneA, Orientation.Vertical) {
            overscroll(SceneA, Orientation.Vertical) {
                translate(TestElements.Bar, x = { 1f }, y = { 2f })
            }
        }
@@ -313,9 +316,7 @@ class TransitionDslTest {

    @Test
    fun overscrollSpec_for_overscrollDisabled() {
        val transitions = transitions {
            overscrollDisabled(TestScenes.SceneA, Orientation.Vertical)
        }
        val transitions = transitions { overscrollDisabled(SceneA, Orientation.Vertical) }
        val overscrollSpec = transitions.overscrollSpecs.single()
        assertThat(overscrollSpec.transformationSpec.transformations).isEmpty()
    }
@@ -323,8 +324,22 @@ class TransitionDslTest {
    @Test
    fun overscrollSpec_throwIfTransformationsIsEmpty() {
        assertThrows(IllegalStateException::class.java) {
            transitions { overscroll(TestScenes.SceneA, Orientation.Vertical) {} }
            transitions { overscroll(SceneA, Orientation.Vertical) {} }
        }
    }

    @Test
    fun transitionIsPassedToBuilder() = runTest {
        var transitionPassedToBuilder: TransitionState.Transition? = null
        val state =
            MutableSceneTransitionLayoutState(
                SceneA,
                transitions { from(SceneA, to = SceneB) { transitionPassedToBuilder = transition } },
            )

        val transition = aToB()
        state.startTransitionImmediately(animationScope = backgroundScope, transition)
        assertThat(transitionPassedToBuilder).isSameInstanceAs(transition)
    }

    companion object {