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
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d124c02..d50a92b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1667,3 +1667,12 @@
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"
+}
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fe720b9..412d1d9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -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.
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
index 5cade68..d537056 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -52,8 +52,10 @@
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.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 @@
selectedCategoryType,
onCategorySelected = { selectedCategoryType = it },
onKeyboardSettingsClicked,
+ shortcutsUiState.isShortcutCustomizerFlagEnabled,
)
}
}
@@ -357,10 +361,29 @@
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 @@
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 @@
}
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 @@
}
@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 @@
modifier = Modifier.padding(vertical = 8.dp),
searchQuery = searchQuery,
shortcut = shortcut,
+ isCustomizing = isCustomizing,
)
}
}
@@ -448,7 +513,12 @@
}
@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 @@
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 @@
@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 @@
}
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 @@
@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 @@
@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,
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
index f64d59a..435968e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt
@@ -27,13 +27,24 @@
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.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.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 @@
}
}
+/**
+ * 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)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
index 8f23261..02b0b43 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutsUiState.kt
@@ -24,6 +24,7 @@
val searchQuery: String,
val shortcutCategories: List<ShortcutCategoryUi>,
val defaultSelectedCategory: ShortcutCategoryType?,
+ val isShortcutCustomizerFlagEnabled: Boolean = false,
) : ShortcutsUiState
data object Inactive : ShortcutsUiState
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
index 20d09ed..912bfe9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModel.kt
@@ -26,6 +26,7 @@
import androidx.compose.material.icons.filled.Tv
import androidx.compose.material.icons.filled.VerticalSplit
import com.android.compose.ui.graphics.painter.DrawablePainter
+import com.android.systemui.Flags.keyboardShortcutHelperShortcutCustomizer
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutHelperStateInteractor
@@ -86,6 +87,7 @@
searchQuery = query,
shortcutCategories = shortcutCategoriesUi,
defaultSelectedCategory = getDefaultSelectedCategory(filteredCategories),
+ isShortcutCustomizerFlagEnabled = keyboardShortcutHelperShortcutCustomizer(),
)
}
}