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

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

Clean-up ExpandableController

This CL cleans up ExpandableController so that we don't expose State
properties and instead expose normal properties (invisibly backed by a
State). This is because it's general good practice to not expose State
parameters, and this old code was adhering to this practice.

Bug: 285250939
Test: N/A
Flag: EXEMPT simple clean-up
Change-Id: If0c48e56cadf43b01f9909a2642c37ac72e50d63
parent b9e8a0ea
Loading
Loading
Loading
Loading
+10 −15
Original line number Diff line number Diff line
@@ -218,26 +218,23 @@ fun Expandable(
            // animating.
            AnimatedContentInOverlay(
                color,
                controller.boundsInComposeViewRoot.value.size,
                controller.animatorState,
                controller.overlay.value
                controller.boundsInComposeViewRoot.size,
                controller.overlay
                    ?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."),
                controller,
                wrappedContent,
                controller.composeViewRoot,
                { controller.currentComposeViewInOverlay.value = it },
                { controller.currentComposeViewInOverlay = it },
                controller.density,
            )
        }
        controller.isDialogShowing.value -> {
        controller.isDialogShowing -> {
            Box(
                modifier
                    .updateExpandableSize()
                    .then(minInteractiveSizeModifier)
                    .drawWithContent { /* Don't draw anything when the dialog is shown. */ }
                    .onGloballyPositioned {
                        controller.boundsInComposeViewRoot.value = it.boundsInRoot()
                    }
                    .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
            ) {
                wrappedContent(controller.expandable)
            }
@@ -250,9 +247,7 @@ fun Expandable(
                    .then(clickModifier(controller, onClick, interactionSource))
                    .background(color, shape)
                    .border(controller)
                    .onGloballyPositioned {
                        controller.boundsInComposeViewRoot.value = it.boundsInRoot()
                    }
                    .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
            ) {
                wrappedContent(controller.expandable)
            }
@@ -309,7 +304,6 @@ private fun clickModifier(
private fun AnimatedContentInOverlay(
    color: Color,
    sizeInOriginalLayout: Size,
    animatorState: State<TransitionAnimator.State?>,
    overlay: ViewGroupOverlay,
    controller: ExpandableControllerImpl,
    content: @Composable (Expandable) -> Unit,
@@ -332,7 +326,7 @@ private fun AnimatedContentInOverlay(
                    // so that its content is laid out exactly the same way.
                    .requiredSize(with(density) { sizeInOriginalLayout.toDpSize() })
                    .drawWithContent {
                        val animatorState = animatorState.value ?: return@drawWithContent
                        val animatorState = controller.animatorState ?: return@drawWithContent

                        // Scale the content with the background while keeping its aspect ratio.
                        val widthRatio =
@@ -356,7 +350,8 @@ private fun AnimatedContentInOverlay(
                    setContent {
                        Box(
                            Modifier.fillMaxSize().drawWithContent {
                                val animatorState = animatorState.value ?: return@drawWithContent
                                val animatorState =
                                    controller.animatorState ?: return@drawWithContent
                                if (!animatorState.visible) {
                                    return@drawWithContent
                                }
@@ -393,7 +388,7 @@ private fun AnimatedContentInOverlay(
        overlay.add(composeViewInOverlay)

        val startState =
            animatorState.value
            controller.animatorState
                ?: throw IllegalStateException(
                    "AnimatedContentInOverlay shouldn't be composed with null animatorState."
                )
+43 −54
Original line number Diff line number Diff line
@@ -25,12 +25,11 @@ import androidx.compose.foundation.BorderStroke
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.Size
@@ -78,24 +77,9 @@ fun rememberExpandableController(
    val density = LocalDensity.current
    val layoutDirection = LocalLayoutDirection.current

    // The current animation state, if we are currently animating a dialog or activity.
    val animatorState = remember { mutableStateOf<TransitionAnimator.State?>(null) }

    // Whether a dialog controlled by this ExpandableController is currently showing.
    val isDialogShowing = remember { mutableStateOf(false) }

    // The overlay in which we should animate the launch.
    val overlay = remember { mutableStateOf<ViewGroupOverlay?>(null) }

    // The current [ComposeView] being animated in the [overlay], if any.
    val currentComposeViewInOverlay = remember { mutableStateOf<View?>(null) }

    // The bounds in [composeViewRoot] of the expandable controlled by this controller.
    val boundsInComposeViewRoot = remember { mutableStateOf(Rect.Zero) }

    // Whether this composable is still composed. We only do the dialog exit animation if this is
    // true.
    val isComposed = remember { mutableStateOf(true) }
    var isComposed by remember { mutableStateOf(true) }

    val controller =
        remember(
@@ -114,19 +98,14 @@ fun rememberExpandableController(
                borderStroke,
                composeViewRoot,
                density,
                animatorState,
                isDialogShowing,
                overlay,
                currentComposeViewInOverlay,
                boundsInComposeViewRoot,
                layoutDirection,
                isComposed,
                { isComposed },
            )
        }

    DisposableEffect(Unit) {
        onDispose {
            isComposed.value = false
            isComposed = false
            if (TransitionAnimator.returnAnimationsEnabled()) {
                controller.onDispose()
            }
@@ -143,14 +122,27 @@ internal class ExpandableControllerImpl(
    internal val borderStroke: BorderStroke?,
    internal val composeViewRoot: View,
    internal val density: Density,
    internal val animatorState: MutableState<TransitionAnimator.State?>,
    internal val isDialogShowing: MutableState<Boolean>,
    internal val overlay: MutableState<ViewGroupOverlay?>,
    internal val currentComposeViewInOverlay: MutableState<View?>,
    internal val boundsInComposeViewRoot: MutableState<Rect>,
    private val layoutDirection: LayoutDirection,
    private val isComposed: State<Boolean>,
    private val isComposed: () -> Boolean,
) : ExpandableController {
    /** The current animation state, if we are currently animating a dialog or activity. */
    var animatorState by mutableStateOf<TransitionAnimator.State?>(null)
        private set

    /** Whether a dialog controlled by this ExpandableController is currently showing. */
    var isDialogShowing by mutableStateOf(false)
        private set

    /** The overlay in which we should animate the launch. */
    var overlay by mutableStateOf<ViewGroupOverlay?>(null)
        private set

    /** The current [ComposeView] being animated in the [overlay], if any. */
    var currentComposeViewInOverlay by mutableStateOf<View?>(null)

    /** The bounds in [composeViewRoot] of the expandable controlled by this controller. */
    var boundsInComposeViewRoot by mutableStateOf(Rect.Zero)

    /** The [ActivityTransitionAnimator.Controller] to be cleaned up [onDispose]. */
    private var activityControllerForDisposal: ActivityTransitionAnimator.Controller? = null

@@ -163,7 +155,7 @@ internal class ExpandableControllerImpl(
                returnCujType: Int?,
                isEphemeral: Boolean,
            ): ActivityTransitionAnimator.Controller? {
                if (!isComposed.value) {
                if (!isComposed()) {
                    return null
                }

@@ -179,7 +171,7 @@ internal class ExpandableControllerImpl(
            override fun dialogTransitionController(
                cuj: DialogCuj?
            ): DialogTransitionAnimator.Controller? {
                if (!isComposed.value) {
                if (!isComposed()) {
                    return null
                }

@@ -187,9 +179,7 @@ internal class ExpandableControllerImpl(
            }
        }

    override val isAnimating: Boolean by derivedStateOf {
        animatorState.value != null && overlay.value != null
    }
    override val isAnimating: Boolean by derivedStateOf { animatorState != null && overlay != null }

    override fun onDispose() {
        activityControllerForDisposal?.onDispose()
@@ -213,7 +203,7 @@ internal class ExpandableControllerImpl(
            override val isLaunching: Boolean = true

            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                animatorState.value = null
                animatorState = null
            }

            override fun onTransitionAnimationProgress(
@@ -223,7 +213,7 @@ internal class ExpandableControllerImpl(
            ) {
                // We copy state given that it's always the same object that is mutated by
                // ActivityTransitionAnimator.
                animatorState.value =
                animatorState =
                    TransitionAnimator.State(
                            state.top,
                            state.bottom,
@@ -236,13 +226,11 @@ internal class ExpandableControllerImpl(

                // Force measure and layout the ComposeView in the overlay whenever the animation
                // state changes.
                currentComposeViewInOverlay.value?.let {
                    measureAndLayoutComposeViewInOverlay(it, state)
                }
                currentComposeViewInOverlay?.let { measureAndLayoutComposeViewInOverlay(it, state) }
            }

            override fun createAnimatorState(): TransitionAnimator.State {
                val boundsInRoot = boundsInComposeViewRoot.value
                val boundsInRoot = boundsInComposeViewRoot
                val outline =
                    shape.createOutline(
                        Size(boundsInRoot.width, boundsInRoot.height),
@@ -294,7 +282,7 @@ internal class ExpandableControllerImpl(

            private fun rootLocationOnScreen(): Offset {
                composeViewRoot.getLocationOnScreen(rootLocationOnScreen)
                val boundsInRoot = boundsInComposeViewRoot.value
                val boundsInRoot = boundsInComposeViewRoot
                val x = rootLocationOnScreen[0] + boundsInRoot.left
                val y = rootLocationOnScreen[1] + boundsInRoot.top
                return Offset(x, y)
@@ -328,14 +316,14 @@ internal class ExpandableControllerImpl(

            override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
                delegate.onTransitionAnimationStart(isExpandingFullyAbove)
                overlay.value = transitionContainer.overlay as ViewGroupOverlay
                overlay = transitionContainer.overlay as ViewGroupOverlay
                cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
            }

            override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                cujType?.let { InteractionJankMonitor.getInstance().end(it) }
                delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                overlay.value = null
                overlay = null
            }
        }
    }
@@ -348,14 +336,14 @@ internal class ExpandableControllerImpl(

            override fun startDrawingInOverlayOf(viewGroup: ViewGroup) {
                val newOverlay = viewGroup.overlay as ViewGroupOverlay
                if (newOverlay != overlay.value) {
                    overlay.value = newOverlay
                if (newOverlay != overlay) {
                    overlay = newOverlay
                }
            }

            override fun stopDrawingInOverlay() {
                if (overlay.value != null) {
                    overlay.value = null
                if (overlay != null) {
                    overlay = null
                }
            }

@@ -366,7 +354,7 @@ internal class ExpandableControllerImpl(
                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)

                        // Make sure we don't draw this expandable when the dialog is showing.
                        isDialogShowing.value = true
                        isDialogShowing = true
                    }
                }
            }
@@ -376,16 +364,17 @@ internal class ExpandableControllerImpl(
                return object : TransitionAnimator.Controller by delegate {
                    override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
                        delegate.onTransitionAnimationEnd(isExpandingFullyAbove)
                        isDialogShowing.value = false
                        isDialogShowing = false
                    }
                }
            }

            override fun shouldAnimateExit(): Boolean =
                isComposed.value && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown
            override fun shouldAnimateExit(): Boolean {
                return isComposed() && composeViewRoot.isAttachedToWindow && composeViewRoot.isShown
            }

            override fun onExitAnimationCancelled() {
                isDialogShowing.value = false
                isDialogShowing = false
            }

            override fun jankConfigurationBuilder(): InteractionJankMonitor.Configuration.Builder? {