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

Commit 903b0ec1 authored by Olivier St-Onge's avatar Olivier St-Onge Committed by Android (Google) Code Review
Browse files

Merge "Fix the ripple implementation for tiles in edit mode" into main

parents 8ccfa04c 338ab39b
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
@@ -1167,9 +1167,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,
@@ -1219,8 +1219,9 @@ private fun LazyGridItemScope.TileGridCell(
                        customActions = actions
                    }
                }
                .thenIf(isSelectable) { selectableModifier }
                .thenIf(isSelectable) { draggableModifier }
                .tileBackground { backgroundColor }
                .thenIf(isSelectable) { selectableModifier }
        ) {
            EditTile(
                tile = cell.tile,
@@ -1284,16 +1285,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
                    }
                },
@@ -1311,7 +1311,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