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

Commit f1e1be0f authored by Coco Duan's avatar Coco Duan Committed by Android (Google) Code Review
Browse files

Merge "Long press anywhere to enter glanceable hub edit mode" into main

parents 9b26927b 016671bf
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -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.
@@ -413,6 +432,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(
+39 −0
Original line number Diff line number Diff line
@@ -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 {
+2 −0
Original line number Diff line number Diff line
@@ -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] -->