Loading packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt +1 −5 Original line number Diff line number Diff line Loading @@ -32,11 +32,7 @@ import com.android.systemui.common.shared.model.Icon * Note: You can use [Color.Unspecified] to disable the tint and keep the original icon colors. */ @Composable fun Icon( icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current, ) { fun Icon(icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current) { val contentDescription = icon.contentDescription?.load() when (icon) { is Icon.Loaded -> { Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +25 −11 Original line number Diff line number Diff line Loading @@ -18,12 +18,12 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import android.graphics.drawable.Animatable import android.text.TextUtils import androidx.compose.animation.animateColorAsState import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box Loading @@ -32,8 +32,9 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue Loading @@ -44,6 +45,7 @@ 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.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape Loading @@ -57,7 +59,9 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.modifiers.size import com.android.compose.modifiers.thenIf import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon Loading Loading @@ -88,12 +92,14 @@ fun LargeTileContent( ) { // Icon val longPressLabel = longPressLabel().takeIf { onLongClick != null } val animatedBackgroundColor by animateColorAsState(colors.iconBackground, label = "QSTileDualTargetBackgroundColor") Box( modifier = Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) { Modifier.clip(iconShape) .verticalSquish(squishiness) .background(colors.iconBackground) .drawBehind { drawRect(animatedBackgroundColor) } .combinedClickable( onClick = toggleClick!!, onLongClick = onLongClick, Loading @@ -117,6 +123,7 @@ fun LargeTileContent( SmallTileContent( icon = icon, color = colors.icon, size = { CommonTileDefaults.LargeTileIconSize }, modifier = Modifier.align(Alignment.Center), ) } Loading @@ -139,18 +146,22 @@ fun LargeTileLabels( modifier: Modifier = Modifier, accessibilityUiState: AccessibilityUiState? = null, ) { val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor") val animatedSecondaryLabelColor by animateColorAsState(colors.secondaryLabel, label = "QSTileSecondaryLabelColor") Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { Text( BasicText( label, style = MaterialTheme.typography.labelLarge, color = colors.label, color = { animatedLabelColor }, maxLines = 1, overflow = TextOverflow.Ellipsis, ) if (!TextUtils.isEmpty(secondaryLabel)) { Text( BasicText( secondaryLabel ?: "", color = colors.secondaryLabel, color = { animatedSecondaryLabelColor }, maxLines = 1, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.thenIf( Loading @@ -170,9 +181,11 @@ fun SmallTileContent( modifier: Modifier = Modifier, icon: Icon, color: Color, size: () -> Dp = { CommonTileDefaults.IconSize }, animateToEnd: Boolean = false, ) { val iconModifier = modifier.size(CommonTileDefaults.IconSize) val animatedColor by animateColorAsState(color, label = "QSTileIconColor") val iconModifier = modifier.size({ size().roundToPx() }, { size().roundToPx() }) val context = LocalContext.current val loadedDrawable = remember(icon, context) { Loading @@ -182,7 +195,7 @@ fun SmallTileContent( } } if (loadedDrawable !is Animatable) { Icon(icon = icon, tint = color, modifier = iconModifier) Icon(icon = icon, tint = animatedColor, modifier = iconModifier) } else if (icon is Icon.Resource) { val image = AnimatedImageVector.animatedVectorResource(id = icon.res) val painter = Loading @@ -198,14 +211,15 @@ fun SmallTileContent( Image( painter = painter, contentDescription = icon.contentDescription?.load(), colorFilter = ColorFilter.tint(color = color), colorFilter = ColorFilter.tint(color = animatedColor), modifier = iconModifier, ) } } object CommonTileDefaults { val IconSize = 24.dp val IconSize = 32.dp val LargeTileIconSize = 28.dp val ToggleTargetSize = 56.dp val TileHeight = 72.dp val TilePadding = 8.dp Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +83 −35 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn Loading Loading @@ -54,7 +55,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon Loading @@ -69,21 +69,22 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot Loading @@ -103,6 +104,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.bounceable import com.android.compose.modifiers.height import com.android.systemui.common.ui.compose.load Loading Loading @@ -134,9 +136,10 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.flow.collectLatest object TileType Loading Loading @@ -409,7 +412,7 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * * @param cells the pairs of [GridCell] to [BounceableTileViewModel] * @param cells the pairs of [GridCell] to [AnimatableTileViewModel] * @param dragAndDropState the [DragAndDropState] for this grid * @param selectionState the [MutableSelectionState] for this grid * @param onToggleSize the callback when a tile's size is toggled Loading Loading @@ -545,9 +548,27 @@ private fun TileGridCell( selectionState::unSelect, ) .tileBackground(colors.background) .tilePadding() ) { EditTile(tile = cell.tile, iconOnly = cell.isIcon) val targetValue = if (cell.isIcon) 0f else 1f val animatedProgress = remember { Animatable(targetValue) } if (selected) { val resizingState = selectionState.resizingState LaunchedEffect(targetValue, resizingState) { if (resizingState == null) { animatedProgress.animateTo(targetValue) } else { snapshotFlow { resizingState.progression } .collectLatest { animatedProgress.snapTo(it) } } } } EditTile( tile = cell.tile, tileWidths = { tileWidths }, progress = { animatedProgress.value }, ) } } } Loading Loading @@ -612,45 +633,72 @@ private fun SpacerGridCell(modifier: Modifier = Modifier) { } @Composable fun BoxScope.EditTile( fun EditTile( tile: EditTileViewModel, iconOnly: Boolean, tileWidths: () -> TileWidths?, progress: () -> Float, colors: TileColors = EditModeTileDefaults.editTileColors(), ) { // Animated horizontal alignment from center (0f) to start (-1f) val alignmentValue by animateFloatAsState( targetValue = if (iconOnly) 0f else -1f, label = "QSEditTileContentAlignment", ) val alignment by remember { derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) } val iconSizeDiff = CommonTileDefaults.IconSize - CommonTileDefaults.LargeTileIconSize Row( horizontalArrangement = spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.layout { measurable, constraints -> // Always display the tile using the large size and trust the parent composable // to clip the content as needed. This stop the labels from being truncated. val width = tileWidths()?.max ?: constraints.maxWidth val placeable = measurable.measure(constraints.copy(minWidth = width, maxWidth = width)) val currentProgress = progress() val startPadding = if (currentProgress == 0f) { // Find the center of the max width when the tile is icon only iconHorizontalCenter(constraints.maxWidth) } else { // Find the center of the minimum width to hold the same position as the // tile is resized. val basePadding = tileWidths()?.min?.let { iconHorizontalCenter(it) } ?: 0f // Large tiles, represented with a progress of 1f, have a 0.dp padding basePadding * (1f - currentProgress) } layout(constraints.maxWidth, constraints.maxHeight) { placeable.place(startPadding.roundToInt(), 0) } } .tilePadding(), ) { // Icon Box(Modifier.size(ToggleTargetSize).align(alignment)) { Box(Modifier.size(ToggleTargetSize)) { SmallTileContent( icon = tile.icon, color = colors.icon, animateToEnd = true, size = { CommonTileDefaults.IconSize - iconSizeDiff * progress() }, modifier = Modifier.align(Alignment.Center), ) } // Labels, positioned after the icon AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { LargeTileLabels( label = tile.label.text, secondaryLabel = tile.appName?.text, colors = colors, modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), modifier = Modifier.weight(1f).graphicsLayer { alpha = progress() }, ) } } private fun Modifier.tileBackground(color: Color): Modifier { return drawBehind { drawRoundRect(SolidColor(color), cornerRadius = CornerRadius(InactiveCornerRadius.toPx())) private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float { return (containerSize - ToggleTargetSize.roundToPx()) / 2f - CommonTileDefaults.TilePadding.toPx() } private fun Modifier.tileBackground(color: Color): Modifier { // Clip tile contents from overflowing past the tile return clip(RoundedCornerShape(InactiveCornerRadius)).drawBehind { drawRect(color) } } private object EditModeTileDefaults { Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +30 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import android.content.res.Resources import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable Loading Loading @@ -61,6 +62,7 @@ import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.modifiers.thenIf Loading @@ -74,6 +76,7 @@ import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel Loading @@ -82,7 +85,6 @@ import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import java.util.function.Supplier import kotlinx.coroutines.CoroutineScope import com.android.app.tracing.coroutines.launchTraced as launch private const val TEST_TAG_SMALL = "qs_tile_small" private const val TEST_TAG_LARGE = "qs_tile_large" Loading Loading @@ -128,14 +130,18 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape = TileDefaults.animateTileShape(uiState.state) TileExpandable( color = val animatedColor by animateColorAsState( if (iconOnly || !uiState.handlesSecondaryClick) { colors.iconBackground } else { colors.background }, label = "QSTileBackgroundColor", ) TileExpandable( color = { animatedColor }, shape = tileShape, squishiness = squishiness, hapticsViewModel = hapticsViewModel, Loading Loading @@ -212,7 +218,7 @@ fun Tile( @Composable private fun TileExpandable( color: Color, color: () -> Color, shape: Shape, squishiness: () -> Float, hapticsViewModel: TileHapticsViewModel?, Loading @@ -220,7 +226,7 @@ private fun TileExpandable( content: @Composable (Expandable) -> Unit, ) { Expandable( color = color, color = color(), shape = shape, modifier = modifier.clip(shape).verticalSquish(squishiness), ) { Loading @@ -238,7 +244,7 @@ fun TileContainer( ) { Box( modifier = Modifier.height(CommonTileDefaults.TileHeight) Modifier.height(TileHeight) .fillMaxWidth() .tileCombinedClickable( onClick = onClick, Loading Loading @@ -335,6 +341,16 @@ private object TileDefaults { icon = MaterialTheme.colorScheme.onPrimary, ) @Composable fun inactiveDualTargetTileColors(): TileColors = TileColors( background = MaterialTheme.colorScheme.surfaceVariant, iconBackground = MaterialTheme.colorScheme.surfaceContainerHighest, label = MaterialTheme.colorScheme.onSurfaceVariant, secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, icon = MaterialTheme.colorScheme.onSurfaceVariant, ) @Composable fun inactiveTileColors(): TileColors = TileColors( Loading Loading @@ -365,7 +381,13 @@ private object TileDefaults { activeTileColors() } } STATE_INACTIVE -> inactiveTileColors() STATE_INACTIVE -> { if (uiState.handlesSecondaryClick) { inactiveDualTargetTileColors() } else { inactiveTileColors() } } else -> unavailableTileColors() } } Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt +10 −5 Original line number Diff line number Diff line Loading @@ -17,25 +17,30 @@ package com.android.systemui.qs.panels.ui.compose.selection import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.setValue import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD class ResizingState(private val widths: TileWidths, private val onResize: () -> Unit) { // Total drag offset of this resize operation private var totalOffset = 0f /** Total drag offset of this resize operation. */ private var totalOffset by mutableFloatStateOf(0f) /** Width in pixels of the resizing tile. */ var width by mutableIntStateOf(widths.base) /** Progression between icon (0) and large (1) sizes. */ val progression get() = calculateProgression() // Whether the tile is currently over the threshold and should be a large tile private var passedThreshold: Boolean = passedThreshold(calculateProgression(width)) private var passedThreshold: Boolean = passedThreshold(progression) fun onDrag(offset: Float) { totalOffset += offset width = (widths.base + totalOffset).toInt().coerceIn(widths.min, widths.max) passedThreshold(calculateProgression(width)).let { passedThreshold(progression).let { // Resize if we went over the threshold if (passedThreshold != it) { passedThreshold = it Loading @@ -49,7 +54,7 @@ class ResizingState(private val widths: TileWidths, private val onResize: () -> } /** The progression of the resizing tile between an icon tile (0f) and a large tile (1f) */ private fun calculateProgression(width: Int): Float { private fun calculateProgression(): Float { return ((width - widths.min) / (widths.max - widths.min).toFloat()).coerceIn(0f, 1f) } } Loading Loading
packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt +1 −5 Original line number Diff line number Diff line Loading @@ -32,11 +32,7 @@ import com.android.systemui.common.shared.model.Icon * Note: You can use [Color.Unspecified] to disable the tint and keep the original icon colors. */ @Composable fun Icon( icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current, ) { fun Icon(icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current) { val contentDescription = icon.contentDescription?.load() when (icon) { is Icon.Loaded -> { Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +25 −11 Original line number Diff line number Diff line Loading @@ -18,12 +18,12 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import android.graphics.drawable.Animatable import android.text.TextUtils import androidx.compose.animation.animateColorAsState import androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi import androidx.compose.animation.graphics.res.animatedVectorResource import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box Loading @@ -32,8 +32,9 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicText import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue Loading @@ -44,6 +45,7 @@ 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.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape Loading @@ -57,7 +59,9 @@ import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.modifiers.size import com.android.compose.modifiers.thenIf import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon Loading Loading @@ -88,12 +92,14 @@ fun LargeTileContent( ) { // Icon val longPressLabel = longPressLabel().takeIf { onLongClick != null } val animatedBackgroundColor by animateColorAsState(colors.iconBackground, label = "QSTileDualTargetBackgroundColor") Box( modifier = Modifier.size(CommonTileDefaults.ToggleTargetSize).thenIf(toggleClick != null) { Modifier.clip(iconShape) .verticalSquish(squishiness) .background(colors.iconBackground) .drawBehind { drawRect(animatedBackgroundColor) } .combinedClickable( onClick = toggleClick!!, onLongClick = onLongClick, Loading @@ -117,6 +123,7 @@ fun LargeTileContent( SmallTileContent( icon = icon, color = colors.icon, size = { CommonTileDefaults.LargeTileIconSize }, modifier = Modifier.align(Alignment.Center), ) } Loading @@ -139,18 +146,22 @@ fun LargeTileLabels( modifier: Modifier = Modifier, accessibilityUiState: AccessibilityUiState? = null, ) { val animatedLabelColor by animateColorAsState(colors.label, label = "QSTileLabelColor") val animatedSecondaryLabelColor by animateColorAsState(colors.secondaryLabel, label = "QSTileSecondaryLabelColor") Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { Text( BasicText( label, style = MaterialTheme.typography.labelLarge, color = colors.label, color = { animatedLabelColor }, maxLines = 1, overflow = TextOverflow.Ellipsis, ) if (!TextUtils.isEmpty(secondaryLabel)) { Text( BasicText( secondaryLabel ?: "", color = colors.secondaryLabel, color = { animatedSecondaryLabelColor }, maxLines = 1, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.thenIf( Loading @@ -170,9 +181,11 @@ fun SmallTileContent( modifier: Modifier = Modifier, icon: Icon, color: Color, size: () -> Dp = { CommonTileDefaults.IconSize }, animateToEnd: Boolean = false, ) { val iconModifier = modifier.size(CommonTileDefaults.IconSize) val animatedColor by animateColorAsState(color, label = "QSTileIconColor") val iconModifier = modifier.size({ size().roundToPx() }, { size().roundToPx() }) val context = LocalContext.current val loadedDrawable = remember(icon, context) { Loading @@ -182,7 +195,7 @@ fun SmallTileContent( } } if (loadedDrawable !is Animatable) { Icon(icon = icon, tint = color, modifier = iconModifier) Icon(icon = icon, tint = animatedColor, modifier = iconModifier) } else if (icon is Icon.Resource) { val image = AnimatedImageVector.animatedVectorResource(id = icon.res) val painter = Loading @@ -198,14 +211,15 @@ fun SmallTileContent( Image( painter = painter, contentDescription = icon.contentDescription?.load(), colorFilter = ColorFilter.tint(color = color), colorFilter = ColorFilter.tint(color = animatedColor), modifier = iconModifier, ) } } object CommonTileDefaults { val IconSize = 24.dp val IconSize = 32.dp val LargeTileIconSize = 28.dp val ToggleTargetSize = 56.dp val TileHeight = 72.dp val TilePadding = 8.dp Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +83 −35 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.fadeIn Loading Loading @@ -54,7 +55,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon Loading @@ -69,21 +69,22 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.MeasureScope import androidx.compose.ui.layout.layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot Loading @@ -103,6 +104,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.bounceable import com.android.compose.modifiers.height import com.android.systemui.common.ui.compose.load Loading Loading @@ -134,9 +136,10 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import com.android.app.tracing.coroutines.launchTraced as launch import kotlinx.coroutines.flow.collectLatest object TileType Loading Loading @@ -409,7 +412,7 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * * @param cells the pairs of [GridCell] to [BounceableTileViewModel] * @param cells the pairs of [GridCell] to [AnimatableTileViewModel] * @param dragAndDropState the [DragAndDropState] for this grid * @param selectionState the [MutableSelectionState] for this grid * @param onToggleSize the callback when a tile's size is toggled Loading Loading @@ -545,9 +548,27 @@ private fun TileGridCell( selectionState::unSelect, ) .tileBackground(colors.background) .tilePadding() ) { EditTile(tile = cell.tile, iconOnly = cell.isIcon) val targetValue = if (cell.isIcon) 0f else 1f val animatedProgress = remember { Animatable(targetValue) } if (selected) { val resizingState = selectionState.resizingState LaunchedEffect(targetValue, resizingState) { if (resizingState == null) { animatedProgress.animateTo(targetValue) } else { snapshotFlow { resizingState.progression } .collectLatest { animatedProgress.snapTo(it) } } } } EditTile( tile = cell.tile, tileWidths = { tileWidths }, progress = { animatedProgress.value }, ) } } } Loading Loading @@ -612,45 +633,72 @@ private fun SpacerGridCell(modifier: Modifier = Modifier) { } @Composable fun BoxScope.EditTile( fun EditTile( tile: EditTileViewModel, iconOnly: Boolean, tileWidths: () -> TileWidths?, progress: () -> Float, colors: TileColors = EditModeTileDefaults.editTileColors(), ) { // Animated horizontal alignment from center (0f) to start (-1f) val alignmentValue by animateFloatAsState( targetValue = if (iconOnly) 0f else -1f, label = "QSEditTileContentAlignment", ) val alignment by remember { derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) } val iconSizeDiff = CommonTileDefaults.IconSize - CommonTileDefaults.LargeTileIconSize Row( horizontalArrangement = spacedBy(6.dp), verticalAlignment = Alignment.CenterVertically, modifier = Modifier.layout { measurable, constraints -> // Always display the tile using the large size and trust the parent composable // to clip the content as needed. This stop the labels from being truncated. val width = tileWidths()?.max ?: constraints.maxWidth val placeable = measurable.measure(constraints.copy(minWidth = width, maxWidth = width)) val currentProgress = progress() val startPadding = if (currentProgress == 0f) { // Find the center of the max width when the tile is icon only iconHorizontalCenter(constraints.maxWidth) } else { // Find the center of the minimum width to hold the same position as the // tile is resized. val basePadding = tileWidths()?.min?.let { iconHorizontalCenter(it) } ?: 0f // Large tiles, represented with a progress of 1f, have a 0.dp padding basePadding * (1f - currentProgress) } layout(constraints.maxWidth, constraints.maxHeight) { placeable.place(startPadding.roundToInt(), 0) } } .tilePadding(), ) { // Icon Box(Modifier.size(ToggleTargetSize).align(alignment)) { Box(Modifier.size(ToggleTargetSize)) { SmallTileContent( icon = tile.icon, color = colors.icon, animateToEnd = true, size = { CommonTileDefaults.IconSize - iconSizeDiff * progress() }, modifier = Modifier.align(Alignment.Center), ) } // Labels, positioned after the icon AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { LargeTileLabels( label = tile.label.text, secondaryLabel = tile.appName?.text, colors = colors, modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), modifier = Modifier.weight(1f).graphicsLayer { alpha = progress() }, ) } } private fun Modifier.tileBackground(color: Color): Modifier { return drawBehind { drawRoundRect(SolidColor(color), cornerRadius = CornerRadius(InactiveCornerRadius.toPx())) private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float { return (containerSize - ToggleTargetSize.roundToPx()) / 2f - CommonTileDefaults.TilePadding.toPx() } private fun Modifier.tileBackground(color: Color): Modifier { // Clip tile contents from overflowing past the tile return clip(RoundedCornerShape(InactiveCornerRadius)).drawBehind { drawRect(color) } } private object EditModeTileDefaults { Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +30 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package com.android.systemui.qs.panels.ui.compose.infinitegrid import android.content.res.Resources import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.animateColorAsState import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.combinedClickable Loading Loading @@ -61,6 +62,7 @@ import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle 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.modifiers.thenIf Loading @@ -74,6 +76,7 @@ import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel Loading @@ -82,7 +85,6 @@ import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import java.util.function.Supplier import kotlinx.coroutines.CoroutineScope import com.android.app.tracing.coroutines.launchTraced as launch private const val TEST_TAG_SMALL = "qs_tile_small" private const val TEST_TAG_LARGE = "qs_tile_large" Loading Loading @@ -128,14 +130,18 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape = TileDefaults.animateTileShape(uiState.state) TileExpandable( color = val animatedColor by animateColorAsState( if (iconOnly || !uiState.handlesSecondaryClick) { colors.iconBackground } else { colors.background }, label = "QSTileBackgroundColor", ) TileExpandable( color = { animatedColor }, shape = tileShape, squishiness = squishiness, hapticsViewModel = hapticsViewModel, Loading Loading @@ -212,7 +218,7 @@ fun Tile( @Composable private fun TileExpandable( color: Color, color: () -> Color, shape: Shape, squishiness: () -> Float, hapticsViewModel: TileHapticsViewModel?, Loading @@ -220,7 +226,7 @@ private fun TileExpandable( content: @Composable (Expandable) -> Unit, ) { Expandable( color = color, color = color(), shape = shape, modifier = modifier.clip(shape).verticalSquish(squishiness), ) { Loading @@ -238,7 +244,7 @@ fun TileContainer( ) { Box( modifier = Modifier.height(CommonTileDefaults.TileHeight) Modifier.height(TileHeight) .fillMaxWidth() .tileCombinedClickable( onClick = onClick, Loading Loading @@ -335,6 +341,16 @@ private object TileDefaults { icon = MaterialTheme.colorScheme.onPrimary, ) @Composable fun inactiveDualTargetTileColors(): TileColors = TileColors( background = MaterialTheme.colorScheme.surfaceVariant, iconBackground = MaterialTheme.colorScheme.surfaceContainerHighest, label = MaterialTheme.colorScheme.onSurfaceVariant, secondaryLabel = MaterialTheme.colorScheme.onSurfaceVariant, icon = MaterialTheme.colorScheme.onSurfaceVariant, ) @Composable fun inactiveTileColors(): TileColors = TileColors( Loading Loading @@ -365,7 +381,13 @@ private object TileDefaults { activeTileColors() } } STATE_INACTIVE -> inactiveTileColors() STATE_INACTIVE -> { if (uiState.handlesSecondaryClick) { inactiveDualTargetTileColors() } else { inactiveTileColors() } } else -> unavailableTileColors() } } Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/ResizingState.kt +10 −5 Original line number Diff line number Diff line Loading @@ -17,25 +17,30 @@ package com.android.systemui.qs.panels.ui.compose.selection import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.setValue import com.android.systemui.qs.panels.ui.compose.selection.ResizingDefaults.RESIZING_THRESHOLD class ResizingState(private val widths: TileWidths, private val onResize: () -> Unit) { // Total drag offset of this resize operation private var totalOffset = 0f /** Total drag offset of this resize operation. */ private var totalOffset by mutableFloatStateOf(0f) /** Width in pixels of the resizing tile. */ var width by mutableIntStateOf(widths.base) /** Progression between icon (0) and large (1) sizes. */ val progression get() = calculateProgression() // Whether the tile is currently over the threshold and should be a large tile private var passedThreshold: Boolean = passedThreshold(calculateProgression(width)) private var passedThreshold: Boolean = passedThreshold(progression) fun onDrag(offset: Float) { totalOffset += offset width = (widths.base + totalOffset).toInt().coerceIn(widths.min, widths.max) passedThreshold(calculateProgression(width)).let { passedThreshold(progression).let { // Resize if we went over the threshold if (passedThreshold != it) { passedThreshold = it Loading @@ -49,7 +54,7 @@ class ResizingState(private val widths: TileWidths, private val onResize: () -> } /** The progression of the resizing tile between an icon tile (0f) and a large tile (1f) */ private fun calculateProgression(width: Int): Float { private fun calculateProgression(): Float { return ((width - widths.min) / (widths.max - widths.min).toFloat()).coerceIn(0f, 1f) } } Loading