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

Commit e2b71c9e authored by Josh's avatar Josh
Browse files

Added buttons for entering and exiting customize mode

+ added buttons for adding shortcut.

Fix: 373632868
Fix: 373619545
Test: Manual - ensure UI corresponds with UI mocks.
Flag: com.android.systemui.keyboard_shortcut_helper_shortcut_customizer
Change-Id: Ia8a695049cbc2dfc2a638f31d06b2708aef20313
parent 3742654e
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -1667,3 +1667,12 @@ flag {
  description: "Expands the shade on long press of any status bar"
  bug: "371224114"
}


flag {
    name: "keyboard_shortcut_helper_shortcut_customizer"
    namespace: "systemui"
    description: "An implementation of shortcut customizations through shortcut helper."
    bug: "365064144"
}
+12 −0
Original line number Diff line number Diff line
@@ -3732,6 +3732,10 @@
    <!-- Title at the top of the keyboard shortcut helper UI. The helper is a component
         that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_title">Keyboard shortcuts</string>
    <!-- Title at the top of the keyboard shortcut helper UI when in customize mode. The helper
         is a component that shows the user which keyboard shortcuts they can use.
         [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string>
    <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
         hasn't typed in anything in the search box yet. The helper is a  component that shows the
         user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
@@ -3743,6 +3747,14 @@
         use. The helper shows shortcuts in categories, which can be collapsed or expanded.
         [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string>
    <!-- Description text of the button that allows user to customize shortcuts in keyboard
         shortcut helper The helper is a  component that shows the  user which keyboard shortcuts
         they can use. [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_customize_button_text">Customize</string>
    <!-- Description text of the button that allows user to exit shortcut customization mode in
         keyboard shortcut helper The helper is a  component that shows the  user which keyboard
         shortcuts they can use. [CHAR LIMIT=NONE] -->
    <string name="shortcut_helper_done_button_text">Done</string>
    <!-- Content description of the icon that allows to expand a keyboard shortcut helper category
         panel. The helper is a  component that shows the  user which keyboard shortcuts they can
         use. The helper shows shortcuts in categories, which can be collapsed or expanded.
+109 −12
Original line number Diff line number Diff line
@@ -52,8 +52,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.OpenInNew
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.ExpandMore
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Tune
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
@@ -69,6 +71,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -169,6 +172,7 @@ private fun ActiveShortcutHelper(
            selectedCategoryType,
            onCategorySelected = { selectedCategoryType = it },
            onKeyboardSettingsClicked,
            shortcutsUiState.isShortcutCustomizerFlagEnabled,
        )
    }
}
@@ -357,10 +361,29 @@ private fun ShortcutHelperTwoPane(
    selectedCategoryType: ShortcutCategoryType?,
    onCategorySelected: (ShortcutCategoryType?) -> Unit,
    onKeyboardSettingsClicked: () -> Unit,
    isShortcutCustomizerFlagEnabled: Boolean,
) {
    val selectedCategory = categories.fastFirstOrNull { it.type == selectedCategoryType }
    var isCustomizeModeEntered by remember { mutableStateOf(false) }
    val isCustomizing by
        remember(isCustomizeModeEntered, isShortcutCustomizerFlagEnabled) {
            derivedStateOf { isCustomizeModeEntered && isCustomizeModeEntered }
        }

    Column(modifier = modifier.fillMaxSize().padding(horizontal = 24.dp)) {
        TitleBar()
        Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
            Box(modifier = Modifier.padding(start = 202.dp).width(412.dp)) {
                TitleBar(isCustomizing)
            }
            Spacer(modifier = Modifier.weight(1f))
            if (isShortcutCustomizerFlagEnabled) {
                if (isCustomizeModeEntered) {
                    DoneButton(onClick = { isCustomizeModeEntered = false })
                } else {
                    CustomizeButton(onClick = { isCustomizeModeEntered = true })
                }
            }
        }
        Spacer(modifier = Modifier.height(12.dp))
        Row(Modifier.fillMaxWidth()) {
            StartSidePanel(
@@ -372,13 +395,46 @@ private fun ShortcutHelperTwoPane(
                onCategoryClicked = { onCategorySelected(it.type) },
            )
            Spacer(modifier = Modifier.width(24.dp))
            EndSidePanel(searchQuery, Modifier.fillMaxSize().padding(top = 8.dp), selectedCategory)
            EndSidePanel(
                searchQuery,
                Modifier.fillMaxSize().padding(top = 8.dp),
                selectedCategory,
                isCustomizing = isCustomizing,
            )
        }
    }
}

@Composable
private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: ShortcutCategoryUi?) {
private fun CustomizeButton(onClick: () -> Unit) {
    ShortcutHelperButton(
        onClick = onClick,
        color = MaterialTheme.colorScheme.secondaryContainer,
        width = 133.dp,
        iconSource = IconSource(imageVector = Icons.Default.Tune),
        text = stringResource(id = R.string.shortcut_helper_customize_button_text),
        contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
    )
}

@Composable
private fun DoneButton(onClick: () -> Unit) {
    ShortcutHelperButton(
        onClick = onClick,
        color = MaterialTheme.colorScheme.primary,
        width = 69.dp,
        text = stringResource(R.string.shortcut_helper_done_button_text),
        contentColor = MaterialTheme.colorScheme.onPrimary,
    )
}

@Composable
private fun EndSidePanel(
    searchQuery: String,
    modifier: Modifier,
    category: ShortcutCategoryUi?,
    isCustomizing: Boolean,
) {
    val listState = rememberLazyListState()
    LaunchedEffect(key1 = category) { if (category != null) listState.animateScrollToItem(0) }
    if (category == null) {
@@ -387,7 +443,11 @@ private fun EndSidePanel(searchQuery: String, modifier: Modifier, category: Shor
    }
    LazyColumn(modifier = modifier, state = listState) {
        items(category.subCategories) { subcategory ->
            SubCategoryContainerDualPane(searchQuery = searchQuery, subCategory = subcategory)
            SubCategoryContainerDualPane(
                searchQuery = searchQuery,
                subCategory = subcategory,
                isCustomizing = isCustomizing,
            )
            Spacer(modifier = Modifier.height(8.dp))
        }
    }
@@ -412,7 +472,11 @@ private fun NoSearchResultsText(horizontalPadding: Dp, fillHeight: Boolean) {
}

@Composable
private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: ShortcutSubCategory) {
private fun SubCategoryContainerDualPane(
    searchQuery: String,
    subCategory: ShortcutSubCategory,
    isCustomizing: Boolean,
) {
    Surface(
        modifier = Modifier.fillMaxWidth(),
        shape = RoundedCornerShape(28.dp),
@@ -432,6 +496,7 @@ private fun SubCategoryContainerDualPane(searchQuery: String, subCategory: Short
                    modifier = Modifier.padding(vertical = 8.dp),
                    searchQuery = searchQuery,
                    shortcut = shortcut,
                    isCustomizing = isCustomizing,
                )
            }
        }
@@ -448,7 +513,12 @@ private fun SubCategoryTitle(title: String) {
}

@Composable
private fun Shortcut(modifier: Modifier, searchQuery: String, shortcut: ShortcutModel) {
private fun Shortcut(
    modifier: Modifier,
    searchQuery: String,
    shortcut: ShortcutModel,
    isCustomizing: Boolean = false,
) {
    val interactionSource = remember { MutableInteractionSource() }
    val isFocused by interactionSource.collectIsFocusedAsState()
    val focusColor = MaterialTheme.colorScheme.secondary
@@ -471,7 +541,7 @@ private fun Shortcut(modifier: Modifier, searchQuery: String, shortcut: Shortcut
            ShortcutDescriptionText(searchQuery = searchQuery, shortcut = shortcut)
        }
        Spacer(modifier = Modifier.width(24.dp))
        ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut)
        ShortcutKeyCombinations(modifier = Modifier.weight(1f), shortcut = shortcut, isCustomizing)
    }
}

@@ -495,7 +565,11 @@ fun ShortcutIcon(

@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun ShortcutKeyCombinations(modifier: Modifier = Modifier, shortcut: ShortcutModel) {
private fun ShortcutKeyCombinations(
    modifier: Modifier = Modifier,
    shortcut: ShortcutModel,
    isCustomizing: Boolean = false,
) {
    FlowRow(
        modifier = modifier,
        verticalArrangement = Arrangement.spacedBy(8.dp),
@@ -507,6 +581,25 @@ private fun ShortcutKeyCombinations(modifier: Modifier = Modifier, shortcut: Sho
            }
            ShortcutCommand(command)
        }
        if (isCustomizing) {
            Spacer(modifier = Modifier.width(16.dp))
            ShortcutHelperButton(
                modifier =
                    Modifier.border(
                        width = 1.dp,
                        color = MaterialTheme.colorScheme.outline,
                        shape = CircleShape,
                    ),
                onClick = {},
                color = Color.Transparent,
                width = 32.dp,
                height = 32.dp,
                iconSource = IconSource(imageVector = Icons.Default.Add),
                contentColor = MaterialTheme.colorScheme.primary,
                contentPaddingVertical = 0.dp,
                contentPaddingHorizontal = 0.dp,
            )
        }
    }
}

@@ -700,12 +793,18 @@ private fun CategoryItemTwoPane(

@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun TitleBar() {
private fun TitleBar(isCustomizing: Boolean = false) {
    val text =
        if (isCustomizing) {
            stringResource(R.string.shortcut_helper_customize_mode_title)
        } else {
            stringResource(R.string.shortcut_helper_title)
        }
    CenterAlignedTopAppBar(
        colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
        title = {
            Text(
                text = stringResource(R.string.shortcut_helper_title),
                text = text,
                color = MaterialTheme.colorScheme.onSurface,
                style = MaterialTheme.typography.headlineSmall,
            )
@@ -753,14 +852,12 @@ private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) {

@Composable
private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) {
    val interactionSource = remember { MutableInteractionSource() }
    ClickableShortcutSurface(
        onClick = onClick,
        shape = RoundedCornerShape(24.dp),
        color = Color.Transparent,
        modifier =
            Modifier.semantics { role = Role.Button }.fillMaxWidth().padding(horizontal = 12.dp),
        interactionSource = interactionSource,
        interactionsConfig =
            InteractionsConfig(
                hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
+108 −1
Original line number Diff line number Diff line
@@ -27,13 +27,24 @@ import androidx.compose.foundation.interaction.InteractionSource
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.PressInteraction
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalAbsoluteTonalElevation
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTonalElevationEnabled
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.material3.surfaceColorAtElevation
@@ -43,6 +54,7 @@ import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
@@ -57,11 +69,16 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.node.DelegatableNode
import androidx.compose.ui.node.DrawModifierNode
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.zIndex
import com.android.compose.modifiers.thenIf
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.modifiers.thenIf
import com.android.systemui.keyboard.shortcut.ui.model.IconSource

/**
 * A selectable surface with no default focus/hover indications.
@@ -175,6 +192,96 @@ fun ClickableShortcutSurface(
    }
}

/**
 * A composable that provides a button with a customizable icon and text, designed to be re-used
 * across shortcut helper/customizer. Supports defaults hover/focus/pressed states used across
 * shortcut helper.
 *
 * This button utilizes [ClickableShortcutSurface] to provide a clickable surface with hover and
 * pressed states, and a focus outline.
 *
 * The content of the button can be an icon (from [IconSource]) and/or text.
 *
 * @param modifier The modifier to be applied to the button.
 * @param onClick The callback function that will be invoked when the button is clicked.
 * @param shape The shape of the button. Defaults to a rounded corner shape used across shortcut
 *   helper.
 * @param color The background color of the button.
 * @param width The width of the button.
 * @param height The height of the button. Defaults to 40.dp as often used in shortcut helper
 * @param iconSource The source of the icon to be displayed. Defaults to an empty [IconSource].
 * @param text The text to be displayed. Defaults to null.
 * @param contentColor The color of the icon and text.
 * @param contentPaddingHorizontal The horizontal padding of the content. Defaults to 16.dp.
 * @param contentPaddingVertical The vertical padding of the content. Defaults to 10.dp.
 */
@Composable
fun ShortcutHelperButton(
    modifier: Modifier = Modifier,
    onClick: () -> Unit,
    shape: Shape = RoundedCornerShape(360.dp),
    color: Color,
    width: Dp,
    height: Dp = 40.dp,
    iconSource: IconSource = IconSource(),
    text: String? = null,
    contentColor: Color,
    contentPaddingHorizontal: Dp = 16.dp,
    contentPaddingVertical: Dp = 10.dp,
) {
    ClickableShortcutSurface(
        onClick = onClick,
        shape = shape,
        color = color,
        modifier = modifier.semantics { role = Role.Button }.width(width).height(height),
        interactionsConfig =
            InteractionsConfig(
                hoverOverlayColor = MaterialTheme.colorScheme.onSurface,
                hoverOverlayAlpha = 0.11f,
                pressedOverlayColor = MaterialTheme.colorScheme.onSurface,
                pressedOverlayAlpha = 0.15f,
                focusOutlineColor = MaterialTheme.colorScheme.secondary,
                focusOutlineStrokeWidth = 3.dp,
                focusOutlinePadding = 2.dp,
                surfaceCornerRadius = 28.dp,
                focusOutlineCornerRadius = 33.dp,
            ),
    ) {
        Row(
            modifier =
                Modifier.padding(
                    horizontal = contentPaddingHorizontal,
                    vertical = contentPaddingVertical,
                ),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.Center,
        ) {
            if (iconSource.imageVector != null) {
                Icon(
                    tint = contentColor,
                    imageVector = iconSource.imageVector,
                    contentDescription = null,
                    modifier = Modifier.size(20.dp).wrapContentSize(Alignment.Center),
                )
            }

            if (iconSource.imageVector != null && text != null) {
                Spacer(modifier = Modifier.weight(1f))
            }

            if (text != null) {
                Text(
                    text,
                    color = contentColor,
                    fontSize = 14.sp,
                    style = MaterialTheme.typography.labelLarge,
                    modifier = Modifier.wrapContentSize(Alignment.Center),
                )
            }
        }
    }
}

@Composable
private fun surfaceColorAtElevation(color: Color, elevation: Dp): Color {
    return MaterialTheme.colorScheme.applyTonalElevation(color, elevation)
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ sealed interface ShortcutsUiState {
        val searchQuery: String,
        val shortcutCategories: List<ShortcutCategoryUi>,
        val defaultSelectedCategory: ShortcutCategoryType?,
        val isShortcutCustomizerFlagEnabled: Boolean = false,
    ) : ShortcutsUiState

    data object Inactive : ShortcutsUiState
Loading