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

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

Implement the QS footer actions in Compose (1/2)

This CL reimplements the FooterActions in Compose. This is going to be
one of the first features to be hidden behind a build time flag in our
SystemUICompose build once that flag is available.

See http://b/242040009#comment20 for a video and http://ag/20745047 for
the screenshot difference between the Compose and View implementations.

Bug: 242040009
Test: atest FooterActionsScreenshotTest
Change-Id: I83df8d191c1ab5d95466471c834ac1b0903ad21a
parent 509c9a2b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2331,6 +2331,7 @@
  <java-symbol type="drawable" name="scrubber_control_selector_holo" />
  <java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
  <java-symbol type="drawable" name="progress_small_material" />
  <java-symbol type="drawable" name="ic_chevron_end" />
  <java-symbol type="string" name="chooseUsbActivity" />
  <java-symbol type="string" name="ext_media_badremoval_notification_message" />
  <java-symbol type="string" name="ext_media_badremoval_notification_title" />
+30 −6
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ 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
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@@ -62,6 +63,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.layout.boundsInRoot
import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.ComposeView
@@ -118,12 +120,14 @@ fun Expandable(
    contentColor: Color = contentColorFor(color),
    borderStroke: BorderStroke? = null,
    onClick: ((Expandable) -> Unit)? = null,
    interactionSource: MutableInteractionSource? = null,
    content: @Composable (Expandable) -> Unit,
) {
    Expandable(
        rememberExpandableController(color, shape, contentColor, borderStroke),
        modifier,
        onClick,
        interactionSource,
        content,
    )
}
@@ -158,6 +162,7 @@ fun Expandable(
    controller: ExpandableController,
    modifier: Modifier = Modifier,
    onClick: ((Expandable) -> Unit)? = null,
    interactionSource: MutableInteractionSource? = null,
    content: @Composable (Expandable) -> Unit,
) {
    val controller = controller as ExpandableControllerImpl
@@ -190,6 +195,18 @@ fun Expandable(

    var thisExpandableSize by remember { mutableStateOf(Size.Zero) }

    /** Set the current element size as this Expandable size. */
    fun Modifier.updateExpandableSize(): Modifier {
        return this.onGloballyPositioned { coords ->
            thisExpandableSize =
                coords
                    .findRootCoordinates()
                    // Make sure that we report the actual size, and not the visual/clipped one.
                    .localBoundingBoxOf(coords, clipBounds = false)
                    .size
        }
    }

    // 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 {
@@ -247,7 +264,7 @@ fun Expandable(
        controller.isDialogShowing.value -> {
            Box(
                modifier
                    .onGloballyPositioned { thisExpandableSize = it.boundsInRoot().size }
                    .updateExpandableSize()
                    .then(minInteractiveSizeModifier)
                    .drawWithContent { /* Don't draw anything when the dialog is shown. */}
                    .onGloballyPositioned {
@@ -258,18 +275,25 @@ fun Expandable(
        else -> {
            val clickModifier =
                if (onClick != null) {
                    Modifier.clickable { onClick(controller.expandable) }
                    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
                    .onGloballyPositioned { thisExpandableSize = it.boundsInRoot().size }
                    .updateExpandableSize()
                    .then(minInteractiveSizeModifier)
                    // Note that clip() *must* be above the clickModifier to properly clip the
                    // ripple.
                    .clip(shape)
                    .then(clickModifier)
                    .background(color, shape)
                    .border(controller)
+7 −5
Original line number Diff line number Diff line
@@ -65,10 +65,12 @@ class AndroidColorScheme internal constructor(context: Context) {
    val colorForeground = getColor(context, R.attr.colorForeground)
    val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)

    private fun getColor(context: Context, attr: Int): Color {
    companion object {
        fun getColor(context: Context, attr: Int): Color {
            val ta = context.obtainStyledAttributes(intArrayOf(attr))
            @ColorInt val color = ta.getColor(0, 0)
            ta.recycle()
            return Color(color)
        }
    }
}
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.compose.theme

import android.annotation.AttrRes
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext

/** Read the [Color] from the given [attribute]. */
@Composable
@ReadOnlyComposable
fun colorAttr(@AttrRes attribute: Int): Color {
    return AndroidColorScheme.getColor(LocalContext.current, attribute)
}
+30 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.common.ui.compose

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.android.systemui.common.shared.model.ContentDescription

/** Returns the loaded [String] or `null` if there isn't one. */
@Composable
fun ContentDescription.load(): String? {
    return when (this) {
        is ContentDescription.Loaded -> description
        is ContentDescription.Resource -> stringResource(res)
    }
}
Loading