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

Commit f2dc6fb4 authored by Omar Miatello's avatar Omar Miatello
Browse files

MM: Remove MotionBuilderContext from MM Modifier APIs 1/3

This change refactors `VerticalFadeContentRevealModifier`,
`VerticalTactileSurfaceRevealModifier`, and `overscrollToDismiss` to
simplify their APIs and improve encapsulation.

Previously, these modifiers required callers to manually create and pass a
`MotionBuilderContext`. This added boilerplate to the call sites and
coupled the implementation to the consumer.

By implementing `CompositionLocalConsumerModifierNode`, the underlying
nodes can now read dependencies like `LocalDensity` and
`LocalMotionScheme` directly from composition locals.

The primary changes are:
- The modifier nodes now implement `CompositionLocalConsumerModifierNode`
  to resolve their own dependencies.
- The `motionBuilderContext` parameter has been removed from the public
  modifier functions, resulting in a cleaner API.

Test: Manually tested in the demo app
Bug: 392535471
Flag: com.android.systemui.scene_container
Change-Id: Ib343a1a08ed708e1922c2c2e1e0b5d10c6358b95
parent e0d16319
Loading
Loading
Loading
Loading
+15 −39
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
@@ -39,43 +40,22 @@ import com.android.mechanics.debug.DebugMotionValueNode
import com.android.mechanics.effects.FixedValue
import com.android.mechanics.spec.Mapping
import com.android.mechanics.spec.MotionSpec
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.ComposeMotionBuilderContext
import com.android.mechanics.spec.builder.effectsMotionSpec
import com.android.mechanics.spec.builder.fixedEffectsValueSpec
import com.android.mechanics.spec.builder.motionBuilderContext

/**
 * This component remains hidden until it reach its target height.
 *
 * TODO: Once b/413283893 is done, [motionBuilderContext] can be read internally via
 *   CompositionLocalConsumerModifierNode, instead of passing it.
 */
fun Modifier.verticalFadeContentReveal(
    motionBuilderContext: MotionBuilderContext,
    deltaY: Float = 0f,
    label: String? = null,
): Modifier =
    this then
        FadeContentRevealElement(
            motionBuilderContext = motionBuilderContext,
            deltaY = deltaY,
            label = label,
        )
/** This component remains hidden until it reach its target height. */
fun Modifier.verticalFadeContentReveal(deltaY: Float = 0f, label: String? = null): Modifier =
    this then FadeContentRevealElement(deltaY = deltaY, label = label)

private data class FadeContentRevealElement(
    val motionBuilderContext: MotionBuilderContext,
    val deltaY: Float,
    val label: String?,
) : ModifierNodeElement<FadeContentRevealNode>() {
private data class FadeContentRevealElement(val deltaY: Float, val label: String?) :
    ModifierNodeElement<FadeContentRevealNode>() {
    override fun create(): FadeContentRevealNode =
        FadeContentRevealNode(
            motionBuilderContext = motionBuilderContext,
            deltaY = deltaY,
            label = label,
        )
        FadeContentRevealNode(deltaY = deltaY, label = label)

    override fun update(node: FadeContentRevealNode) {
        check(node.deltaY == deltaY) { "Cannot update deltaY from ${node.deltaY} to $deltaY" }
        node.update(motionBuilderContext = motionBuilderContext)
    }

    override fun InspectorInfo.inspectableProperties() {
@@ -85,11 +65,8 @@ private data class FadeContentRevealElement(
    }
}

private class FadeContentRevealNode(
    private var motionBuilderContext: MotionBuilderContext,
    val deltaY: Float,
    private val label: String?,
) : DelegatingNode(), ApproachLayoutModifierNode {
private class FadeContentRevealNode(val deltaY: Float, private val label: String?) :
    DelegatingNode(), ApproachLayoutModifierNode, CompositionLocalConsumerModifierNode {
    // These properties are calculated during the lookahead pass (`lookAheadMeasure`) to
    // orchestrate the reveal animation. They are guaranteed to be updated before `approachMeasure`
    // is called.
@@ -106,12 +83,11 @@ private class FadeContentRevealNode(
     */
    private lateinit var motionDriver: MotionDriver

    private lateinit var motionBuilderContext: ComposeMotionBuilderContext

    override fun onAttach() {
        motionDriver = findMotionDriver()
    }

    fun update(motionBuilderContext: MotionBuilderContext) {
        this.motionBuilderContext = motionBuilderContext
        motionBuilderContext = motionBuilderContext()
    }

    override fun onDetach() {
+9 −16
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import androidx.compose.ui.layout.ApproachMeasureScope
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.DelegatingNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.platform.InspectorInfo
@@ -48,27 +49,23 @@ import com.android.mechanics.debug.DebugMotionValueNode
import com.android.mechanics.effects.RevealOnThreshold
import com.android.mechanics.spec.Mapping
import com.android.mechanics.spec.MotionSpec
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.ComposeMotionBuilderContext
import com.android.mechanics.spec.builder.fixedSpatialValueSpec
import com.android.mechanics.spec.builder.motionBuilderContext
import com.android.mechanics.spec.builder.spatialMotionSpec
import kotlin.math.roundToInt

/**
 * This component remains hidden until its target height meets a minimum threshold. At that point,
 * it reveals itself by animating its height from 0 to the current target height.
 *
 * TODO: Once b/413283893 is done, [motionBuilderContext] can be read internally via
 *   CompositionLocalConsumerModifierNode, instead of passing it.
 */
fun Modifier.verticalTactileSurfaceReveal(
    motionBuilderContext: MotionBuilderContext,
    deltaY: Float = 0f,
    revealOnThreshold: RevealOnThreshold = DefaultRevealOnThreshold,
    label: String? = null,
): Modifier =
    this then
        VerticalTactileSurfaceRevealElement(
            motionBuilderContext = motionBuilderContext,
            deltaY = deltaY,
            revealOnThreshold = revealOnThreshold,
            label = label,
@@ -77,14 +74,12 @@ fun Modifier.verticalTactileSurfaceReveal(
private val DefaultRevealOnThreshold = RevealOnThreshold()

private data class VerticalTactileSurfaceRevealElement(
    val motionBuilderContext: MotionBuilderContext,
    val deltaY: Float,
    val revealOnThreshold: RevealOnThreshold,
    val label: String?,
) : ModifierNodeElement<VerticalTactileSurfaceRevealNode>() {
    override fun create(): VerticalTactileSurfaceRevealNode =
        VerticalTactileSurfaceRevealNode(
            motionBuilderContext = motionBuilderContext,
            deltaY = deltaY,
            revealOnThreshold = revealOnThreshold,
            label = label,
@@ -92,10 +87,7 @@ private data class VerticalTactileSurfaceRevealElement(

    override fun update(node: VerticalTactileSurfaceRevealNode) {
        check(node.deltaY == deltaY) { "Cannot update deltaY from ${node.deltaY} to $deltaY" }
        node.update(
            motionBuilderContext = motionBuilderContext,
            revealOnThreshold = revealOnThreshold,
        )
        node.update(revealOnThreshold = revealOnThreshold)
    }

    override fun InspectorInfo.inspectableProperties() {
@@ -107,11 +99,10 @@ private data class VerticalTactileSurfaceRevealElement(
}

private class VerticalTactileSurfaceRevealNode(
    private var motionBuilderContext: MotionBuilderContext,
    val deltaY: Float,
    private var revealOnThreshold: RevealOnThreshold,
    private val label: String?,
) : DelegatingNode(), ApproachLayoutModifierNode {
) : DelegatingNode(), ApproachLayoutModifierNode, CompositionLocalConsumerModifierNode {
    // These properties are calculated during the lookahead pass (`lookAheadMeasure`) to
    // orchestrate the reveal animation. They are guaranteed to be updated before `approachMeasure`
    // is called.
@@ -128,12 +119,14 @@ private class VerticalTactileSurfaceRevealNode(
     */
    private lateinit var motionDriver: MotionDriver

    private lateinit var motionBuilderContext: ComposeMotionBuilderContext

    override fun onAttach() {
        motionDriver = findMotionDriver()
        motionBuilderContext = motionBuilderContext()
    }

    fun update(motionBuilderContext: MotionBuilderContext, revealOnThreshold: RevealOnThreshold) {
        this.motionBuilderContext = motionBuilderContext
    fun update(revealOnThreshold: RevealOnThreshold) {
        this.revealOnThreshold = revealOnThreshold
    }

+1 −5
Original line number Diff line number Diff line
@@ -56,7 +56,6 @@ import com.android.compose.animation.scene.transitions
import com.android.mechanics.debug.LocalMotionValueDebugController
import com.android.mechanics.debug.MotionValueDebugController
import com.android.mechanics.spec.builder.MotionBuilderContext
import com.android.mechanics.spec.builder.rememberMotionBuilderContext
import com.android.mechanics.testing.FakeMotionSpecBuilderContext
import org.junit.Rule
import org.junit.Test
@@ -122,10 +121,7 @@ class VerticalTactileSurfaceRevealModifierTest(val useOverlays: Boolean) :
                                            else -> Color.Blue
                                        },
                                    )
                                    .verticalTactileSurfaceReveal(
                                        motionBuilderContext = rememberMotionBuilderContext(),
                                        label = "box$it",
                                    )
                                    .verticalTactileSurfaceReveal(label = "box$it")
                                    .size(50.dp)
                            )
                        }
+18 −2
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MotionScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Density
import com.android.mechanics.spring.SpringParameters
@@ -32,7 +34,8 @@ import com.android.mechanics.spring.SpringParameters
 *
 * See go/motion-system.
 *
 * @see rememberMotionBuilderContext for Compose
 * @see rememberMotionBuilderContext for Compose (in composition)
 * @see motionBuilderContext for Compose (in Modifier.Node)
 * @see standardViewMotionBuilderContext for Views
 * @see expressiveViewMotionBuilderContext for Views
 */
@@ -84,7 +87,20 @@ fun rememberMotionBuilderContext(): MotionBuilderContext {
    return remember(density, motionScheme) { ComposeMotionBuilderContext(motionScheme, density) }
}

class ComposeMotionBuilderContext(motionScheme: MotionScheme, density: Density) :
/**
 * [MotionBuilderContext] for building motion specs in a [androidx.compose.ui.Modifier.Node].
 *
 * This should be read when the node is attached.
 */
fun CompositionLocalConsumerModifierNode.motionBuilderContext(): ComposeMotionBuilderContext {
    return ComposeMotionBuilderContext(
        motionScheme = currentValueOf(MaterialTheme.LocalMotionScheme),
        density = currentValueOf(LocalDensity),
    )
}

class ComposeMotionBuilderContext
internal constructor(motionScheme: MotionScheme, density: Density) :
    MotionBuilderContext, Density by density {

    override val spatial =