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

Commit c1794771 authored by Ahmed Mehfooz's avatar Ahmed Mehfooz Committed by Android (Google) Code Review
Browse files

Merge "[ROSP] Update StatusBarPopupChip to align with UX specs" into main

parents 94fba95e 8df269a3
Loading
Loading
Loading
Loading
+112 −28
Original line number Diff line number Diff line
@@ -16,33 +16,49 @@

package com.android.systemui.statusbar.featurepods.popups.ui.compose

import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.CompositingStrategy
import androidx.compose.ui.layout.layout
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.modifiers.thenIf
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.res.R
import com.android.systemui.statusbar.featurepods.popups.shared.model.HoverBehavior
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel

@@ -54,31 +70,48 @@ import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipM
@Composable
fun StatusBarPopupChip(viewModel: PopupChipModel.Shown, modifier: Modifier = Modifier) {
    val hasHoverBehavior = viewModel.hoverBehavior !is HoverBehavior.None
    val hoverInteractionSource = remember { MutableInteractionSource() }
    val isHovered by hoverInteractionSource.collectIsHoveredAsState()
    val interactionSource = remember { MutableInteractionSource() }
    val hoveredState by interactionSource.collectIsHoveredAsState()
    val isHovered = hasHoverBehavior && hoveredState
    val isPopupShown = viewModel.isPopupShown

    val indication = if (hoveredState) null else LocalIndication.current
    val chipShape =
        RoundedCornerShape(dimensionResource(id = R.dimen.ongoing_activity_chip_corner_radius))
    val chipBackgroundColor =
        if (isPopupShown) {
            MaterialTheme.colorScheme.primaryContainer
            MaterialTheme.colorScheme.primary
        } else {
            MaterialTheme.colorScheme.surfaceContainerHighest
            MaterialTheme.colorScheme.surfaceDim
        }
    Surface(
        shape = RoundedCornerShape(16.dp),

    // Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
    // height of the chip is determined by the height of the background of the Row below. The
    // `indication` for Clicks is applied in the Row below as well.
    Box(
        contentAlignment = Alignment.Center,
        modifier =
            modifier
                .widthIn(max = 120.dp)
                .padding(vertical = 4.dp)
                .animateContentSize()
                .thenIf(hasHoverBehavior) { Modifier.hoverable(hoverInteractionSource) }
                .thenIf(!isPopupShown) { Modifier.clickable { viewModel.showPopup() } },
        color = chipBackgroundColor,
            modifier.minimumInteractiveComponentSize().thenIf(!isPopupShown) {
                Modifier.clickable(
                    onClick = { viewModel.showPopup() },
                    indication = null,
                    interactionSource = interactionSource,
                )
            },
    ) {
        Row(
            modifier = Modifier.padding(start = 4.dp, end = 8.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(4.dp),
            verticalAlignment = Alignment.CenterVertically,
            modifier =
                Modifier.height(dimensionResource(R.dimen.ongoing_appops_chip_height))
                    .clip(chipShape)
                    .background(chipBackgroundColor)
                    .border(
                        width = dimensionResource(id = R.dimen.ongoing_activity_chip_outline_width),
                        color = MaterialTheme.colorScheme.outlineVariant,
                        shape = chipShape,
                    )
                    .indication(interactionSource, indication)
                    .padding(start = 4.dp, end = 8.dp),
        ) {
            val iconColor =
                if (isHovered) chipBackgroundColor else contentColorFor(chipBackgroundColor)
@@ -92,9 +125,11 @@ fun StatusBarPopupChip(viewModel: PopupChipModel.Shown, modifier: Modifier = Mod
                        else -> viewModel.icon
                    },
                modifier =
                    Modifier.thenIf(isHovered) {
                            Modifier.padding(3.dp)
                                .background(color = iconBackgroundColor, shape = CircleShape)
                    Modifier.height(20.dp)
                        .width(20.dp)
                        .thenIf(isHovered) {
                            Modifier.background(color = iconBackgroundColor, shape = CircleShape)
                                .padding(2.dp)
                        }
                        .thenIf(hoverBehavior is HoverBehavior.Button) {
                            Modifier.clickable(
@@ -102,18 +137,67 @@ fun StatusBarPopupChip(viewModel: PopupChipModel.Shown, modifier: Modifier = Mod
                                onClick = (hoverBehavior as HoverBehavior.Button).onIconPressed,
                                indication = ripple(),
                                interactionSource = iconInteractionSource,
                                enabled = isHovered,
                            )
                        }
                        .padding(3.dp),
                        },
                tint = iconColor,
            )

            val text = viewModel.chipText
            val textStyle = MaterialTheme.typography.labelLarge
            val textMeasurer = rememberTextMeasurer()
            var textOverflow by remember { mutableStateOf(false) }

            Text(
                text = viewModel.chipText,
                style = MaterialTheme.typography.labelLarge,
                text = text,
                style = textStyle,
                softWrap = false,
                overflow = TextOverflow.Ellipsis,
                modifier =
                    Modifier.widthIn(
                            max =
                                dimensionResource(id = R.dimen.ongoing_activity_chip_max_text_width)
                        )
                        .layout { measurables, constraints ->
                            val placeable = measurables.measure(constraints)
                            val intrinsicWidth =
                                textMeasurer.measure(text, textStyle, softWrap = false).size.width
                            textOverflow = intrinsicWidth > constraints.maxWidth

                            layout(placeable.width, placeable.height) {
                                if (textOverflow) {
                                    placeable.placeWithLayer(0, 0) {
                                        compositingStrategy = CompositingStrategy.Offscreen
                                    }
                                } else {
                                    placeable.place(0, 0)
                                }
                            }
                        }
                        .overflowFadeOut(
                            hasOverflow = { textOverflow },
                            fadeLength =
                                dimensionResource(
                                    id = R.dimen.ongoing_activity_chip_text_fading_edge_length
                                ),
                        ),
            )
        }
    }
}

private fun Modifier.overflowFadeOut(hasOverflow: () -> Boolean, fadeLength: Dp): Modifier {
    return drawWithCache {
        val width = size.width
        val start = (width - fadeLength.toPx()).coerceAtLeast(0f)
        val gradient =
            Brush.horizontalGradient(
                colors = listOf(Color.Black, Color.Transparent),
                startX = start,
                endX = width,
            )
        onDrawWithContent {
            drawContent()
            if (hasOverflow()) drawRect(brush = gradient, blendMode = BlendMode.DstIn)
        }
    }
}