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

Commit 68d52650 authored by Jordan Demeulenaere's avatar Jordan Demeulenaere Committed by Android (Google) Code Review
Browse files

Merge "Optimize Compose QS recompositions (1/2)" into main

parents 1901510f b7dfe500
Loading
Loading
Loading
Loading
+53 −3
Original line number Diff line number Diff line
@@ -17,13 +17,20 @@
package com.android.compose.animation

import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasureResult
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.layout
import androidx.compose.ui.node.LayoutModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import kotlin.math.roundToInt

/** A component that can bounce in one dimension, for instance when it is tapped. */
@Stable
interface Bounceable {
    val bounce: Dp
}
@@ -46,6 +53,7 @@ interface Bounceable {
 *   RTL layouts) side. This can be used for grids for which the last item does not align perfectly
 *   with the end of the grid.
 */
@Stable
fun Modifier.bounceable(
    bounceable: Bounceable,
    previousBounceable: Bounceable?,
@@ -53,7 +61,47 @@ fun Modifier.bounceable(
    orientation: Orientation,
    bounceEnd: Boolean = nextBounceable != null,
): Modifier {
    return layout { measurable, constraints ->
    return this then
        BounceableElement(bounceable, previousBounceable, nextBounceable, orientation, bounceEnd)
}

private data class BounceableElement(
    private val bounceable: Bounceable,
    private val previousBounceable: Bounceable?,
    private val nextBounceable: Bounceable?,
    private val orientation: Orientation,
    private val bounceEnd: Boolean,
) : ModifierNodeElement<BounceableNode>() {
    override fun create(): BounceableNode {
        return BounceableNode(
            bounceable,
            previousBounceable,
            nextBounceable,
            orientation,
            bounceEnd,
        )
    }

    override fun update(node: BounceableNode) {
        node.bounceable = bounceable
        node.previousBounceable = previousBounceable
        node.nextBounceable = nextBounceable
        node.orientation = orientation
        node.bounceEnd = bounceEnd
    }
}

private class BounceableNode(
    var bounceable: Bounceable,
    var previousBounceable: Bounceable?,
    var nextBounceable: Bounceable?,
    var orientation: Orientation,
    var bounceEnd: Boolean = nextBounceable != null,
) : Modifier.Node(), LayoutModifierNode {
    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints,
    ): MeasureResult {
        // The constraints in the orientation should be fixed, otherwise there is no way to know
        // what the size of our child node will be without this animation code.
        checkFixedSize(constraints, orientation)
@@ -61,10 +109,12 @@ fun Modifier.bounceable(
        var sizePrevious = 0f
        var sizeNext = 0f

        val previousBounceable = previousBounceable
        if (previousBounceable != null) {
            sizePrevious += bounceable.bounce.toPx() - previousBounceable.bounce.toPx()
        }

        val nextBounceable = nextBounceable
        if (nextBounceable != null) {
            sizeNext += bounceable.bounce.toPx() - nextBounceable.bounce.toPx()
        } else if (bounceEnd) {
@@ -84,7 +134,7 @@ fun Modifier.bounceable(
                // constraints, otherwise the parent will automatically center this node given the
                // size that it expects us to be. This allows us to then place the element where we
                // want it to be.
                layout(idleWidth, placeable.height) {
                return layout(idleWidth, placeable.height) {
                    placeable.placeRelative(-sizePrevious.roundToInt(), 0)
                }
            }
@@ -95,7 +145,7 @@ fun Modifier.bounceable(
                    constraints.copy(minHeight = animatedHeight, maxHeight = animatedHeight)

                val placeable = measurable.measure(animatedConstraints)
                layout(placeable.width, idleHeight) {
                return layout(placeable.width, idleHeight) {
                    placeable.placeRelative(0, -sizePrevious.roundToInt())
                }
            }
+7 −3
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

package com.android.systemui.compose.modifiers

import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.semantics
@@ -26,7 +26,11 @@ import androidx.compose.ui.semantics.testTagsAsResourceId
 * Set a test tag on this node so that it is associated with [resId]. This node will then be
 * accessible by integration tests using `sysuiResSelector(resId)`.
 */
@OptIn(ExperimentalComposeUiApi::class)
@Stable
fun Modifier.sysuiResTag(resId: String): Modifier {
    return this.semantics { testTagsAsResourceId = true }.testTag("com.android.systemui:id/$resId")
    // TODO(b/372412931): Only compose the semantics modifier once, at the root of the SystemUI
    // window.
    return this.then(TestTagAsResourceIdModifier).testTag("com.android.systemui:id/$resId")
}

private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true }
+25 −16
Original line number Diff line number Diff line
@@ -141,7 +141,7 @@ fun FooterActions(
        mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null)
    }
    var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
    var power by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
    var power by remember { mutableStateOf(viewModel.initialPower()) }

    LaunchedEffect(
        context,
@@ -218,23 +218,19 @@ fun FooterActions(
            }

            val useModifierBasedExpandable = remember { QSComposeFragment.isEnabled }
            security?.let { SecurityButton(it, useModifierBasedExpandable, Modifier.weight(1f)) }
            foregroundServices?.let { ForegroundServicesButton(it, useModifierBasedExpandable) }
            userSwitcher?.let {
            SecurityButton({ security }, useModifierBasedExpandable, Modifier.weight(1f))
            ForegroundServicesButton({ foregroundServices }, useModifierBasedExpandable)
            IconButton(
                    it,
                { userSwitcher },
                useModifierBasedExpandable,
                Modifier.sysuiResTag("multi_user_switch"),
            )
            }
            IconButton(
                viewModel.settings,
                { viewModel.settings },
                useModifierBasedExpandable,
                Modifier.sysuiResTag("settings_button_container"),
            )
            power?.let {
                IconButton(it, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite"))
            }
            IconButton({ power }, useModifierBasedExpandable, Modifier.sysuiResTag("pm_lite"))
        }
    }
}
@@ -242,10 +238,11 @@ fun FooterActions(
/** The security button. */
@Composable
private fun SecurityButton(
    model: FooterActionsSecurityButtonViewModel,
    model: () -> FooterActionsSecurityButtonViewModel?,
    useModifierBasedExpandable: Boolean,
    modifier: Modifier = Modifier,
) {
    val model = model() ?: return
    val onClick: ((Expandable) -> Unit)? =
        model.onClick?.let { onClick ->
            val context = LocalContext.current
@@ -265,9 +262,10 @@ private fun SecurityButton(
/** The foreground services button. */
@Composable
private fun RowScope.ForegroundServicesButton(
    model: FooterActionsForegroundServicesButtonViewModel,
    model: () -> FooterActionsForegroundServicesButtonViewModel?,
    useModifierBasedExpandable: Boolean,
) {
    val model = model() ?: return
    if (model.displayText) {
        TextButton(
            Icon.Resource(R.drawable.ic_info_outline, contentDescription = null),
@@ -288,6 +286,17 @@ private fun RowScope.ForegroundServicesButton(
    }
}

/** A button with an icon. */
@Composable
fun IconButton(
    model: () -> FooterActionsButtonViewModel?,
    useModifierBasedExpandable: Boolean,
    modifier: Modifier = Modifier,
) {
    val model = model() ?: return
    IconButton(model, useModifierBasedExpandable, modifier)
}

/** A button with an icon. */
@Composable
fun IconButton(
+6 −11
Original line number Diff line number Diff line
@@ -165,16 +165,11 @@ fun BrightnessSlider(

    val activeIconColor = colors.activeTickColor
    val inactiveIconColor = colors.inactiveTickColor
    val trackIcon: DrawScope.(Offset, Color, Float) -> Unit =
        remember(painter) {
    val trackIcon: DrawScope.(Offset, Color, Float) -> Unit = remember {
        { offset, color, alpha ->
            translate(offset.x + IconPadding.toPx(), offset.y) {
                with(painter) {
                        draw(
                            IconSize.toSize(),
                            colorFilter = ColorFilter.tint(color),
                            alpha = alpha,
                        )
                    draw(IconSize.toSize(), colorFilter = ColorFilter.tint(color), alpha = alpha)
                }
            }
        }
+2 −0
Original line number Diff line number Diff line
@@ -19,11 +19,13 @@ package com.android.systemui.common.shared.model
import android.annotation.AttrRes
import android.annotation.ColorInt
import android.annotation.ColorRes
import androidx.compose.runtime.Stable

/**
 * Models a color that can be either a specific [Color.Loaded] value or a resolvable theme
 * [Color.Attribute]
 */
@Stable
sealed interface Color {

    data class Loaded(@ColorInt val color: Int) : Color
Loading