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

Commit 338ab39b authored by Olivier St-Onge's avatar Olivier St-Onge
Browse files

Fix the ripple implementation for tiles in edit mode

1. Fix the ripple shape for available tiles
2. Add the ripple on taps and hover for current tiles

Flag: EXEMPT bugfix
Fixes: 433236252
Test: manually - selecting and dragging tiles
Test: manually - navigating using Talkback
Test: manually - interacting with tiles using a mouse
Change-Id: I1788f3225d25ae4892c7337089598ae2f1d888e5
parent 41995c9d
Loading
Loading
Loading
Loading
+55 −32
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.systemui.common.ui.compose.gestures
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
@@ -40,15 +42,24 @@ import kotlinx.coroutines.coroutineScope
 * @param onTap the single tap callback
 */
suspend fun PointerInputScope.detectEagerTapGestures(
    interactionSource: MutableInteractionSource,
    doubleTapEnabled: () -> Boolean,
    onDoubleTap: (Offset) -> Unit,
    onTap: () -> Unit,
) = coroutineScope {
    awaitEachGesture {
        var firstPress: PressInteraction.Press? = null
        var secondPress: PressInteraction.Press? = null

        try {
            val down = awaitFirstDown()
            down.consume()

        // Capture whether double tap is enabled on first down as this state can change following
            firstPress = PressInteraction.Press(down.position)
            interactionSource.tryEmit(firstPress)

            // Capture whether double tap is enabled on first down as this state can change
            // following
            // the first tap
            val isDoubleTapEnabled = doubleTapEnabled()

@@ -60,6 +71,9 @@ suspend fun PointerInputScope.detectEagerTapGestures(
                upOrCancel.consume()
                onTap.invoke()

                interactionSource.tryEmit(PressInteraction.Release(firstPress))
                firstPress = null

                if (isDoubleTapEnabled) {
                    // check for second tap
                    val secondDown =
@@ -67,8 +81,8 @@ suspend fun PointerInputScope.detectEagerTapGestures(
                            val minUptime =
                                upOrCancel.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis
                            var change: PointerInputChange
                        // The second tap doesn't count if it happens before DoubleTapMinTime of the
                        // first tap
                            // The second tap doesn't count if it happens before DoubleTapMinTime of
                            // the first tap
                            do {
                                change = awaitFirstDown()
                            } while (change.uptimeMillis < minUptime)
@@ -77,15 +91,24 @@ suspend fun PointerInputScope.detectEagerTapGestures(

                    if (secondDown != null) {
                        // Second tap down detected
                        secondPress = PressInteraction.Press(secondDown.position)
                        interactionSource.tryEmit(secondPress)

                        // Might have a long second press as the second tap
                        val secondUp = waitForUpOrCancellation()
                        if (secondUp != null) {
                            secondUp.consume()
                            onDoubleTap(secondUp.position)
                            interactionSource.tryEmit(PressInteraction.Release(secondPress))
                            secondPress = null
                        }
                    }
                }
            }
        } finally {
            // Cancelling pending interactions when the scope is cancelled
            firstPress?.let { interactionSource.tryEmit(PressInteraction.Cancel(it)) }
            secondPress?.let { interactionSource.tryEmit(PressInteraction.Cancel(it)) }
        }
    }
}
+26 −17
Original line number Diff line number Diff line
@@ -1035,9 +1035,9 @@ private fun LazyGridItemScope.TileGridCell(
        LaunchedEffect(canLayoutTile, dragAndDropState.dragInProgress) {
            isSelectable = canLayoutTile && !dragAndDropState.dragInProgress
        }
        val selectableModifier =
            Modifier.selectableTile(cell.tile.tileSpec, selectionState)
                .dragAndDropTileSource(
        val selectableModifier = Modifier.selectableTile(cell.tile.tileSpec, selectionState)
        val draggableModifier =
            Modifier.dragAndDropTileSource(
                SizedTileImpl(cell.tile, cell.width),
                dragAndDropState,
                DragType.Move,
@@ -1087,8 +1087,9 @@ private fun LazyGridItemScope.TileGridCell(
                        customActions = actions
                    }
                }
                .thenIf(isSelectable) { selectableModifier }
                .thenIf(isSelectable) { draggableModifier }
                .tileBackground { backgroundColor }
                .thenIf(isSelectable) { selectableModifier }
        ) {
            EditTile(
                tile = cell.tile,
@@ -1152,16 +1153,15 @@ private fun AvailableTileGridCell(
        modifier =
            modifier
                .graphicsLayer { this.alpha = alpha }
                .clickable(enabled = !cell.isCurrent, onClick = onClick, onClickLabel = clickLabel)
                .semantics {
                .semantics(mergeDescendants = true) {
                    if (stateDescription != null) {
                        this.stateDescription = stateDescription
                    } else {
                        // This is needed due to b/418803616. When a clickable element that doesn't
                        // have semantics is slightly out of bounds of a scrollable container, it
                        // will be found by talkback. Because the text is off screen, it will say
                        // "Unlabelled". Instead, give it a role (that is also meaningful when on
                        // screen), and it will be skipped when not visible.
                        // This is needed due to b/418803616. When a clickable element that
                        // doesn't have semantics is slightly out of bounds of a scrollable
                        // container, it will be found by talkback. Because the text is off screen,
                        // it will say "Unlabelled". Instead, give it a role (that is also
                        // meaningful when on screen), and it will be skipped when not visible.
                        this.role = Role.Button
                    }
                },
@@ -1179,7 +1179,16 @@ private fun AvailableTileGridCell(
                        selectionState.unSelect()
                    }
                }
            Box(draggableModifier.fillMaxSize().tileBackground { colors.background }) {
            Box(
                Modifier.then(draggableModifier)
                    .fillMaxSize()
                    .tileBackground { colors.background }
                    .clickable(
                        enabled = !cell.isCurrent,
                        onClick = onClick,
                        onClickLabel = clickLabel,
                    )
            ) {
                // Icon
                SmallTileContent(
                    iconProvider = { cell.icon },
+20 −11
Original line number Diff line number Diff line
@@ -16,6 +16,10 @@

package com.android.systemui.qs.panels.ui.compose.selection

import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
@@ -168,15 +172,20 @@ sealed interface PlacementEvent {
/**
 * Listens for click events on selectable tiles.
 *
 * Use this on current tiles as they can be selected.
 * Use this on current tiles as they can be selected. This applies the [LocalIndication] on taps and
 * hover.
 *
 * @param tileSpec the [TileSpec] of the tile this modifier is applied to
 * @param selectionState the [MutableSelectionState] representing the grid's selection
 */
@Composable
fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelectionState): Modifier {
    return pointerInput(Unit) {
    val interactionSource = remember { MutableInteractionSource() }
    return hoverable(interactionSource)
        .indication(interactionSource, LocalIndication.current)
        .pointerInput(Unit) {
            detectEagerTapGestures(
                interactionSource = interactionSource,
                doubleTapEnabled = {
                    // Double tap enabled if where not in placement mode already
                    !selectionState.placementEnabled