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

Commit 5a0147f0 authored by Josh's avatar Josh
Browse files

Redesigned Add Shortcut Dialog Edit Box

The add shortcut dialog has a box where the users' selected key
combination is shown. Previously this box was just a clickable surface
that displayed some text/icon composable.

There are some downsides to this design:
1. No blinking cursor to show the user that they're currently editing.
2. A11y - No talkback actions on double click despite talback reading
   "double tap to activate"

The main consideration for not using a textfield composable previously
was that textfields can only show text, but keyboard keys can be
represented as text or glyphs(drawables) which traditional textfields
don't support.

This CL implements a custom InputField composable `OutlinedInputField`
which has all the benefits of a text field(blinking cursor, A11y
features) but rather than displaying just text, it generally supports
displaying any composable content which can be text, icon, or anything
else.this solution hence addresses both Downsides to the previous
design.

Test: Manual - Ensure the desired behaviour is observed in the add
shortcut dialog.
Flag: com.android.systemui.keyboard_shortcut_helper_shortcut_customizer
Fix: 390278358
Fix: 390102227
Fix: 390281127
Bug: 387995731

Change-Id: Ic5af2c9ba06a8cd0c204f742804d6202f2043e8a
parent 10959b9e
Loading
Loading
Loading
Loading
+97 −63
Original line number Diff line number Diff line
@@ -17,14 +17,10 @@
package com.android.systemui.keyboard.shortcut.ui.composable

import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@@ -38,13 +34,15 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ErrorOutline
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
@@ -55,6 +53,7 @@ import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.LiveRegionMode
@@ -247,7 +246,8 @@ private fun ErrorMessageContainer(errorMessage: String) {
                lineHeight = 20.sp,
                fontWeight = FontWeight.W500,
                color = MaterialTheme.colorScheme.error,
                modifier = Modifier.padding(start = 24.dp).width(252.dp).semantics {
                modifier =
                    Modifier.padding(start = 24.dp).width(252.dp).semantics {
                        contentDescription = errorMessage
                        liveRegion = LiveRegionMode.Polite
                    },
@@ -263,49 +263,50 @@ private fun SelectedKeyCombinationContainer(
    pressedKeys: List<ShortcutKey>,
    onConfirmSetShortcut: () -> Unit,
) {
    val interactionSource = remember { MutableInteractionSource() }
    val isFocused by interactionSource.collectIsFocusedAsState()
    val outlineColor =
        if (!isFocused) MaterialTheme.colorScheme.outline
        else if (shouldShowError) MaterialTheme.colorScheme.error
        else MaterialTheme.colorScheme.primary
    val focusRequester = remember { FocusRequester() }

    val focusManager = LocalFocusManager.current
    LaunchedEffect(Unit) { focusRequester.requestFocus() }

    ClickableShortcutSurface(
        onClick = {},
        color = Color.Transparent,
        shape = RoundedCornerShape(50.dp),
    OutlinedInputField(
        modifier =
            Modifier.padding(all = 16.dp)
                .sizeIn(minWidth = 332.dp, minHeight = 56.dp)
                .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp))
                .focusRequester(focusRequester)
                .focusProperties { canFocus = true }
                .onPreviewKeyEvent { keyEvent ->
                    val keyEventProcessed = onShortcutKeyCombinationSelected(keyEvent)
                    if (
                        !keyEventProcessed &&
                            keyEvent.key == Key.Enter &&
                            keyEvent.type == KeyEventType.KeyUp
                    ) {
                        onConfirmSetShortcut()
                    if (keyEventProcessed) {
                        true
                    } else keyEventProcessed
                    } else {
                        if (keyEvent.type == KeyEventType.KeyUp) {
                            when (keyEvent.key) {
                                Key.Enter -> {
                                    onConfirmSetShortcut()
                                    return@onPreviewKeyEvent true
                                }
                .focusProperties { canFocus = true } // enables keyboard focus when in touch mode
                .focusRequester(focusRequester),
        interactionSource = interactionSource,
    ) {
        Row(
            modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            if (pressedKeys.isEmpty()) {
                PressKeyPrompt()
                                Key.DirectionDown -> {
                                    focusManager.moveFocus(FocusDirection.Down)
                                    return@onPreviewKeyEvent true
                                }
                                else -> return@onPreviewKeyEvent false
                            }
                        } else false
                    }
                },
        trailingIcon = { ErrorIcon(shouldShowError) },
        isError = shouldShowError,
        placeholder = { PressKeyPrompt() },
        content =
            if (pressedKeys.isNotEmpty()) {
                { PressedKeysTextContainer(pressedKeys) }
            } else {
                PressedKeysTextContainer(pressedKeys)
                null
            },
    )
}
            Spacer(modifier = Modifier.weight(1f))

@Composable
private fun ErrorIcon(shouldShowError: Boolean) {
    if (shouldShowError) {
        Icon(
            imageVector = Icons.Default.ErrorOutline,
@@ -315,11 +316,14 @@ private fun SelectedKeyCombinationContainer(
        )
    }
}
    }
}

@Composable
private fun RowScope.PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) {
private fun PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) {
    Row(
        modifier =
            Modifier.semantics(mergeDescendants = true) { liveRegion = LiveRegionMode.Polite },
        verticalAlignment = Alignment.CenterVertically,
    ) {
        pressedKeys.forEachIndexed { keyIndex, key ->
            if (keyIndex > 0) {
                ShortcutKeySeparator()
@@ -331,6 +335,7 @@ private fun RowScope.PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) {
            }
        }
    }
}

@Composable
private fun ShortcutKeySeparator() {
@@ -344,7 +349,7 @@ private fun ShortcutKeySeparator() {
}

@Composable
private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) {
private fun ShortcutIconKey(key: ShortcutKey.Icon) {
    Icon(
        painter =
            when (key) {
@@ -352,7 +357,8 @@ private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) {
                is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable)
            },
        contentDescription = null,
        modifier = Modifier.align(Alignment.CenterVertically).height(24.dp),
        modifier =
        Modifier.height(24.dp),
        tint = MaterialTheme.colorScheme.onSurfaceVariant,
    )
}
@@ -403,7 +409,7 @@ private fun Description(text: String) {
                .width(316.dp)
                .wrapContentSize(Alignment.Center),
        color = MaterialTheme.colorScheme.onSurfaceVariant,
        textAlign = TextAlign.Center
        textAlign = TextAlign.Center,
    )
}

@@ -467,3 +473,31 @@ private fun PlusIconContainer() {
        modifier = Modifier.padding(vertical = 12.dp).size(24.dp).wrapContentSize(Alignment.Center),
    )
}

@Composable
private fun OutlinedInputField(
    content: @Composable (() -> Unit)?,
    placeholder: @Composable () -> Unit,
    trailingIcon: @Composable () -> Unit,
    isError: Boolean,
    modifier: Modifier = Modifier,
) {
    OutlinedTextField(
        value = "",
        onValueChange = {},
        placeholder = if (content == null) placeholder else null,
        prefix = content,
        singleLine = true,
        modifier = modifier,
        trailingIcon = trailingIcon,
        colors =
            OutlinedTextFieldDefaults.colors()
                .copy(
                    focusedIndicatorColor = MaterialTheme.colorScheme.primary,
                    unfocusedIndicatorColor = MaterialTheme.colorScheme.outline,
                    errorIndicatorColor = MaterialTheme.colorScheme.error,
                ),
        shape = RoundedCornerShape(50.dp),
        isError = isError,
    )
}