diff options
| author | 2024-05-31 10:34:42 +0000 | |
|---|---|---|
| committer | 2024-05-31 10:34:42 +0000 | |
| commit | da37b50d26bc6c8f0d8f96fb2ea14d3e79e8c4e0 (patch) | |
| tree | e6b8fb523cc992e0457c8e1d8e26e1e090e1f8e7 | |
| parent | 15bedcc39855c9159b2b2fde97fba7042076a37a (diff) | |
| parent | 65355716db65191b17a228744fe5fb1c5e08a7f1 (diff) | |
Merge "Shortcut Helper - UI for Categories" into main
5 files changed, 484 insertions, 13 deletions
diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml index 292e49610e2a..06d1bf4c01cb 100644 --- a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml +++ b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml @@ -5,9 +5,9 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <LinearLayout + <FrameLayout android:id="@+id/shortcut_helper_sheet" - style="@style/Widget.Material3.BottomSheet" + style="@style/ShortcutHelperBottomSheet" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" @@ -19,13 +19,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - <TextView + <androidx.compose.ui.platform.ComposeView + android:id="@+id/shortcut_helper_compose_container" android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_weight="1" - android:gravity="center" - android:textAppearance="?textAppearanceDisplayLarge" - android:background="?colorTertiaryContainer" - android:text="Shortcut Helper Content" /> - </LinearLayout> -</androidx.coordinatorlayout.widget.CoordinatorLayout> + android:layout_height="match_parent" /> + </FrameLayout> +</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c038a8207d43..1226bbf21a0f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3489,6 +3489,45 @@ <!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] --> <string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string> + <!-- Title of the keyboard shortcut helper category "System". The helper is a component that + shows the user which keyboard shortcuts they can use. The "System" shortcuts are for + example "Take a screenshot" or "Go back". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_system">System</string> + <!-- Title of the keyboard shortcut helper category "Multitasking". The helper is a component + that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are + for example "Enter split screen". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_multitasking">Multitasking</string> + <!-- Title of the keyboard shortcut helper category "Input". The helper is a component + that shows the user which keyboard shortcuts they can use. The "Input" shortcuts are + the ones provided by the keyboard. Examples are "Access emoji" or "Switch to next language" + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_input">Input</string> + <!-- Title of the keyboard shortcut helper category "App shortcuts". The helper is a component + that shows the user which keyboard shortcuts they can use. The "App shortcuts" are + for example "Open browser" or "Open calculator". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_app_shortcuts">App shortcuts</string> + <!-- Title of the keyboard shortcut helper category "Accessibility". The helper is a component + that shows the user which keyboard shortcuts they can use. The "Accessibility" shortcuts + are for example "Turn on talkback". [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_category_a11y">Accessibility</string> + <!-- 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> + <!-- 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] --> + <string name="shortcut_helper_search_placeholder">Search shortcuts</string> + <!-- Content description of the icon that allows to collapse 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. + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</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. + [CHAR LIMIT=NONE] --> + <string name="shortcut_helper_content_description_expand_icon">Expand icon</string> + <!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] --> <string name="keyboard_backlight_dialog_title">Keyboard backlight</string> <!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 64717fcc8c5d..1e0adec4e84f 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1665,6 +1665,10 @@ <item name="android:colorBackground">@color/transparent</item> </style> + <style name="ShortcutHelperBottomSheet" parent="@style/Widget.Material3.BottomSheet"> + <item name="backgroundTint">?colorSurfaceContainer</item> + </style> + <style name="ShortcutHelperAnimation" parent="@android:style/Animation.Activity"> <item name="android:activityOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item> <item name="android:taskOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item> 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 new file mode 100644 index 000000000000..52ccc219353e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -0,0 +1,413 @@ +/* + * 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.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.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +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.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.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.NavigationDrawerItemColors +import androidx.compose.material3.NavigationDrawerItemDefaults +import androidx.compose.material3.SearchBar +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.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +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.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.sp +import androidx.compose.ui.util.fastForEach +import androidx.compose.ui.util.fastForEachIndexed +import com.android.compose.windowsizeclass.LocalWindowSizeClass +import com.android.systemui.res.R + +@Composable +fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () -> Unit) { + if (shouldUseSinglePane()) { + ShortcutHelperSinglePane(modifier, categories, onKeyboardSettingsClicked) + } else { + ShortcutHelperTwoPane(modifier, categories, onKeyboardSettingsClicked) + } +} + +@Composable +private fun shouldUseSinglePane() = + LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact + +@Composable +private fun ShortcutHelperSinglePane( + modifier: Modifier = Modifier, + categories: List<ShortcutHelperCategory>, + onKeyboardSettingsClicked: () -> Unit, +) { + Column( + modifier = + modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(start = 16.dp, end = 16.dp, top = 26.dp) + ) { + TitleBar() + Spacer(modifier = Modifier.height(6.dp)) + ShortcutsSearchBar() + Spacer(modifier = Modifier.height(16.dp)) + CategoriesPanelSinglePane(categories) + Spacer(modifier = Modifier.weight(1f)) + KeyboardSettings(onClick = onKeyboardSettingsClicked) + } +} + +@Composable +private fun CategoriesPanelSinglePane( + categories: List<ShortcutHelperCategory>, +) { + var expandedCategory by remember { mutableStateOf<ShortcutHelperCategory?>(null) } + Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { + categories.fastForEachIndexed { index, category -> + val isExpanded = expandedCategory == category + val itemShape = + if (index == 0) { + ShortcutHelper.Shapes.singlePaneFirstCategory + } else if (index == categories.lastIndex) { + ShortcutHelper.Shapes.singlePaneLastCategory + } else { + ShortcutHelper.Shapes.singlePaneCategory + } + CategoryItemSinglePane( + category = category, + isExpanded = isExpanded, + onClick = { + expandedCategory = + if (isExpanded) { + null + } else { + category + } + }, + shape = itemShape, + ) + } + } +} + +@Composable +private fun CategoryItemSinglePane( + category: ShortcutHelperCategory, + isExpanded: Boolean, + onClick: () -> Unit, + shape: Shape, +) { + Surface( + color = MaterialTheme.colorScheme.surfaceBright, + shape = shape, + onClick = onClick, + ) { + Column { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp) + ) { + Icon(category.icon, contentDescription = null) + Spacer(modifier = Modifier.width(16.dp)) + Text(stringResource(category.labelResId)) + Spacer(modifier = Modifier.weight(1f)) + RotatingExpandCollapseIcon(isExpanded) + } + AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(category) } + } + } +} + +@Composable +private fun RotatingExpandCollapseIcon(isExpanded: Boolean) { + val expandIconRotationDegrees by + animateFloatAsState( + targetValue = + if (isExpanded) { + 180f + } else { + 0f + }, + label = "Expand icon rotation animation" + ) + Icon( + modifier = + Modifier.background( + color = MaterialTheme.colorScheme.surfaceContainerHigh, + shape = CircleShape + ) + .graphicsLayer { rotationZ = expandIconRotationDegrees }, + imageVector = Icons.Default.ExpandMore, + contentDescription = + if (isExpanded) { + stringResource(R.string.shortcut_helper_content_description_collapse_icon) + } else { + stringResource(R.string.shortcut_helper_content_description_expand_icon) + }, + tint = MaterialTheme.colorScheme.onSurface + ) +} + +@Composable +private fun ShortcutCategoryDetailsSinglePane(category: ShortcutHelperCategory) { + Box(modifier = Modifier.fillMaxWidth().heightIn(min = 300.dp)) { + Text( + modifier = Modifier.align(Alignment.Center), + text = stringResource(category.labelResId), + ) + } +} + +@Composable +private fun ShortcutHelperTwoPane( + modifier: Modifier = Modifier, + categories: List<ShortcutHelperCategory>, + onKeyboardSettingsClicked: () -> Unit, +) { + Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) { + TitleBar() + Spacer(modifier = Modifier.height(12.dp)) + Row(Modifier.fillMaxWidth()) { + StartSidePanel( + modifier = Modifier.fillMaxWidth(fraction = 0.32f), + categories = categories, + onKeyboardSettingsClicked = onKeyboardSettingsClicked, + ) + Spacer(modifier = Modifier.width(24.dp)) + EndSidePanel(Modifier.fillMaxSize()) + } + } +} + +@Composable +private fun StartSidePanel( + modifier: Modifier, + categories: List<ShortcutHelperCategory>, + onKeyboardSettingsClicked: () -> Unit, +) { + Column(modifier) { + ShortcutsSearchBar() + Spacer(modifier = Modifier.heightIn(16.dp)) + CategoriesPanelTwoPane(categories) + Spacer(modifier = Modifier.weight(1f)) + KeyboardSettings(onKeyboardSettingsClicked) + } +} + +@Composable +private fun CategoriesPanelTwoPane(categories: List<ShortcutHelperCategory>) { + var selected by remember { mutableStateOf(categories.first()) } + Column { + categories.fastForEach { + CategoryItemTwoPane( + label = stringResource(it.labelResId), + icon = it.icon, + selected = selected == it, + onClick = { selected = it } + ) + } + } +} + +@Composable +private fun CategoryItemTwoPane( + label: String, + icon: ImageVector, + selected: Boolean, + onClick: () -> Unit, + colors: NavigationDrawerItemColors = + NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent), +) { + Surface( + selected = selected, + onClick = onClick, + modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 72.dp).fillMaxWidth(), + shape = RoundedCornerShape(28.dp), + color = colors.containerColor(selected).value, + ) { + Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = icon, + contentDescription = null, + tint = colors.iconColor(selected).value + ) + Spacer(Modifier.width(12.dp)) + Box(Modifier.weight(1f)) { + Text( + fontSize = 18.sp, + color = colors.textColor(selected).value, + style = MaterialTheme.typography.headlineSmall, + text = label + ) + } + } + } +} + +@Composable +fun EndSidePanel(modifier: Modifier) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(28.dp), + color = MaterialTheme.colorScheme.surfaceBright + ) {} +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun TitleBar() { + CenterAlignedTopAppBar( + colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent), + title = { + Text( + text = stringResource(R.string.shortcut_helper_title), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.headlineSmall + ) + } + ) +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun ShortcutsSearchBar() { + var query by remember { mutableStateOf("") } + SearchBar( + colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright), + query = query, + active = false, + onActiveChange = {}, + onQueryChange = { query = it }, + onSearch = {}, + leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }, + placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) }, + content = {} + ) +} + +@Composable +private fun KeyboardSettings(onClick: () -> Unit) { + Surface( + onClick = onClick, + shape = RoundedCornerShape(24.dp), + color = Color.Transparent, + modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth() + ) { + Row( + modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + "Keyboard Settings", + color = MaterialTheme.colorScheme.onSurfaceVariant, + fontSize = 16.sp + ) + Spacer(modifier = Modifier.width(8.dp)) + Icon( + imageVector = Icons.AutoMirrored.Default.OpenInNew, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +/** 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 { + val singlePaneFirstCategory = + RoundedCornerShape( + topStart = Dimensions.SinglePaneCategoryCornerRadius, + topEnd = Dimensions.SinglePaneCategoryCornerRadius + ) + val singlePaneLastCategory = + RoundedCornerShape( + bottomStart = Dimensions.SinglePaneCategoryCornerRadius, + bottomEnd = Dimensions.SinglePaneCategoryCornerRadius + ) + val singlePaneCategory = RectangleShape + } + + object Dimensions { + val SinglePaneCategoryCornerRadius = 28.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt index ef4156da4f7b..1e8d23918964 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt @@ -23,9 +23,12 @@ import android.view.WindowInsets import androidx.activity.BackEventCompat import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback +import androidx.compose.ui.platform.ComposeView import androidx.core.view.updatePadding import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope +import com.android.compose.theme.PlatformTheme +import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel import com.android.systemui.res.R import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -58,14 +61,30 @@ constructor( super.onCreate(savedInstanceState) setContentView(R.layout.activity_keyboard_shortcut_helper) setUpBottomSheetWidth() + expandBottomSheet() setUpInsets() setUpPredictiveBack() setUpSheetDismissListener() setUpDismissOnTouchOutside() + setUpComposeView() observeFinishRequired() viewModel.onViewOpened() } + private fun setUpComposeView() { + requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply { + setContent { + PlatformTheme { + ShortcutHelper( + onKeyboardSettingsClicked = ::onKeyboardSettingsClicked, + ) + } + } + } + } + + private fun onKeyboardSettingsClicked() {} + override fun onDestroy() { super.onDestroy() if (isFinishing) { @@ -101,7 +120,8 @@ constructor( bottomSheetContainer.setOnApplyWindowInsetsListener { _, insets -> val safeDrawingInsets = insets.safeDrawing // Make sure the bottom sheet is not covered by the status bar. - bottomSheetContainer.updatePadding(top = safeDrawingInsets.top) + bottomSheetBehavior.maxHeight = + resources.displayMetrics.heightPixels - safeDrawingInsets.top // Make sure the contents inside of the bottom sheet are not hidden by system bars, or // cutouts. bottomSheet.updatePadding( @@ -171,7 +191,6 @@ constructor( private val WindowInsets.safeDrawing get() = getInsets(WindowInsets.Type.systemBars()) - .union(getInsets(WindowInsets.Type.ime())) .union(getInsets(WindowInsets.Type.displayCutout())) private fun Insets.union(insets: Insets): Insets = Insets.max(this, insets) |