summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/strings.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt84
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt21
6 files changed, 127 insertions, 51 deletions
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6ff1240c5e60..3702710f4f24 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2593,6 +2593,9 @@
<!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string>
+ <!-- Accessibility description indicating the currently selected tile is already added [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_tile_already_added">Tile already added</string>
+
<!-- Accessibility announcement after a tile has been added [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_added">Tile added</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index ddadb8879f07..6e6c0b61d85b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -22,6 +22,7 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -78,6 +79,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@@ -140,6 +142,7 @@ import com.android.systemui.qs.panels.ui.compose.selection.TileState
import com.android.systemui.qs.panels.ui.compose.selection.rememberResizingState
import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState
import com.android.systemui.qs.panels.ui.compose.selection.selectableTile
+import com.android.systemui.qs.panels.ui.model.AvailableTileGridCell
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.SpacerGridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
@@ -290,8 +293,19 @@ fun DefaultEditTileGrid(
Text(text = stringResource(id = R.string.drag_to_add_tiles))
}
+ val availableTiles = remember {
+ mutableStateListOf<AvailableTileGridCell>().apply {
+ addAll(toAvailableTiles(listState.tiles, otherTiles))
+ }
+ }
+ LaunchedEffect(listState.tiles, otherTiles) {
+ availableTiles.apply {
+ clear()
+ addAll(toAvailableTiles(listState.tiles, otherTiles))
+ }
+ }
AvailableTileGrid(
- otherTiles,
+ availableTiles,
selectionState,
columns,
onAddTile,
@@ -444,7 +458,7 @@ private fun CurrentTilesGrid(
@Composable
private fun AvailableTileGrid(
- tiles: List<SizedTile<EditTileViewModel>>,
+ tiles: List<AvailableTileGridCell>,
selectionState: MutableSelectionState,
columns: Int,
onAddTile: (TileSpec) -> Unit,
@@ -453,7 +467,7 @@ private fun AvailableTileGrid(
// Available tiles aren't visible during drag and drop, so the row/col isn't needed
val groupedTiles =
remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) {
- groupAndSort(tiles.fastMap { TileGridCell(it, 0, 0) })
+ groupAndSort(tiles)
}
val labelColors = EditModeTileDefaults.editTileColors()
@@ -478,11 +492,10 @@ private fun AvailableTileGrid(
horizontalArrangement = spacedBy(TileArrangementPadding),
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Max),
) {
- row.forEachIndexed { index, tileGridCell ->
- key(tileGridCell.tile.tileSpec) {
+ row.forEach { tileGridCell ->
+ key(tileGridCell.key) {
AvailableTileGridCell(
cell = tileGridCell,
- index = index,
dragAndDropState = dragAndDropState,
selectionState = selectionState,
onAddTile = onAddTile,
@@ -505,10 +518,7 @@ fun gridHeight(rows: Int, tileHeight: Dp, tilePadding: Dp, gridPadding: Dp): Dp
}
private fun GridCell.key(index: Int): Any {
- return when (this) {
- is TileGridCell -> key
- is SpacerGridCell -> index
- }
+ return if (this is TileGridCell) key else index
}
/**
@@ -687,41 +697,44 @@ private fun TileGridCell(
@Composable
private fun AvailableTileGridCell(
- cell: TileGridCell,
- index: Int,
+ cell: AvailableTileGridCell,
dragAndDropState: DragAndDropState,
selectionState: MutableSelectionState,
onAddTile: (TileSpec) -> Unit,
modifier: Modifier = Modifier,
) {
- val onClickActionName = stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ val stateDescription: String? =
+ if (cell.isAvailable) null
+ else stringResource(R.string.accessibility_qs_edit_tile_already_added)
+
+ val alpha by animateFloatAsState(if (cell.isAvailable) 1f else .38f)
val colors = EditModeTileDefaults.editTileColors()
- val onClick = {
- onAddTile(cell.tile.tileSpec)
- selectionState.select(cell.tile.tileSpec)
- }
// Displays the tile as an icon tile with the label underneath
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top),
- modifier = modifier,
+ modifier =
+ modifier
+ .graphicsLayer { this.alpha = alpha }
+ .semantics(mergeDescendants = true) {
+ stateDescription?.let { this.stateDescription = it }
+ },
) {
Box(Modifier.fillMaxWidth().height(TileHeight)) {
- Box(
- Modifier.fillMaxSize()
- .clickable(onClick = onClick, onClickLabel = onClickActionName)
- .semantics(mergeDescendants = true) { this.stateDescription = stateDescription }
- .dragAndDropTileSource(
+ val draggableModifier =
+ if (cell.isAvailable) {
+ Modifier.dragAndDropTileSource(
SizedTileImpl(cell.tile, cell.width),
dragAndDropState,
DragType.Add,
) {
selectionState.unSelect()
}
- .tileBackground(colors.background)
- ) {
+ } else {
+ Modifier
+ }
+ Box(draggableModifier.fillMaxSize().tileBackground(colors.background)) {
// Icon
SmallTileContent(
iconProvider = { cell.tile.icon },
@@ -733,9 +746,13 @@ private fun AvailableTileGridCell(
StaticTileBadge(
icon = Icons.Default.Add,
- contentDescription = onClickActionName,
- onClick = onClick,
- )
+ contentDescription =
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action),
+ enabled = cell.isAvailable,
+ ) {
+ onAddTile(cell.tile.tileSpec)
+ selectionState.select(cell.tile.tileSpec)
+ }
}
Box(Modifier.fillMaxSize()) {
Text(
@@ -819,6 +836,15 @@ fun EditTile(
}
}
+private fun toAvailableTiles(
+ currentTiles: List<GridCell>,
+ otherTiles: List<SizedTile<EditTileViewModel>>,
+): List<AvailableTileGridCell> {
+ return currentTiles.filterIsInstance<TileGridCell>().fastMap {
+ AvailableTileGridCell(it.tile, isAvailable = false)
+ } + otherTiles.fastMap { AvailableTileGridCell(it.tile) }
+}
+
private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float {
return (containerSize - ToggleTargetSize.roundToPx()) / 2f -
CommonTileDefaults.TilePadding.toPx()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 699e5f6b77e9..153238fc91c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.panels.ui.compose.selection
import androidx.compose.animation.animateColor
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.animateOffset
import androidx.compose.animation.core.animateSize
import androidx.compose.animation.core.updateTransition
@@ -61,6 +62,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
import com.android.compose.modifiers.size
+import com.android.compose.modifiers.thenIf
import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BADGE_ANGLE_RAD
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.BadgeSize
@@ -184,18 +186,37 @@ private fun Modifier.selectionBorder(
}
}
+/**
+ * Draws a clickable badge in the top end corner of the parent composable.
+ *
+ * The badge will fade in and fade out based on whether or not it's enabled.
+ *
+ * @param icon the [ImageVector] to display in the badge
+ * @param contentDescription the content description for the icon
+ * @param enabled Whether the badge should be visible and clickable
+ * @param onClick the callback when the badge is clicked
+ */
@Composable
-fun StaticTileBadge(icon: ImageVector, contentDescription: String?, onClick: () -> Unit) {
+fun StaticTileBadge(
+ icon: ImageVector,
+ contentDescription: String?,
+ enabled: Boolean,
+ onClick: () -> Unit,
+) {
val offset = with(LocalDensity.current) { Offset(BadgeXOffset.toPx(), BadgeYOffset.toPx()) }
+ val alpha by animateFloatAsState(if (enabled) 1f else 0f)
MinimumInteractiveSizeComponent(angle = { BADGE_ANGLE_RAD }, offset = { offset }) {
Box(
Modifier.fillMaxSize()
- .clickable(
- interactionSource = null,
- indication = null,
- onClickLabel = contentDescription,
- onClick = onClick,
- )
+ .graphicsLayer { this.alpha = alpha }
+ .thenIf(enabled) {
+ Modifier.clickable(
+ interactionSource = null,
+ indication = null,
+ onClickLabel = contentDescription,
+ onClick = onClick,
+ )
+ }
) {
val secondaryColor = MaterialTheme.colorScheme.secondary
Icon(
@@ -214,7 +235,8 @@ fun StaticTileBadge(icon: ImageVector, contentDescription: String?, onClick: ()
private fun MinimumInteractiveSizeComponent(
angle: () -> Float,
offset: () -> Offset,
- content: @Composable BoxScope.() -> Unit,
+ modifier: Modifier = Modifier,
+ content: @Composable BoxScope.() -> Unit = {},
) {
// Use a higher zIndex than the tile to draw over it, and manually create the touch target
// as we're drawing over neighbor tiles as well.
@@ -222,7 +244,8 @@ private fun MinimumInteractiveSizeComponent(
Box(
contentAlignment = Alignment.Center,
modifier =
- Modifier.zIndex(2f)
+ modifier
+ .zIndex(2f)
.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) }
.layout { measurable, constraints ->
val size = minTouchTargetSize.roundToPx()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index c0441f8a38a1..78fd8c0168dd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -21,13 +21,13 @@ import androidx.compose.runtime.Immutable
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.CategoryAndName
/** Represents an item from a grid associated with a row and a span */
sealed interface GridCell {
val row: Int
val span: GridItemSpan
- val s: String
}
/**
@@ -40,7 +40,6 @@ data class TileGridCell(
override val row: Int,
override val width: Int,
override val span: GridItemSpan = GridItemSpan(width),
- override val s: String = "${tile.tileSpec.spec}-$row-$width",
val column: Int,
) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile {
val key: String = "${tile.tileSpec.spec}-$row"
@@ -52,12 +51,23 @@ data class TileGridCell(
) : this(tile = sizedTile.tile, row = row, column = column, width = sizedTile.width)
}
+/**
+ * Represents a [EditTileViewModel] from the edit mode available tiles grid and whether it is
+ * available to add or not.
+ */
+@Immutable
+data class AvailableTileGridCell(
+ override val tile: EditTileViewModel,
+ override val width: Int = 1,
+ val isAvailable: Boolean = true,
+ val key: TileSpec = tile.tileSpec,
+) : SizedTile<EditTileViewModel>, CategoryAndName by tile
+
/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
@Immutable
data class SpacerGridCell(
override val row: Int,
override val span: GridItemSpan = GridItemSpan(1),
- override val s: String = "spacer",
) : GridCell
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
index 8c09b81744d7..e76be82c9340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
@@ -25,6 +25,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
@@ -80,7 +82,9 @@ class EditModeTest : SysuiTestCase() {
composeRule.assertCurrentTilesGridContainsExactly(
listOf("tileA", "tileB", "tileC", "tileD_large", "tileE", "tileF")
)
- composeRule.assertAvailableTilesGridContainsExactly(listOf("tileG_large"))
+ composeRule.assertAvailableTilesGridContainsExactly(
+ TestEditTiles.map { it.tile.tileSpec.spec }
+ )
}
@Test
@@ -88,7 +92,8 @@ class EditModeTest : SysuiTestCase() {
composeRule.setContent { EditTileGridUnderTest() }
composeRule.waitForIdle()
- composeRule.onNodeWithContentDescription("tileA").performClick() // Selects
+ // Selects first "tileA", i.e. the one in the current grid
+ composeRule.onAllNodesWithText("tileA").onFirst().performClick()
composeRule.onNodeWithText("Remove").performClick() // Removes
composeRule.waitForIdle()
@@ -96,7 +101,9 @@ class EditModeTest : SysuiTestCase() {
composeRule.assertCurrentTilesGridContainsExactly(
listOf("tileB", "tileC", "tileD_large", "tileE")
)
- composeRule.assertAvailableTilesGridContainsExactly(listOf("tileA", "tileF", "tileG_large"))
+ composeRule.assertAvailableTilesGridContainsExactly(
+ TestEditTiles.map { it.tile.tileSpec.spec }
+ )
}
private fun ComposeContentTestRule.assertCurrentTilesGridContainsExactly(specs: List<String>) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index bd4c5f50eee7..021be3235ee1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -25,7 +25,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.click
import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performCustomAccessibilityActionWithLabel
import androidx.compose.ui.test.performTouchInput
@@ -85,7 +86,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performCustomAccessibilityActionWithLabel(
context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
)
@@ -103,7 +105,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performCustomAccessibilityActionWithLabel(
context.getString(R.string.accessibility_qs_edit_toggle_tile_size_action)
)
@@ -121,7 +124,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -141,7 +145,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Tap on resizing handle
click(centerRight)
@@ -161,7 +166,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileA")
+ .onAllNodesWithText("tileA")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Resize up
swipeRight(startX = right, endX = right * 2)
@@ -181,7 +187,8 @@ class ResizingTest : SysuiTestCase() {
composeRule.waitForIdle()
composeRule
- .onNodeWithContentDescription("tileD_large")
+ .onAllNodesWithText("tileD_large")
+ .onFirst()
.performClick() // Select
.performTouchInput { // Resize down
swipeLeft()