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

Commit 64bc28bd authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[flexiglass] Fixes long-press on emergency button." into main

parents bc83c3d0 3e388b26
Loading
Loading
Loading
Loading
+72 −154
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
@@ -46,7 +47,6 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
@@ -64,6 +64,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.onKeyEvent
@@ -72,6 +73,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
@@ -88,7 +92,6 @@ import com.android.compose.animation.scene.SceneKey
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.SceneTransitionLayout
import com.android.compose.animation.scene.transitions
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.bouncer.ui.BouncerDialogFactory
@@ -131,7 +134,7 @@ fun BouncerContent(
    layout: BouncerSceneLayout,
    viewModel: BouncerSceneContentViewModel,
    dialogFactory: BouncerDialogFactory,
    modifier: Modifier
    modifier: Modifier,
) {
    Box(
        // Allows the content within each of the layouts to react to the appearance and
@@ -140,31 +143,17 @@ fun BouncerContent(
        // Despite the keyboard only being part of the password bouncer, adding it at this level is
        // both necessary to properly handle the keyboard in all layouts and harmless in cases when
        // the keyboard isn't used (like the PIN or pattern auth methods).
        modifier = modifier.imePadding().onKeyEvent(viewModel::onKeyEvent),
        modifier = modifier.imePadding().onKeyEvent(viewModel::onKeyEvent)
    ) {
        when (layout) {
            BouncerSceneLayout.STANDARD_BOUNCER ->
                StandardLayout(
                    viewModel = viewModel,
                )
            BouncerSceneLayout.STANDARD_BOUNCER -> StandardLayout(viewModel = viewModel)
            BouncerSceneLayout.BESIDE_USER_SWITCHER ->
                BesideUserSwitcherLayout(
                    viewModel = viewModel,
                )
            BouncerSceneLayout.BELOW_USER_SWITCHER ->
                BelowUserSwitcherLayout(
                    viewModel = viewModel,
                )
            BouncerSceneLayout.SPLIT_BOUNCER ->
                SplitLayout(
                    viewModel = viewModel,
                )
                BesideUserSwitcherLayout(viewModel = viewModel)
            BouncerSceneLayout.BELOW_USER_SWITCHER -> BelowUserSwitcherLayout(viewModel = viewModel)
            BouncerSceneLayout.SPLIT_BOUNCER -> SplitLayout(viewModel = viewModel)
        }

        Dialog(
            bouncerViewModel = viewModel,
            dialogFactory = dialogFactory,
        )
        Dialog(bouncerViewModel = viewModel, dialogFactory = dialogFactory)
    }
}

@@ -173,31 +162,19 @@ fun BouncerContent(
 * authentication attempt, including all messaging UI (directives, reasoning, errors, etc.).
 */
@Composable
private fun StandardLayout(
    viewModel: BouncerSceneContentViewModel,
    modifier: Modifier = Modifier,
) {
private fun StandardLayout(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) {
    val isHeightExpanded =
        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded

    FoldAware(
        modifier =
            modifier.padding(
                start = 32.dp,
                top = 92.dp,
                end = 32.dp,
                bottom = 48.dp,
            ),
        modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 48.dp),
        viewModel = viewModel,
        aboveFold = {
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier.fillMaxWidth(),
            ) {
                StatusMessage(
                    viewModel = viewModel.message,
                    modifier = Modifier,
                )
                StatusMessage(viewModel = viewModel.message, modifier = Modifier)

                OutputArea(
                    viewModel = viewModel,
@@ -210,9 +187,7 @@ private fun StandardLayout(
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier.fillMaxWidth(),
            ) {
                Box(
                    modifier = Modifier.weight(1f),
                ) {
                Box(modifier = Modifier.weight(1f)) {
                    InputArea(
                        viewModel = viewModel,
                        pinButtonRowVerticalSpacing = 12.dp,
@@ -221,10 +196,7 @@ private fun StandardLayout(
                    )
                }

                ActionArea(
                    viewModel = viewModel,
                    modifier = Modifier.padding(top = 48.dp),
                )
                ActionArea(viewModel = viewModel, modifier = Modifier.padding(top = 48.dp))
            }
        },
    )
@@ -235,10 +207,7 @@ private fun StandardLayout(
 * by double-tapping on the side.
 */
@Composable
private fun SplitLayout(
    viewModel: BouncerSceneContentViewModel,
    modifier: Modifier = Modifier,
) {
private fun SplitLayout(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) {
    val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()

    Row(
@@ -248,12 +217,10 @@ private fun SplitLayout(
                .padding(
                    horizontal = 24.dp,
                    vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp,
                ),
                )
    ) {
        // Left side (in left-to-right locales).
        Box(
            modifier = Modifier.fillMaxHeight().weight(1f),
        ) {
        Box(modifier = Modifier.fillMaxHeight().weight(1f)) {
            when (authMethod) {
                is PinBouncerViewModel -> {
                    StatusMessage(
@@ -263,7 +230,7 @@ private fun SplitLayout(
                    OutputArea(
                        viewModel = viewModel,
                        modifier =
                            Modifier.align(Alignment.Center).sysuiResTag("bouncer_text_entry")
                            Modifier.align(Alignment.Center).sysuiResTag("bouncer_text_entry"),
                    )

                    ActionArea(
@@ -293,9 +260,7 @@ private fun SplitLayout(
        }

        // Right side (in right-to-left locales).
        Box(
            modifier = Modifier.fillMaxHeight().weight(1f),
        ) {
        Box(modifier = Modifier.fillMaxHeight().weight(1f)) {
            when (authMethod) {
                is PinBouncerViewModel,
                is PatternBouncerViewModel -> {
@@ -311,13 +276,11 @@ private fun SplitLayout(
                        horizontalAlignment = Alignment.CenterHorizontally,
                        modifier = Modifier.fillMaxWidth().align(Alignment.Center),
                    ) {
                        StatusMessage(
                            viewModel = viewModel.message,
                        )
                        StatusMessage(viewModel = viewModel.message)
                        OutputArea(
                            viewModel = viewModel,
                            modifier =
                                Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry")
                                Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry"),
                        )
                    }
                }
@@ -365,7 +328,7 @@ private fun BesideUserSwitcherLayout(
                .padding(
                    top = if (isHeightExpanded) 128.dp else 96.dp,
                    bottom = if (isHeightExpanded) 128.dp else 48.dp,
                ),
                )
    ) {
        LaunchedEffect(isSwapped) { swapAnimationEnd = false }
        val animatedOffset by
@@ -419,14 +382,12 @@ private fun BesideUserSwitcherLayout(
            aboveFold = {
                Column(
                    horizontalAlignment = Alignment.CenterHorizontally,
                    modifier = Modifier.fillMaxWidth()
                    modifier = Modifier.fillMaxWidth(),
                ) {
                    StatusMessage(
                        viewModel = viewModel.message,
                    )
                    StatusMessage(viewModel = viewModel.message)
                    OutputArea(
                        viewModel = viewModel,
                        modifier = Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry")
                        modifier = Modifier.padding(top = 24.dp).sysuiResTag("bouncer_text_entry"),
                    )
                }
            },
@@ -444,7 +405,7 @@ private fun BesideUserSwitcherLayout(
                    Box(
                        modifier =
                            Modifier.weight(1f)
                                .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp),
                                .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp)
                    ) {
                        InputArea(
                            viewModel = viewModel,
@@ -470,16 +431,8 @@ private fun BelowUserSwitcherLayout(
    viewModel: BouncerSceneContentViewModel,
    modifier: Modifier = Modifier,
) {
    Column(
        modifier =
            modifier.padding(
                vertical = 128.dp,
            )
    ) {
        UserSwitcher(
            viewModel = viewModel,
            modifier = Modifier.fillMaxWidth(),
        )
    Column(modifier = modifier.padding(vertical = 128.dp)) {
        UserSwitcher(viewModel = viewModel, modifier = Modifier.fillMaxWidth())

        Spacer(Modifier.weight(1f))

@@ -488,9 +441,7 @@ private fun BelowUserSwitcherLayout(
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier.fillMaxWidth(),
            ) {
                StatusMessage(
                    viewModel = viewModel.message,
                )
                StatusMessage(viewModel = viewModel.message)
                OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))

                InputArea(
@@ -500,10 +451,7 @@ private fun BelowUserSwitcherLayout(
                    modifier = Modifier.padding(top = 128.dp),
                )

                ActionArea(
                    viewModel = viewModel,
                    modifier = Modifier.padding(top = 48.dp),
                )
                ActionArea(viewModel = viewModel, modifier = Modifier.padding(top = 48.dp))
            }
        }
    }
@@ -533,19 +481,11 @@ private fun FoldAware(

    SceneTransitionLayout(state, modifier = modifier) {
        scene(SceneKeys.ContiguousSceneKey) {
            FoldableScene(
                aboveFold = aboveFold,
                belowFold = belowFold,
                isSplit = false,
            )
            FoldableScene(aboveFold = aboveFold, belowFold = belowFold, isSplit = false)
        }

        scene(SceneKeys.SplitSceneKey) {
            FoldableScene(
                aboveFold = aboveFold,
                belowFold = belowFold,
                isSplit = true,
            )
            FoldableScene(aboveFold = aboveFold, belowFold = belowFold, isSplit = true)
        }
    }
}
@@ -562,9 +502,7 @@ private fun SceneScope.FoldableScene(
            R.dimen.motion_layout_half_fold_bouncer_height_ratio
        )

    Column(
        modifier = modifier.fillMaxHeight(),
    ) {
    Column(modifier = modifier.fillMaxHeight()) {
        // Content above the fold, when split on a foldable device in a "table top" posture:
        Box(
            modifier =
@@ -575,7 +513,7 @@ private fun SceneScope.FoldableScene(
                        } else {
                            Modifier
                        }
                    ),
                    )
        ) {
            aboveFold()
        }
@@ -590,7 +528,7 @@ private fun SceneScope.FoldableScene(
                        } else {
                            1f
                        }
                    ),
                    )
        ) {
            belowFold()
        }
@@ -598,10 +536,7 @@ private fun SceneScope.FoldableScene(
}

@Composable
private fun StatusMessage(
    viewModel: BouncerMessageViewModel,
    modifier: Modifier = Modifier,
) {
private fun StatusMessage(viewModel: BouncerMessageViewModel, modifier: Modifier = Modifier) {
    val message: MessageViewModel? by viewModel.message.collectAsStateWithLifecycle()

    DisposableEffect(Unit) {
@@ -634,7 +569,7 @@ private fun StatusMessage(
                    fontSize = 14.sp,
                    lineHeight = 20.sp,
                    overflow = TextOverflow.Ellipsis,
                    maxLines = 2
                    maxLines = 2,
                )
            }
        }
@@ -647,22 +582,19 @@ private fun StatusMessage(
 * For example, this can be the PIN shapes or password text field.
 */
@Composable
private fun OutputArea(
    viewModel: BouncerSceneContentViewModel,
    modifier: Modifier = Modifier,
) {
private fun OutputArea(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) {
    val authMethodViewModel: AuthMethodBouncerViewModel? by
        viewModel.authMethodViewModel.collectAsStateWithLifecycle()
    when (val nonNullViewModel = authMethodViewModel) {
        is PinBouncerViewModel ->
            PinInputDisplay(
                viewModel = nonNullViewModel,
                modifier = modifier.sysuiResTag("bouncer_text_entry")
                modifier = modifier.sysuiResTag("bouncer_text_entry"),
            )
        is PasswordBouncerViewModel ->
            PasswordBouncer(
                viewModel = nonNullViewModel,
                modifier = modifier.sysuiResTag("bouncer_text_entry")
                modifier = modifier.sysuiResTag("bouncer_text_entry"),
            )
        else -> Unit
    }
@@ -703,10 +635,7 @@ private fun InputArea(
}

@Composable
private fun ActionArea(
    viewModel: BouncerSceneContentViewModel,
    modifier: Modifier = Modifier,
) {
private fun ActionArea(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) {
    val actionButton: BouncerActionButtonModel? by
        viewModel.actionButton.collectAsStateWithLifecycle()
    val appearFadeInAnimatable = remember { Animatable(0f) }
@@ -722,7 +651,7 @@ private fun ActionArea(
                        durationMillis = 450,
                        delayMillis = 133,
                        easing = Easings.LegacyDecelerate,
                    )
                    ),
            )
        }
        LaunchedEffect(Unit) {
@@ -733,42 +662,38 @@ private fun ActionArea(
                        durationMillis = 450,
                        delayMillis = 133,
                        easing = Easings.StandardDecelerate,
                    )
                    ),
            )
        }

        Box(
            modifier =
                modifier.graphicsLayer {
                modifier
                    .graphicsLayer {
                        // Translate the button up from an initially pushed-down position:
                    translationY = (1 - appearMoveAnimatable.value) * appearAnimationInitialOffset
                        translationY =
                            (1 - appearMoveAnimatable.value) * appearAnimationInitialOffset
                        // Fade the button in:
                        alpha = appearFadeInAnimatable.value
                },
        ) {
            Button(
                onClick = actionButtonViewModel.onClick,
                modifier =
                    Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) {
                        Modifier.combinedClickable(
                            onClick = actionButtonViewModel.onClick,
                            onLongClick = actionButtonViewModel.onLongClick,
                    }
                    .height(56.dp)
                    .clip(ButtonDefaults.shape)
                    .background(color = MaterialTheme.colorScheme.tertiaryContainer)
                    .semantics { role = Role.Button }
                    .combinedClickable(
                        onClick = { actionButtonViewModel.onClick() },
                        onLongClick = actionButtonViewModel.onLongClick?.let { { it.invoke() } },
                    )
                    },
                colors =
                    ButtonDefaults.buttonColors(
                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
                    ),
        ) {
            Text(
                text = actionButtonViewModel.label,
                style = MaterialTheme.typography.bodyMedium,
                color = MaterialTheme.colorScheme.onTertiaryContainer,
                modifier = Modifier.align(Alignment.Center).padding(ButtonDefaults.ContentPadding),
            )
        }
    }
}
}

@Composable
private fun Dialog(
@@ -800,15 +725,10 @@ private fun Dialog(

/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
@Composable
private fun UserSwitcher(
    viewModel: BouncerSceneContentViewModel,
    modifier: Modifier = Modifier,
) {
private fun UserSwitcher(viewModel: BouncerSceneContentViewModel, modifier: Modifier = Modifier) {
    if (!viewModel.isUserSwitcherVisible) {
        // Take up the same space as the user switcher normally would, but with nothing inside it.
        Box(
            modifier = modifier,
        )
        Box(modifier = modifier)
        return
    }

@@ -891,7 +811,7 @@ private fun UserSwitcherDropdownMenu(
    MaterialTheme(
        colorScheme =
            MaterialTheme.colorScheme.copy(
                surface = MaterialTheme.colorScheme.surfaceContainerHighest,
                surface = MaterialTheme.colorScheme.surfaceContainerHighest
            ),
        shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(28.dp)),
    ) {
@@ -932,9 +852,7 @@ private fun UserSwitcherDropdownMenu(
 * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of
 * the two reaches a stopping point but `0` in the middle of the transition.
 */
private fun animatedAlpha(
    offset: Float,
): Float {
private fun animatedAlpha(offset: Float): Float {
    // Describes a curve that is made of two parabolic U-shaped curves mirrored horizontally around
    // the y-axis. The U on the left runs between x = -1 and x = 0 while the U on the right runs
    // between x = 0 and x = 1.
+0 −2
Original line number Diff line number Diff line
@@ -123,8 +123,6 @@ constructor(
            onLongClick = {
                if (emergencyAffordanceManager.needsEmergencyAffordance()) {
                    prepareToPerformAction()

                    // TODO(b/369767936): Check that !longPressWasDragged before invoking.
                    emergencyAffordanceManager.performEmergencyCall()
                }
            },