diff options
| author | 2024-01-24 00:41:43 +0000 | |
|---|---|---|
| committer | 2024-01-24 00:47:49 +0000 | |
| commit | 016671bf9f3e4bea1c2fc5cf21fb26f7bc24633c (patch) | |
| tree | 2c0910788704812d877d641992bd836a70c217a0 | |
| parent | a9233e79a06fe177fe6f5adffa1e847c8cd899e9 (diff) | |
Long press anywhere to enter glanceable hub edit mode
Implemented the long press detector and applied to the glanceable
hub container. When long press is detected, a button will pop up
to enter edit mode.
Bug: b/318533815
Flag: ACONFIG com.android.systemui.communal_hub DEVELOPMENT
Test: on device
Change-Id: I57d274573c8bf3f6d4f0caeb4266d35cec3ac8ff
3 files changed, 88 insertions, 0 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 409f15bb4bb8..761e74e52237 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -97,11 +97,13 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Popup import androidx.core.view.setPadding +import com.android.compose.modifiers.thenIf import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth import com.android.systemui.communal.ui.compose.extensions.allowGestures +import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel @@ -132,6 +134,8 @@ fun CommunalHub( val removeButtonEnabled by remember { derivedStateOf { selectedIndex.value != null || reorderingWidgets } } + val (isButtonToEditWidgetsShowing, setIsButtonToEditWidgetsShowing) = + remember { mutableStateOf(false) } val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize) val contentOffset = beforeContentPadding(contentPadding).toOffset() @@ -158,6 +162,11 @@ fun CommunalHub( } viewModel.setSelectedIndex(newIndex) } + } + .thenIf(!viewModel.isEditMode) { + Modifier.pointerInput(Unit) { + detectLongPressGesture { offset -> setIsButtonToEditWidgetsShowing(true) } + } }, ) { CommunalHubLazyGrid( @@ -207,6 +216,16 @@ fun CommunalHub( PopupOnDismissCtaTile(viewModel::onHidePopupAfterDismissCta) } + if (isButtonToEditWidgetsShowing) { + ButtonToEditWidgets( + onClick = { + setIsButtonToEditWidgetsShowing(false) + viewModel.onOpenWidgetEditor() + }, + onHide = { setIsButtonToEditWidgetsShowing(false) }, + ) + } + // This spacer covers the edge of the LazyHorizontalGrid and prevents it from receiving // touches, so that the SceneTransitionLayout can intercept the touches and allow an edge // swipe back to the blank scene. @@ -414,6 +433,34 @@ private fun Toolbar( } @Composable +private fun ButtonToEditWidgets( + onClick: () -> Unit, + onHide: () -> Unit, +) { + Popup(alignment = Alignment.TopCenter, offset = IntOffset(0, 40), onDismissRequest = onHide) { + val colors = LocalAndroidColorScheme.current + Button( + modifier = + Modifier.height(56.dp).background(colors.secondary, RoundedCornerShape(50.dp)), + onClick = onClick, + ) { + Icon( + imageVector = Icons.Outlined.Widgets, + contentDescription = stringResource(R.string.button_to_configure_widgets_text), + tint = colors.onSecondary, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = stringResource(R.string.button_to_configure_widgets_text), + style = MaterialTheme.typography.titleSmall, + color = colors.onSecondary, + ) + } + } +} + +@Composable private fun PopupOnDismissCtaTile(onHidePopupAfterDismissCta: () -> Unit) { Popup( alignment = Alignment.TopCenter, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt index 14074944259b..bc1e429e57cf 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt @@ -20,9 +20,13 @@ import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventTimeoutCancellationException import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope +import androidx.compose.ui.util.fastAny +import androidx.compose.ui.util.fastForEach import kotlinx.coroutines.coroutineScope /** @@ -44,6 +48,41 @@ suspend fun PointerInputScope.observeTapsWithoutConsuming( } } +/** + * Detect long press gesture and calls onLongPress when detected. The callback parameter receives an + * Offset representing the position relative to the containing element. + */ +suspend fun PointerInputScope.detectLongPressGesture( + pass: PointerEventPass = PointerEventPass.Initial, + onLongPress: ((Offset) -> Unit), +) = coroutineScope { + awaitEachGesture { + val down = awaitFirstDown(pass = pass) + val longPressTimeout = viewConfiguration.longPressTimeoutMillis + // wait for first tap up or long press + try { + withTimeout(longPressTimeout) { waitForUpOrCancellation(pass = pass) } + } catch (_: PointerEventTimeoutCancellationException) { + // withTimeout throws exception if timeout has passed before block completes + onLongPress.invoke(down.position) + consumeUntilUp(pass) + } + } +} + +/** + * Consumes all pointer events until nothing is pressed and then returns. This method assumes that + * something is currently pressed. + */ +private suspend fun AwaitPointerEventScope.consumeUntilUp( + pass: PointerEventPass = PointerEventPass.Initial +) { + do { + val event = awaitPointerEvent(pass = pass) + event.changes.fastForEach { it.consume() } + } while (event.changes.fastAny { it.pressed }) +} + /** Consume all gestures on the initial pass so that child elements do not receive them. */ suspend fun PointerInputScope.consumeAllGestures() = coroutineScope { awaitEachGesture { diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 9bc7681665f1..2b43360f0689 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1089,6 +1089,8 @@ <string name="cta_label_to_open_widget_picker">Add more widgets</string> <!-- Text for the popup to be displayed after dismissing the CTA tile. [CHAR LIMIT=50] --> <string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string> + <!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] --> + <string name="button_to_configure_widgets_text">Customize widgets</string> <!-- Label for the button which configures widgets [CHAR LIMIT=NONE] --> <string name="edit_widget">Edit widget</string> <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] --> |