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

Commit 19566c82 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge changes I0714871b,Id4751ddb into main

* changes:
  Add a lambda based color overload to ExpandableController
  Rename fadingBackground to animatedBackground with () -> Color param
parents 3e6d370f 561077a5
Loading
Loading
Loading
Loading
+27 −18
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroupOverlay
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -62,6 +61,7 @@ import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.graphics.rememberGraphicsLayer
@@ -82,6 +82,7 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.android.compose.modifiers.animatedBackground
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.FullScreenComposeViewInOverlay
import com.android.systemui.animation.ComposableControllerFactory
@@ -291,7 +292,7 @@ fun Expandable(
                    .updateExpandableSize()
                    .then(minInteractiveSizeModifier)
                    .then(clickModifier(controller, onClick, interactionSource))
                    .background(color, shape)
                    .animatedBackground(color, shape = shape)
                    .border(controller)
                    .onGloballyPositioned { controller.boundsInComposeViewRoot = it.boundsInRoot() }
            ) {
@@ -307,12 +308,14 @@ private fun WrappedContent(
    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 minSizeContent =
        @Composable {
            // 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),
@@ -321,6 +324,12 @@ private fun WrappedContent(
                content(expandable)
            }
        }

    if (contentColor.isSpecified) {
        CompositionLocalProvider(LocalContentColor provides contentColor, content = minSizeContent)
    } else {
        minSizeContent()
    }
}

@Composable
@@ -345,7 +354,7 @@ private fun Modifier.expandable(
        .thenIf(drawContent) {
            Modifier.border(controller)
                .then(clickModifier(controller, onClick, interactionSource))
                .background(controller.color, controller.shape)
                .animatedBackground(controller.color, shape = controller.shape)
        }
        .onPlaced { controller.boundsInComposeViewRoot = it.boundsInRoot() }
        .drawWithContent {
@@ -422,7 +431,7 @@ private class DrawExpandableInOverlayNode(
            // Background.
            this@draw.drawBackground(
                state,
                controller.color,
                controller.color(),
                controller.borderStroke,
                size = Size(state.width.toFloat(), state.height.toFloat()),
            )
@@ -469,7 +478,7 @@ private fun clickModifier(
/** Draw [content] in [overlay] while respecting its screen position given by [animatorState]. */
@Composable
private fun AnimatedContentInOverlay(
    color: Color,
    color: () -> Color,
    sizeInOriginalLayout: Size,
    overlay: ViewGroupOverlay,
    controller: ExpandableControllerImpl,
@@ -523,7 +532,7 @@ private fun AnimatedContentInOverlay(
                                    return@drawWithContent
                                }

                                drawBackground(animatorState, color, controller.borderStroke)
                                drawBackground(animatorState, color(), controller.borderStroke)
                                drawContent()
                            },
                            // We center the content in the expanding container.
+19 −2
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -79,6 +78,24 @@ fun rememberExpandableController(
    contentColor: Color = contentColorFor(color),
    borderStroke: BorderStroke? = null,
    transitionControllerFactory: ComposableControllerFactory? = null,
): ExpandableController {
    return rememberExpandableController(
        color = { color },
        shape = shape,
        contentColor = contentColor,
        borderStroke = borderStroke,
        transitionControllerFactory = transitionControllerFactory,
    )
}

/** Create an [ExpandableController] to control an [Expandable]. */
@Composable
fun rememberExpandableController(
    color: () -> Color,
    shape: Shape,
    contentColor: Color = Color.Unspecified,
    borderStroke: BorderStroke? = null,
    transitionControllerFactory: ComposableControllerFactory? = null,
): ExpandableController {
    val composeViewRoot = LocalView.current
    val density = LocalDensity.current
@@ -125,7 +142,7 @@ fun rememberExpandableController(
}

internal class ExpandableControllerImpl(
    internal val color: Color,
    internal val color: () -> Color,
    internal val contentColor: Color,
    internal val shape: Shape,
    internal val borderStroke: BorderStroke?,
+159 −0
Original line number Diff line number Diff line
@@ -17,32 +17,37 @@
package com.android.compose.modifiers

import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.DrawModifier
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawOutline
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.ObserverModifierNode
import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.node.observeReads
import androidx.compose.ui.platform.InspectorInfo
import androidx.compose.ui.platform.InspectorValueInfo
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.unit.LayoutDirection

/**
 * Draws a fading [shape] with a solid [color] and [alpha] behind the content.
 * Draws a background in a given [shape] and with a [color] or [alpha] that can be animated.
 *
 * @param color color to paint background with
 * @param alpha alpha of the background
 * @param shape desired shape of the background
 */
fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = RectangleShape) =
fun Modifier.animatedBackground(
    color: () -> Color,
    alpha: () -> Float = DefaultAlpha,
    shape: Shape = RectangleShape,
) =
    this.then(
        FadingBackground(
            brush = SolidColor(color),
        BackgroundElement(
            color = color,
            alpha = alpha,
            shape = shape,
            inspectorInfo =
@@ -56,17 +61,53 @@ fun Modifier.fadingBackground(color: Color, alpha: () -> Float, shape: Shape = R
        )
    )

private class FadingBackground
constructor(
    private val brush: Brush,
    private val shape: Shape,
private val DefaultAlpha = { 1f }

private class BackgroundElement(
    private val color: () -> Color,
    private val alpha: () -> Float,
    inspectorInfo: InspectorInfo.() -> Unit,
) : DrawModifier, InspectorValueInfo(inspectorInfo) {
    // naive cache outline calculation if size is the same
    private var lastSize: Size? = null
    private val shape: Shape,
    private val inspectorInfo: InspectorInfo.() -> Unit,
) : ModifierNodeElement<BackgroundNode>() {
    override fun create(): BackgroundNode {
        return BackgroundNode(color, alpha, shape)
    }

    override fun update(node: BackgroundNode) {
        node.color = color
        node.alpha = alpha
        node.shape = shape
    }

    override fun InspectorInfo.inspectableProperties() {
        inspectorInfo()
    }

    override fun hashCode(): Int {
        var result = color.hashCode()
        result = 31 * result + alpha.hashCode()
        result = 31 * result + shape.hashCode()
        return result
    }

    override fun equals(other: Any?): Boolean {
        val otherModifier = other as? BackgroundElement ?: return false
        return color == otherModifier.color &&
            alpha == otherModifier.alpha &&
            shape == otherModifier.shape
    }
}

private class BackgroundNode(var color: () -> Color, var alpha: () -> Float, var shape: Shape) :
    DrawModifierNode, Modifier.Node(), ObserverModifierNode {

    // Naively cache outline calculation if input parameters are the same, we manually observe
    // reads inside shape#createOutline separately
    private var lastSize: Size = Size.Unspecified
    private var lastLayoutDirection: LayoutDirection? = null
    private var lastOutline: Outline? = null
    private var lastShape: Shape? = null
    private var tmpOutline: Outline? = null

    override fun ContentDrawScope.draw() {
        if (shape === RectangleShape) {
@@ -78,36 +119,41 @@ constructor(
        drawContent()
    }

    override fun onObservedReadsChanged() {
        // Reset cached properties
        lastSize = Size.Unspecified
        lastLayoutDirection = null
        lastOutline = null
        lastShape = null
        // Invalidate draw so we build the cache again - this is needed because observeReads within
        // the draw scope obscures the state reads from the draw scope's observer
        invalidateDraw()
    }

    private fun ContentDrawScope.drawRect() {
        drawRect(brush, alpha = alpha())
        drawRect(color = color(), alpha = alpha())
    }

    private fun ContentDrawScope.drawOutline() {
        val outline =
            if (size == lastSize && layoutDirection == lastLayoutDirection) {
                lastOutline!!
        val outline = getOutline()
        drawOutline(outline, color = color(), alpha = alpha())
    }

    private fun ContentDrawScope.getOutline(): Outline {
        val outline: Outline?
        if (size == lastSize && layoutDirection == lastLayoutDirection && lastShape == shape) {
            outline = lastOutline!!
        } else {
                shape.createOutline(size, layoutDirection, this)
            // Manually observe reads so we can directly invalidate the outline when it changes
            // Use tmpOutline to avoid creating an object reference to local var outline
            observeReads { tmpOutline = shape.createOutline(size, layoutDirection, this) }
            outline = tmpOutline
            tmpOutline = null
        }
        drawOutline(outline, brush = brush, alpha = alpha())
        lastOutline = outline
        lastSize = size
        lastLayoutDirection = layoutDirection
        lastShape = shape
        return outline!!
    }

    override fun hashCode(): Int {
        var result = brush.hashCode()
        result = 31 * result + alpha.hashCode()
        result = 31 * result + shape.hashCode()
        return result
    }

    override fun equals(other: Any?): Boolean {
        val otherModifier = other as? FadingBackground ?: return false
        return brush == otherModifier.brush &&
            alpha == otherModifier.alpha &&
            shape == otherModifier.shape
    }

    override fun toString(): String = "FadingBackground(brush=$brush, alpha = $alpha, shape=$shape)"
}
+3 −3
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.ContentScope
import com.android.compose.modifiers.fadingBackground
import com.android.compose.modifiers.animatedBackground
import com.android.compose.theme.colorAttr
import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.Expandable
@@ -172,8 +172,8 @@ fun FooterActions(
    val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
    val backgroundModifier =
        remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) {
            Modifier.fadingBackground(
                backgroundColor,
            Modifier.animatedBackground(
                { backgroundColor },
                backgroundAlphaValue,
                RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
            )
+2 −2
Original line number Diff line number Diff line
@@ -76,6 +76,7 @@ import androidx.compose.ui.util.trace
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.Expandable
import com.android.compose.animation.bounceable
import com.android.compose.animation.rememberExpandableController
import com.android.compose.modifiers.thenIf
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.Flags
@@ -265,8 +266,7 @@ private fun TileExpandable(
    content: @Composable (Expandable) -> Unit,
) {
    Expandable(
        color = color(),
        shape = shape,
        controller = rememberExpandableController(color = color, shape = shape),
        modifier = modifier.clip(shape).verticalSquish(squishiness),
        useModifierBasedImplementation = true,
    ) {
Loading