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

Commit 593cfcf1 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere
Browse files

Expose ElementKey.currentAlpha() in ContentScope

This CL exposes a new ElementKey.currentAlpha() API in ContentScope that
can be used to get the computed alpha for a given element, reusing the
same logic used by Modifier.element() during transitions. This can be
used to share this logic with non compose elements like NSSL.

Bug: 437121774
Test: atest ContentTest
Flag: EXEMPT new API
Change-Id: Ic2bb2062f0f358309727a6c38545379b1d334f20
parent 75aa2493
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -692,7 +692,7 @@ internal class ElementNode(
}

/** The [TransitionState] that we should consider for [element]. */
private fun elementState(
internal fun elementState(
    layoutImpl: SceneTransitionLayoutImpl,
    element: Element,
    transitionStates: List<List<TransitionState>>,
@@ -1127,7 +1127,7 @@ private fun isElementOpaque(
 * [isElementOpaque] is checked during placement and we don't want to read the transition progress
 * in that phase.
 */
private fun elementAlpha(
internal fun elementAlpha(
    layoutImpl: SceneTransitionLayoutImpl,
    element: Element,
    transition: TransitionState.Transition?,
+9 −0
Original line number Diff line number Diff line
@@ -312,6 +312,15 @@ interface BaseContentScope : ElementStateScope {
    fun Modifier.disableSwipesWhenScrolling(
        bounds: NestedScrollableBound = NestedScrollableBound.Any
    ): Modifier

    /**
     * Return the alpha of [this] element in this content, given the current transition state and
     * transition transformations (e.g. fade).
     *
     * Important: This should *not* be read during composition and should instead be read during
     * layout, drawing or in a LaunchedEffect.
     */
    fun ElementKey.currentAlpha(): Float?
}

@Stable
+14 −0
Original line number Diff line number Diff line
@@ -76,8 +76,12 @@ import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.ValueKey
import com.android.compose.animation.scene.animateSharedValueAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.effect.GestureEffect
import com.android.compose.animation.scene.element
import com.android.compose.animation.scene.elementAlpha
import com.android.compose.animation.scene.elementState
import com.android.compose.animation.scene.getAllNestedTransitionStates
import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
import com.android.compose.gesture.NestedScrollControlState
import com.android.compose.gesture.NestedScrollableBound
@@ -422,6 +426,16 @@ internal class ContentScopeImpl(
                it.fromContent == content || it.toContent == content
            }
    }

    override fun ElementKey.currentAlpha(): Float? {
        val element = layoutImpl.elements[this] ?: return null
        val stateInContent = element.stateByContent[contentKey] ?: return null
        val elementState =
            elementState(layoutImpl, element, getAllNestedTransitionStates(layoutImpl))
                ?: return null
        val transition = elementState as? TransitionState.Transition
        return elementAlpha(layoutImpl, element, transition, stateInContent)
    }
}

/** A [LifecycleOwner] that follows its [parentLifecycle] but is capped at [maxLifecycleState]. */
+53 −0
Original line number Diff line number Diff line
@@ -24,11 +24,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalViewConfiguration
@@ -380,4 +382,55 @@ class ContentTest {
        assertThat(nestedMiddle.ancestors).containsExactly(outerState)
        assertThat(nestedInner.ancestors).containsExactly(outerState, middleState).inOrder()
    }

    @Test
    fun currentElementAlpha() {
        var lastAlpha: Float? = null

        val state =
            rule.runOnUiThread {
                MutableSceneTransitionLayoutStateForTests(
                    SceneA,
                    transitions =
                        transitions { from(SceneA, to = SceneB) { fade(TestElements.Foo) } },
                )
            }
        val scope =
            rule.setContentAndCreateMainScope {
                SceneTransitionLayout(state) {
                    scene(SceneA) {}
                    scene(SceneB) {
                        LaunchedEffect(Unit) {
                            snapshotFlow { TestElements.Foo.currentAlpha() }
                                .collect { lastAlpha = it }
                        }

                        Box(Modifier.element(TestElements.Foo).fillMaxSize())
                    }
                }
            }

        assertThat(lastAlpha).isNull()

        var progress by mutableStateOf(0f)
        scope.launch { state.startTransition(transition(SceneA, SceneB, progress = { progress })) }
        rule.waitForIdle()
        assertThat(lastAlpha).isWithin(0.01f).of(0f)

        progress = 0.25f
        rule.waitForIdle()
        assertThat(lastAlpha).isWithin(0.01f).of(0.25f)

        progress = 0.5f
        rule.waitForIdle()
        assertThat(lastAlpha).isWithin(0.01f).of(0.5f)

        progress = 0.75f
        rule.waitForIdle()
        assertThat(lastAlpha).isWithin(0.01f).of(0.75f)

        progress = 1f
        rule.waitForIdle()
        assertThat(lastAlpha).isWithin(0.01f).of(1f)
    }
}