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

Commit 58312c64 authored by Olivier St-Onge's avatar Olivier St-Onge
Browse files

Add greyed out available tiles once they are added to the current grid

Available tiles shows every tiles. Added tiles are greyed out and can't be interacted with until they are removed from the current grid

Test: manually
Test: EditModeTest
Flag: com.android.systemui.qs_ui_refactor_compose_fragment
Fixes: 396690775
Change-Id: I6aaddec033ffd87a2df29303adc3c0461947892d
parent 6f022908
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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>

+55 −29
Original line number Diff line number Diff line
@@ -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()
+32 −9
Original line number Diff line number Diff line
@@ -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(
                .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()
+13 −3
Original line number Diff line number Diff line
@@ -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

/**
+10 −3
Original line number Diff line number Diff line
@@ -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>) =
Loading