Loading packages/SystemUI/src/com/android/systemui/common/ui/compose/gestures/EagerTap.kt +55 −32 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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 = Loading @@ -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) Loading @@ -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)) } } } } packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +26 −17 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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, Loading Loading @@ -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 } }, Loading @@ -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 }, Loading packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt +20 −11 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading Loading
packages/SystemUI/src/com/android/systemui/common/ui/compose/gestures/EagerTap.kt +55 −32 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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 = Loading @@ -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) Loading @@ -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)) } } } }
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +26 −17 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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, Loading Loading @@ -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 } }, Loading @@ -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 }, Loading
packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt +20 −11 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading