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

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

Extract Expandable related functions to be used by ag/31718343

This CL simply extracts some functions from Expandable (and related
classes) that will be reused by ag/31718343. This is a pure refactoring
meant to make the parsing/review of ag/31718343 easier.

Bug: 285250939
Test: N/A
Flag: EXEMPT simple code extraction
Change-Id: Ib4541b474323d9c68433560a5bda193f45ff860d
parent 2acac35d
Loading
Loading
Loading
Loading
+47 −39
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.movableContentOf
import androidx.compose.runtime.mutableStateOf
@@ -175,21 +174,7 @@ fun Expandable(
    val wrappedContent =
        remember(content) {
            movableContentOf { expandable: Expandable ->
                CompositionLocalProvider(LocalContentColor provides contentColor) {
                    // We make sure that the content itself (wrapped by the background) is at least
                    // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
                    // null, to make it easier to write expandables that are sometimes clickable and
                    // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
                    // the expandable is not clickable directly, then something in its content
                    // should be (and with a size >= 40dp).
                    val minSize = 40.dp
                    Box(
                        Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
                        contentAlignment = Alignment.Center,
                    ) {
                        content(expandable)
                    }
                }
                WrappedContent(expandable, contentColor, content)
            }
        }

@@ -209,11 +194,7 @@ fun Expandable(

    // Make sure we don't read animatorState directly here to avoid recomposition every time the
    // state changes (i.e. every frame of the animation).
    val isAnimating by remember {
        derivedStateOf {
            controller.animatorState.value != null && controller.overlay.value != null
        }
    }
    val isAnimating = controller.isAnimating

    // If this expandable is expanded when it's being directly clicked on, let's ensure that it has
    // the minimum interactive size followed by all M3 components (48.dp).
@@ -262,28 +243,11 @@ fun Expandable(
            }
        }
        else -> {
            val clickModifier =
                if (onClick != null) {
                    if (interactionSource != null) {
                        // If the caller provided an interaction source, then that means that they
                        // will draw the click indication themselves.
                        Modifier.clickable(interactionSource, indication = null) {
                            onClick(controller.expandable)
                        }
                    } else {
                        // If no interaction source is provided, we draw the default indication (a
                        // ripple) and make sure it's clipped by the expandable shape.
                        Modifier.clip(shape).clickable { onClick(controller.expandable) }
                    }
                } else {
                    Modifier
                }

            Box(
                modifier
                    .updateExpandableSize()
                    .then(minInteractiveSizeModifier)
                    .then(clickModifier)
                    .then(clickModifier(controller, onClick, interactionSource))
                    .background(color, shape)
                    .border(controller)
                    .onGloballyPositioned {
@@ -296,6 +260,50 @@ fun Expandable(
    }
}

@Composable
private fun WrappedContent(
    expandable: Expandable,
    contentColor: Color,
    content: @Composable (Expandable) -> Unit,
) {
    CompositionLocalProvider(LocalContentColor provides contentColor) {
        // We make sure that the content itself (wrapped by the background) is at least 40.dp, which
        // is the same as the M3 buttons. This applies even if onClick is null, to make it easier to
        // write expandables that are sometimes clickable and sometimes not. There shouldn't be any
        // Expandable smaller than 40dp because if the expandable is not clickable directly, then
        // something in its content should be (and with a size >= 40dp).
        val minSize = 40.dp
        Box(
            Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
            contentAlignment = Alignment.Center,
        ) {
            content(expandable)
        }
    }
}

private fun clickModifier(
    controller: ExpandableControllerImpl,
    onClick: ((Expandable) -> Unit)?,
    interactionSource: MutableInteractionSource?,
): Modifier {
    if (onClick == null) {
        return Modifier
    }

    if (interactionSource != null) {
        // If the caller provided an interaction source, then that means that they will draw the
        // click indication themselves.
        return Modifier.clickable(interactionSource, indication = null) {
            onClick(controller.expandable)
        }
    }

    // If no interaction source is provided, we draw the default indication (a ripple) and make sure
    // it's clipped by the expandable shape.
    return Modifier.clip(controller.shape).clickable { onClick(controller.expandable) }
}

/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
@Composable
private fun AnimatedContentInOverlay(
+9 −0
Original line number Diff line number Diff line
@@ -27,6 +27,8 @@ 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.ui.geometry.Offset
@@ -53,6 +55,9 @@ interface ExpandableController {
    /** The [Expandable] controlled by this controller. */
    val expandable: Expandable

    /** Whether this controller is currently animating a launch. */
    val isAnimating: Boolean

    /** Called when the [Expandable] stop being included in the composition. */
    fun onDispose()
}
@@ -182,6 +187,10 @@ internal class ExpandableControllerImpl(
            }
        }

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

    override fun onDispose() {
        activityControllerForDisposal?.onDispose()
        activityControllerForDisposal = null
+9 −4
Original line number Diff line number Diff line
@@ -42,13 +42,19 @@ import androidx.savedstate.setViewTreeSavedStateRegistryOwner
@Composable
fun Modifier.drawInOverlay(): Modifier {
    val containerState = remember { ContainerState() }
    FullScreenComposeViewInOverlay { Modifier.container(containerState) }
    return this.drawInContainer(containerState, enabled = { true })
}

@Composable
internal fun FullScreenComposeViewInOverlay(modifier: (ComposeView) -> Modifier = { Modifier }) {
    val context = LocalContext.current
    val localView = LocalView.current
    val compositionContext = rememberCompositionContext()
    val displayMetrics = context.resources.displayMetrics
    val displaySize = IntSize(displayMetrics.widthPixels, displayMetrics.heightPixels)

    DisposableEffect(containerState, context, localView, compositionContext, displaySize) {
    DisposableEffect(context, localView, compositionContext, displaySize) {
        val overlay = localView.rootView.overlay as ViewGroupOverlay
        val view =
            ComposeView(context).apply {
@@ -59,7 +65,8 @@ fun Modifier.drawInOverlay(): Modifier {
                setViewTreeViewModelStoreOwner(localView.findViewTreeViewModelStoreOwner())
                setViewTreeSavedStateRegistryOwner(localView.findViewTreeSavedStateRegistryOwner())

                setContent { Box(Modifier.fillMaxSize().container(containerState)) }
                val view = this
                setContent { Box(modifier(view).fillMaxSize()) }
            }

        overlay.add(view)
@@ -74,6 +81,4 @@ fun Modifier.drawInOverlay(): Modifier {

        onDispose { overlay.remove(view) }
    }

    return this.drawInContainer(containerState, enabled = { true })
}