Loading packages/SystemUI/res/values/strings.xml +8 −0 Original line number Diff line number Diff line Loading @@ -3529,6 +3529,14 @@ use. The helper shows shortcuts in categories, which can be collapsed or expanded. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_content_description_expand_icon">Expand icon</string> <!-- Word that separates different possible key combinations of a shortcut. For example the "Go to home screen" shortcut could be triggered using "home button" OR "ctrl + h". This is that "or" separator. The keyboard shortcut helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_key_combinations_or_separator">or</string> <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] --> <string name="keyboard_backlight_dialog_title">Keyboard backlight</string> Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +203 −48 Original line number Diff line number Diff line Loading @@ -16,13 +16,16 @@ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize Loading @@ -32,21 +35,19 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape 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.Accessibility import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Tv import androidx.compose.material.icons.filled.VerticalSplit import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationDrawerItemColors Loading @@ -56,6 +57,7 @@ import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue Loading @@ -69,10 +71,13 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.stringResource 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.util.fastForEach Loading @@ -81,8 +86,13 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R @Composable fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () -> Unit) { if (shouldUseSinglePane()) { fun ShortcutHelper( onKeyboardSettingsClicked: () -> Unit, modifier: Modifier = Modifier, categories: List<ShortcutHelperCategory> = ShortcutHelperTemporaryData.categories, useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() }, ) { if (useSinglePane()) { ShortcutHelperSinglePane(modifier, categories, onKeyboardSettingsClicked) } else { ShortcutHelperTwoPane(modifier, categories, onKeyboardSettingsClicked) Loading @@ -91,7 +101,8 @@ fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () @Composable private fun shouldUseSinglePane() = LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact || LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact @Composable private fun ShortcutHelperSinglePane( Loading Loading @@ -209,11 +220,31 @@ private fun RotatingExpandCollapseIcon(isExpanded: Boolean) { @Composable private fun ShortcutCategoryDetailsSinglePane(category: ShortcutHelperCategory) { Box(modifier = Modifier.fillMaxWidth().heightIn(min = 300.dp)) { Text( modifier = Modifier.align(Alignment.Center), text = stringResource(category.labelResId), ) Column(Modifier.padding(horizontal = 16.dp)) { category.subCategories.fastForEach { subCategory -> ShortcutSubCategorySinglePane(subCategory) } } } @Composable private fun ShortcutSubCategorySinglePane(subCategory: SubCategory) { // This @Composable is expected to be in a Column. SubCategoryTitle(subCategory.label) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider() } ShortcutSinglePane(shortcut) } } @Composable private fun ShortcutSinglePane(shortcut: Shortcut) { Column(Modifier.padding(vertical = 24.dp)) { ShortcutDescriptionText(shortcut = shortcut) Spacer(modifier = Modifier.height(12.dp)) ShortcutKeyCombinations(shortcut = shortcut) } } Loading @@ -223,6 +254,7 @@ private fun ShortcutHelperTwoPane( categories: List<ShortcutHelperCategory>, onKeyboardSettingsClicked: () -> Unit, ) { var selectedCategory by remember { mutableStateOf(categories.first()) } Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) { TitleBar() Spacer(modifier = Modifier.height(12.dp)) Loading @@ -230,12 +262,157 @@ private fun ShortcutHelperTwoPane( StartSidePanel( modifier = Modifier.fillMaxWidth(fraction = 0.32f), categories = categories, selectedCategory = selectedCategory, onCategoryClicked = { selectedCategory = it }, onKeyboardSettingsClicked = onKeyboardSettingsClicked, ) Spacer(modifier = Modifier.width(24.dp)) EndSidePanel(Modifier.fillMaxSize()) EndSidePanel(Modifier.fillMaxSize(), selectedCategory) } } } @Composable private fun EndSidePanel(modifier: Modifier, category: ShortcutHelperCategory) { LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) { items(items = category.subCategories, key = { item -> item.label }) { SubCategoryContainerDualPane(it) Spacer(modifier = Modifier.height(8.dp)) } } } @Composable private fun SubCategoryContainerDualPane(subCategory: SubCategory) { Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = MaterialTheme.colorScheme.surfaceBright ) { Column(Modifier.padding(horizontal = 32.dp, vertical = 24.dp)) { SubCategoryTitle(subCategory.label) Spacer(Modifier.height(24.dp)) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider() } ShortcutViewDualPane(shortcut) } } } } @Composable private fun SubCategoryTitle(title: String) { Text( title, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, ) } @Composable private fun ShortcutViewDualPane(shortcut: Shortcut) { Row(Modifier.padding(vertical = 16.dp)) { ShortcutDescriptionText( modifier = Modifier.weight(0.25f).align(Alignment.CenterVertically), shortcut = shortcut, ) ShortcutKeyCombinations( modifier = Modifier.weight(0.75f), shortcut = shortcut, ) } } @OptIn(ExperimentalLayoutApi::class) @Composable private fun ShortcutKeyCombinations( modifier: Modifier = Modifier, shortcut: Shortcut, ) { FlowRow(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { shortcut.commands.forEachIndexed { index, command -> if (index > 0) { ShortcutOrSeparator(spacing = 16.dp) } ShortcutCommand(command) } } } @Composable private fun ShortcutCommand(command: ShortcutCommand) { // This @Composable is expected to be in a Row or FlowRow. command.keys.forEachIndexed { keyIndex, key -> if (keyIndex > 0) { Spacer(Modifier.width(4.dp)) } ShortcutKeyContainer { if (key is ShortcutKey.Text) { ShortcutTextKey(key) } else if (key is ShortcutKey.Icon) { ShortcutIconKey(key) } } } } @Composable private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> Unit) { Box( modifier = Modifier.height(36.dp) .background( color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp) ), ) { shortcutKeyContent() } } @Composable private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, modifier = Modifier.align(Alignment.Center).padding(horizontal = 12.dp), style = MaterialTheme.typography.titleSmall, ) } @Composable private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) { Icon( imageVector = key.value, contentDescription = null, modifier = Modifier.align(Alignment.Center).padding(6.dp) ) } @OptIn(ExperimentalLayoutApi::class) @Composable private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) { Spacer(Modifier.width(spacing)) Text( text = stringResource(R.string.shortcut_helper_key_combinations_or_separator), modifier = Modifier.align(Alignment.CenterVertically), style = MaterialTheme.typography.titleSmall, ) Spacer(Modifier.width(spacing)) } @Composable private fun ShortcutDescriptionText( shortcut: Shortcut, modifier: Modifier = Modifier, ) { Text( modifier = modifier, text = shortcut.label, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, ) } @Composable Loading @@ -243,26 +420,31 @@ private fun StartSidePanel( modifier: Modifier, categories: List<ShortcutHelperCategory>, onKeyboardSettingsClicked: () -> Unit, selectedCategory: ShortcutHelperCategory, onCategoryClicked: (ShortcutHelperCategory) -> Unit, ) { Column(modifier) { ShortcutsSearchBar() Spacer(modifier = Modifier.heightIn(16.dp)) CategoriesPanelTwoPane(categories) CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked) Spacer(modifier = Modifier.weight(1f)) KeyboardSettings(onKeyboardSettingsClicked) } } @Composable private fun CategoriesPanelTwoPane(categories: List<ShortcutHelperCategory>) { var selected by remember { mutableStateOf(categories.first()) } private fun CategoriesPanelTwoPane( categories: List<ShortcutHelperCategory>, selectedCategory: ShortcutHelperCategory, onCategoryClicked: (ShortcutHelperCategory) -> Unit ) { Column { categories.fastForEach { CategoryItemTwoPane( label = stringResource(it.labelResId), icon = it.icon, selected = selected == it, onClick = { selected = it } selected = selectedCategory == it, onClick = { onCategoryClicked(it) } ) } } Loading Loading @@ -304,15 +486,6 @@ private fun CategoryItemTwoPane( } } @Composable fun EndSidePanel(modifier: Modifier) { Surface( modifier = modifier, shape = RoundedCornerShape(28.dp), color = MaterialTheme.colorScheme.surfaceBright ) {} } @Composable @OptIn(ExperimentalMaterial3Api::class) private fun TitleBar() { Loading @@ -333,6 +506,7 @@ private fun TitleBar() { private fun ShortcutsSearchBar() { var query by remember { mutableStateOf("") } SearchBar( modifier = Modifier.fillMaxWidth(), colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), query = query, active = false, Loading Loading @@ -372,25 +546,6 @@ private fun KeyboardSettings(onClick: () -> Unit) { } } /** Temporary data class just to populate the UI. */ private data class ShortcutHelperCategory( @StringRes val labelResId: Int, val icon: ImageVector, ) // Temporarily populating the categories directly in the UI. private val categories = listOf( ShortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv), ShortcutHelperCategory( R.string.shortcut_helper_category_multitasking, Icons.Default.VerticalSplit ), ShortcutHelperCategory(R.string.shortcut_helper_category_input, Icons.Default.Keyboard), ShortcutHelperCategory(R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps), ShortcutHelperCategory(R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility), ) object ShortcutHelper { object Shapes { Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperTemporaryData.kt 0 → 100644 +251 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Accessibility import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.filled.ArrowBackIosNew import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.KeyboardCommandKey import androidx.compose.material.icons.filled.RadioButtonUnchecked import androidx.compose.material.icons.filled.Tv import androidx.compose.material.icons.filled.VerticalSplit import androidx.compose.ui.graphics.vector.ImageVector import com.android.systemui.res.R /** Temporary data classes and data below just to populate the UI. */ data class ShortcutHelperCategory( @StringRes val labelResId: Int, val icon: ImageVector, val subCategories: List<SubCategory>, ) data class SubCategory( val label: String, val shortcuts: List<Shortcut>, ) data class Shortcut(val label: String, val commands: List<ShortcutCommand>) data class ShortcutCommand(val keys: List<ShortcutKey>) sealed interface ShortcutKey { data class Text(val value: String) : ShortcutKey data class Icon(val value: ImageVector) : ShortcutKey } // DSL Builder Functions private fun shortcutHelperCategory( labelResId: Int, icon: ImageVector, block: ShortcutHelperCategoryBuilder.() -> Unit ): ShortcutHelperCategory = ShortcutHelperCategoryBuilder(labelResId, icon).apply(block).build() private fun ShortcutHelperCategoryBuilder.subCategory( label: String, block: SubCategoryBuilder.() -> Unit ) { subCategories.add(SubCategoryBuilder(label).apply(block).build()) } private fun SubCategoryBuilder.shortcut(label: String, block: ShortcutBuilder.() -> Unit) { shortcuts.add(ShortcutBuilder(label).apply(block).build()) } private fun ShortcutBuilder.command(block: ShortcutCommandBuilder.() -> Unit) { commands.add(ShortcutCommandBuilder().apply(block).build()) } private fun ShortcutCommandBuilder.key(value: String) { keys.add(ShortcutKey.Text(value)) } private fun ShortcutCommandBuilder.key(value: ImageVector) { keys.add(ShortcutKey.Icon(value)) } private class ShortcutHelperCategoryBuilder( private val labelResId: Int, private val icon: ImageVector ) { val subCategories = mutableListOf<SubCategory>() fun build() = ShortcutHelperCategory(labelResId, icon, subCategories) } private class SubCategoryBuilder(private val label: String) { val shortcuts = mutableListOf<Shortcut>() fun build() = SubCategory(label, shortcuts) } private class ShortcutBuilder(private val label: String) { val commands = mutableListOf<ShortcutCommand>() fun build() = Shortcut(label, commands) } private class ShortcutCommandBuilder { val keys = mutableListOf<ShortcutKey>() fun build() = ShortcutCommand(keys) } object ShortcutHelperTemporaryData { // Some shortcuts and their strings below are made up just to populate the UI for now. // For this reason they are not in translatable resources yet. val categories = listOf( shortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv) { subCategory("System controls") { shortcut("Go to home screen") { command { key(Icons.Default.RadioButtonUnchecked) } command { key(Icons.Default.KeyboardCommandKey) key("H") } command { key(Icons.Default.KeyboardCommandKey) key("Return") } } shortcut("View recent apps") { command { key(Icons.Default.KeyboardCommandKey) key("Tab") } } shortcut("All apps search") { command { key(Icons.Default.KeyboardCommandKey) } } } subCategory("System apps") { shortcut("Go back") { command { key(Icons.Default.ArrowBackIosNew) } command { key(Icons.Default.KeyboardCommandKey) key("Left arrow") } command { key(Icons.Default.KeyboardCommandKey) key("ESC") } command { key(Icons.Default.KeyboardCommandKey) key("Backspace") } } shortcut("View notifications") { command { key(Icons.Default.KeyboardCommandKey) key("N") } } shortcut("Take a screenshot") { command { key(Icons.Default.KeyboardCommandKey) } command { key("CTRL") } command { key("S") } } shortcut("Open Settings") { command { key(Icons.Default.KeyboardCommandKey) key("I") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_multitasking, Icons.Default.VerticalSplit ) { subCategory("Multitasking & windows") { shortcut("Take a screenshot") { command { key(Icons.Default.KeyboardCommandKey) } command { key("CTRL") } command { key("S") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_input, Icons.Default.Keyboard ) { subCategory("Input") { shortcut("Open Settings") { command { key(Icons.Default.KeyboardCommandKey) key("I") } } shortcut("View notifications") { command { key(Icons.Default.KeyboardCommandKey) key("N") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps ) { subCategory("App shortcuts") { shortcut("Open Settings") { command { key(Icons.Default.KeyboardCommandKey) key("I") } } shortcut("Go back") { command { key(Icons.Default.ArrowBackIosNew) } command { key(Icons.Default.KeyboardCommandKey) key("Left arrow") } command { key(Icons.Default.KeyboardCommandKey) key("ESC") } command { key(Icons.Default.KeyboardCommandKey) key("Backspace") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility ) { subCategory("Accessibility shortcuts") { shortcut("View recent apps") { command { key(Icons.Default.KeyboardCommandKey) key("Tab") } } shortcut("All apps search") { command { key(Icons.Default.KeyboardCommandKey) } } } } ) } Loading
packages/SystemUI/res/values/strings.xml +8 −0 Original line number Diff line number Diff line Loading @@ -3529,6 +3529,14 @@ use. The helper shows shortcuts in categories, which can be collapsed or expanded. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_content_description_expand_icon">Expand icon</string> <!-- Word that separates different possible key combinations of a shortcut. For example the "Go to home screen" shortcut could be triggered using "home button" OR "ctrl + h". This is that "or" separator. The keyboard shortcut helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_key_combinations_or_separator">or</string> <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] --> <string name="keyboard_backlight_dialog_title">Keyboard backlight</string> Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +203 −48 Original line number Diff line number Diff line Loading @@ -16,13 +16,16 @@ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.annotation.StringRes import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRowScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize Loading @@ -32,21 +35,19 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape 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.Accessibility import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.filled.ExpandMore import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Tv import androidx.compose.material.icons.filled.VerticalSplit import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.NavigationDrawerItemColors Loading @@ -56,6 +57,7 @@ import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue Loading @@ -69,10 +71,13 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.rememberNestedScrollInteropConnection import androidx.compose.ui.res.stringResource 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.util.fastForEach Loading @@ -81,8 +86,13 @@ import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.res.R @Composable fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () -> Unit) { if (shouldUseSinglePane()) { fun ShortcutHelper( onKeyboardSettingsClicked: () -> Unit, modifier: Modifier = Modifier, categories: List<ShortcutHelperCategory> = ShortcutHelperTemporaryData.categories, useSinglePane: @Composable () -> Boolean = { shouldUseSinglePane() }, ) { if (useSinglePane()) { ShortcutHelperSinglePane(modifier, categories, onKeyboardSettingsClicked) } else { ShortcutHelperTwoPane(modifier, categories, onKeyboardSettingsClicked) Loading @@ -91,7 +101,8 @@ fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () @Composable private fun shouldUseSinglePane() = LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact || LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact @Composable private fun ShortcutHelperSinglePane( Loading Loading @@ -209,11 +220,31 @@ private fun RotatingExpandCollapseIcon(isExpanded: Boolean) { @Composable private fun ShortcutCategoryDetailsSinglePane(category: ShortcutHelperCategory) { Box(modifier = Modifier.fillMaxWidth().heightIn(min = 300.dp)) { Text( modifier = Modifier.align(Alignment.Center), text = stringResource(category.labelResId), ) Column(Modifier.padding(horizontal = 16.dp)) { category.subCategories.fastForEach { subCategory -> ShortcutSubCategorySinglePane(subCategory) } } } @Composable private fun ShortcutSubCategorySinglePane(subCategory: SubCategory) { // This @Composable is expected to be in a Column. SubCategoryTitle(subCategory.label) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider() } ShortcutSinglePane(shortcut) } } @Composable private fun ShortcutSinglePane(shortcut: Shortcut) { Column(Modifier.padding(vertical = 24.dp)) { ShortcutDescriptionText(shortcut = shortcut) Spacer(modifier = Modifier.height(12.dp)) ShortcutKeyCombinations(shortcut = shortcut) } } Loading @@ -223,6 +254,7 @@ private fun ShortcutHelperTwoPane( categories: List<ShortcutHelperCategory>, onKeyboardSettingsClicked: () -> Unit, ) { var selectedCategory by remember { mutableStateOf(categories.first()) } Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) { TitleBar() Spacer(modifier = Modifier.height(12.dp)) Loading @@ -230,12 +262,157 @@ private fun ShortcutHelperTwoPane( StartSidePanel( modifier = Modifier.fillMaxWidth(fraction = 0.32f), categories = categories, selectedCategory = selectedCategory, onCategoryClicked = { selectedCategory = it }, onKeyboardSettingsClicked = onKeyboardSettingsClicked, ) Spacer(modifier = Modifier.width(24.dp)) EndSidePanel(Modifier.fillMaxSize()) EndSidePanel(Modifier.fillMaxSize(), selectedCategory) } } } @Composable private fun EndSidePanel(modifier: Modifier, category: ShortcutHelperCategory) { LazyColumn(modifier.nestedScroll(rememberNestedScrollInteropConnection())) { items(items = category.subCategories, key = { item -> item.label }) { SubCategoryContainerDualPane(it) Spacer(modifier = Modifier.height(8.dp)) } } } @Composable private fun SubCategoryContainerDualPane(subCategory: SubCategory) { Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = MaterialTheme.colorScheme.surfaceBright ) { Column(Modifier.padding(horizontal = 32.dp, vertical = 24.dp)) { SubCategoryTitle(subCategory.label) Spacer(Modifier.height(24.dp)) subCategory.shortcuts.fastForEachIndexed { index, shortcut -> if (index > 0) { HorizontalDivider() } ShortcutViewDualPane(shortcut) } } } } @Composable private fun SubCategoryTitle(title: String) { Text( title, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary, ) } @Composable private fun ShortcutViewDualPane(shortcut: Shortcut) { Row(Modifier.padding(vertical = 16.dp)) { ShortcutDescriptionText( modifier = Modifier.weight(0.25f).align(Alignment.CenterVertically), shortcut = shortcut, ) ShortcutKeyCombinations( modifier = Modifier.weight(0.75f), shortcut = shortcut, ) } } @OptIn(ExperimentalLayoutApi::class) @Composable private fun ShortcutKeyCombinations( modifier: Modifier = Modifier, shortcut: Shortcut, ) { FlowRow(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) { shortcut.commands.forEachIndexed { index, command -> if (index > 0) { ShortcutOrSeparator(spacing = 16.dp) } ShortcutCommand(command) } } } @Composable private fun ShortcutCommand(command: ShortcutCommand) { // This @Composable is expected to be in a Row or FlowRow. command.keys.forEachIndexed { keyIndex, key -> if (keyIndex > 0) { Spacer(Modifier.width(4.dp)) } ShortcutKeyContainer { if (key is ShortcutKey.Text) { ShortcutTextKey(key) } else if (key is ShortcutKey.Icon) { ShortcutIconKey(key) } } } } @Composable private fun ShortcutKeyContainer(shortcutKeyContent: @Composable BoxScope.() -> Unit) { Box( modifier = Modifier.height(36.dp) .background( color = MaterialTheme.colorScheme.surfaceContainer, shape = RoundedCornerShape(12.dp) ), ) { shortcutKeyContent() } } @Composable private fun BoxScope.ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, modifier = Modifier.align(Alignment.Center).padding(horizontal = 12.dp), style = MaterialTheme.typography.titleSmall, ) } @Composable private fun BoxScope.ShortcutIconKey(key: ShortcutKey.Icon) { Icon( imageVector = key.value, contentDescription = null, modifier = Modifier.align(Alignment.Center).padding(6.dp) ) } @OptIn(ExperimentalLayoutApi::class) @Composable private fun FlowRowScope.ShortcutOrSeparator(spacing: Dp) { Spacer(Modifier.width(spacing)) Text( text = stringResource(R.string.shortcut_helper_key_combinations_or_separator), modifier = Modifier.align(Alignment.CenterVertically), style = MaterialTheme.typography.titleSmall, ) Spacer(Modifier.width(spacing)) } @Composable private fun ShortcutDescriptionText( shortcut: Shortcut, modifier: Modifier = Modifier, ) { Text( modifier = modifier, text = shortcut.label, style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurface, ) } @Composable Loading @@ -243,26 +420,31 @@ private fun StartSidePanel( modifier: Modifier, categories: List<ShortcutHelperCategory>, onKeyboardSettingsClicked: () -> Unit, selectedCategory: ShortcutHelperCategory, onCategoryClicked: (ShortcutHelperCategory) -> Unit, ) { Column(modifier) { ShortcutsSearchBar() Spacer(modifier = Modifier.heightIn(16.dp)) CategoriesPanelTwoPane(categories) CategoriesPanelTwoPane(categories, selectedCategory, onCategoryClicked) Spacer(modifier = Modifier.weight(1f)) KeyboardSettings(onKeyboardSettingsClicked) } } @Composable private fun CategoriesPanelTwoPane(categories: List<ShortcutHelperCategory>) { var selected by remember { mutableStateOf(categories.first()) } private fun CategoriesPanelTwoPane( categories: List<ShortcutHelperCategory>, selectedCategory: ShortcutHelperCategory, onCategoryClicked: (ShortcutHelperCategory) -> Unit ) { Column { categories.fastForEach { CategoryItemTwoPane( label = stringResource(it.labelResId), icon = it.icon, selected = selected == it, onClick = { selected = it } selected = selectedCategory == it, onClick = { onCategoryClicked(it) } ) } } Loading Loading @@ -304,15 +486,6 @@ private fun CategoryItemTwoPane( } } @Composable fun EndSidePanel(modifier: Modifier) { Surface( modifier = modifier, shape = RoundedCornerShape(28.dp), color = MaterialTheme.colorScheme.surfaceBright ) {} } @Composable @OptIn(ExperimentalMaterial3Api::class) private fun TitleBar() { Loading @@ -333,6 +506,7 @@ private fun TitleBar() { private fun ShortcutsSearchBar() { var query by remember { mutableStateOf("") } SearchBar( modifier = Modifier.fillMaxWidth(), colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), query = query, active = false, Loading Loading @@ -372,25 +546,6 @@ private fun KeyboardSettings(onClick: () -> Unit) { } } /** Temporary data class just to populate the UI. */ private data class ShortcutHelperCategory( @StringRes val labelResId: Int, val icon: ImageVector, ) // Temporarily populating the categories directly in the UI. private val categories = listOf( ShortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv), ShortcutHelperCategory( R.string.shortcut_helper_category_multitasking, Icons.Default.VerticalSplit ), ShortcutHelperCategory(R.string.shortcut_helper_category_input, Icons.Default.Keyboard), ShortcutHelperCategory(R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps), ShortcutHelperCategory(R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility), ) object ShortcutHelper { object Shapes { Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelperTemporaryData.kt 0 → 100644 +251 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.annotation.StringRes import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Accessibility import androidx.compose.material.icons.filled.Apps import androidx.compose.material.icons.filled.ArrowBackIosNew import androidx.compose.material.icons.filled.Keyboard import androidx.compose.material.icons.filled.KeyboardCommandKey import androidx.compose.material.icons.filled.RadioButtonUnchecked import androidx.compose.material.icons.filled.Tv import androidx.compose.material.icons.filled.VerticalSplit import androidx.compose.ui.graphics.vector.ImageVector import com.android.systemui.res.R /** Temporary data classes and data below just to populate the UI. */ data class ShortcutHelperCategory( @StringRes val labelResId: Int, val icon: ImageVector, val subCategories: List<SubCategory>, ) data class SubCategory( val label: String, val shortcuts: List<Shortcut>, ) data class Shortcut(val label: String, val commands: List<ShortcutCommand>) data class ShortcutCommand(val keys: List<ShortcutKey>) sealed interface ShortcutKey { data class Text(val value: String) : ShortcutKey data class Icon(val value: ImageVector) : ShortcutKey } // DSL Builder Functions private fun shortcutHelperCategory( labelResId: Int, icon: ImageVector, block: ShortcutHelperCategoryBuilder.() -> Unit ): ShortcutHelperCategory = ShortcutHelperCategoryBuilder(labelResId, icon).apply(block).build() private fun ShortcutHelperCategoryBuilder.subCategory( label: String, block: SubCategoryBuilder.() -> Unit ) { subCategories.add(SubCategoryBuilder(label).apply(block).build()) } private fun SubCategoryBuilder.shortcut(label: String, block: ShortcutBuilder.() -> Unit) { shortcuts.add(ShortcutBuilder(label).apply(block).build()) } private fun ShortcutBuilder.command(block: ShortcutCommandBuilder.() -> Unit) { commands.add(ShortcutCommandBuilder().apply(block).build()) } private fun ShortcutCommandBuilder.key(value: String) { keys.add(ShortcutKey.Text(value)) } private fun ShortcutCommandBuilder.key(value: ImageVector) { keys.add(ShortcutKey.Icon(value)) } private class ShortcutHelperCategoryBuilder( private val labelResId: Int, private val icon: ImageVector ) { val subCategories = mutableListOf<SubCategory>() fun build() = ShortcutHelperCategory(labelResId, icon, subCategories) } private class SubCategoryBuilder(private val label: String) { val shortcuts = mutableListOf<Shortcut>() fun build() = SubCategory(label, shortcuts) } private class ShortcutBuilder(private val label: String) { val commands = mutableListOf<ShortcutCommand>() fun build() = Shortcut(label, commands) } private class ShortcutCommandBuilder { val keys = mutableListOf<ShortcutKey>() fun build() = ShortcutCommand(keys) } object ShortcutHelperTemporaryData { // Some shortcuts and their strings below are made up just to populate the UI for now. // For this reason they are not in translatable resources yet. val categories = listOf( shortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv) { subCategory("System controls") { shortcut("Go to home screen") { command { key(Icons.Default.RadioButtonUnchecked) } command { key(Icons.Default.KeyboardCommandKey) key("H") } command { key(Icons.Default.KeyboardCommandKey) key("Return") } } shortcut("View recent apps") { command { key(Icons.Default.KeyboardCommandKey) key("Tab") } } shortcut("All apps search") { command { key(Icons.Default.KeyboardCommandKey) } } } subCategory("System apps") { shortcut("Go back") { command { key(Icons.Default.ArrowBackIosNew) } command { key(Icons.Default.KeyboardCommandKey) key("Left arrow") } command { key(Icons.Default.KeyboardCommandKey) key("ESC") } command { key(Icons.Default.KeyboardCommandKey) key("Backspace") } } shortcut("View notifications") { command { key(Icons.Default.KeyboardCommandKey) key("N") } } shortcut("Take a screenshot") { command { key(Icons.Default.KeyboardCommandKey) } command { key("CTRL") } command { key("S") } } shortcut("Open Settings") { command { key(Icons.Default.KeyboardCommandKey) key("I") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_multitasking, Icons.Default.VerticalSplit ) { subCategory("Multitasking & windows") { shortcut("Take a screenshot") { command { key(Icons.Default.KeyboardCommandKey) } command { key("CTRL") } command { key("S") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_input, Icons.Default.Keyboard ) { subCategory("Input") { shortcut("Open Settings") { command { key(Icons.Default.KeyboardCommandKey) key("I") } } shortcut("View notifications") { command { key(Icons.Default.KeyboardCommandKey) key("N") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps ) { subCategory("App shortcuts") { shortcut("Open Settings") { command { key(Icons.Default.KeyboardCommandKey) key("I") } } shortcut("Go back") { command { key(Icons.Default.ArrowBackIosNew) } command { key(Icons.Default.KeyboardCommandKey) key("Left arrow") } command { key(Icons.Default.KeyboardCommandKey) key("ESC") } command { key(Icons.Default.KeyboardCommandKey) key("Backspace") } } } }, shortcutHelperCategory( R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility ) { subCategory("Accessibility shortcuts") { shortcut("View recent apps") { command { key(Icons.Default.KeyboardCommandKey) key("Tab") } } shortcut("All apps search") { command { key(Icons.Default.KeyboardCommandKey) } } } } ) }