Loading packages/SystemUI/res/values/strings.xml +4 −0 Original line number Original line Diff line number Diff line Loading @@ -3827,6 +3827,10 @@ assigning a new custom key combination to a shortcut in shortcut helper. The helper is a assigning a new custom key combination to a shortcut in shortcut helper. The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string> <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string> <!-- Plus sign, used in keyboard shortcut helper to combine keys for shortcut. E.g. Ctrl + A The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_plus_symbol">+</string> <!-- Keyboard touchpad tutorial scheduler--> <!-- Keyboard touchpad tutorial scheduler--> Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt +13 −3 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.shortcut.ui package com.android.systemui.keyboard.shortcut.ui import android.app.Dialog import android.app.Dialog import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight Loading @@ -33,6 +34,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.statusbar.phone.create import com.android.systemui.statusbar.phone.create import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation class ShortcutCustomizationDialogStarter class ShortcutCustomizationDialogStarter @AssistedInject @AssistedInject Loading @@ -57,6 +59,7 @@ constructor( dialog = null dialog = null } } } } awaitCancellation() } } fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { Loading @@ -66,14 +69,21 @@ constructor( private fun createAddShortcutDialog(): Dialog { private fun createAddShortcutDialog(): Dialog { return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog -> -> val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle() val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle( initialValue = ShortcutCustomizationUiState.Inactive ) AssignNewShortcutDialog( AssignNewShortcutDialog( uiState = uiState, uiState = uiState, modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp), modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp), onKeyPress = { viewModel.onKeyPressed(it) }, onKeyPress = { viewModel.onKeyPressed(it) }, onCancel = { dialog.dismiss() }, onCancel = { dialog.dismiss() }, ) ) dialog.setOnDismissListener { viewModel.onAddShortcutDialogDismissed() } dialog.setOnDismissListener { viewModel.onDialogDismissed() } // By default, apps cannot intercept action key. The system always handles it. This // flag is needed to enable customisation dialog window to intercept action key dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) } } } } Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt +82 −18 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding Loading @@ -39,18 +40,22 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.res.R import com.android.systemui.res.R Loading Loading @@ -81,15 +86,16 @@ fun AssignNewShortcutDialog( SelectedKeyCombinationContainer( SelectedKeyCombinationContainer( shouldShowErrorMessage = uiState.shouldShowErrorMessage, shouldShowErrorMessage = uiState.shouldShowErrorMessage, onKeyPress = onKeyPress, onKeyPress = onKeyPress, pressedKeys = uiState.pressedKeys, ) ) KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage) KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage) DialogButtons(onCancel, isValidKeyCombination = uiState.isValidKeyCombination) DialogButtons(onCancel, isSetShortcutButtonEnabled = uiState.pressedKeys.isNotEmpty()) } } } } } } @Composable @Composable fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) { fun DialogButtons(onCancel: () -> Unit, isSetShortcutButtonEnabled: Boolean) { Row( Row( modifier = modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp) Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp) Loading @@ -113,7 +119,7 @@ fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) { contentColor = MaterialTheme.colorScheme.onPrimary, contentColor = MaterialTheme.colorScheme.onPrimary, text = text = stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label), stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label), enabled = isValidKeyCombination, enabled = isSetShortcutButtonEnabled, ) ) } } } } Loading @@ -137,10 +143,9 @@ fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) { @Composable @Composable fun SelectedKeyCombinationContainer( fun SelectedKeyCombinationContainer( keyCombination: String = stringResource(R.string.shortcut_helper_add_shortcut_dialog_placeholder), shouldShowErrorMessage: Boolean, shouldShowErrorMessage: Boolean, onKeyPress: (KeyEvent) -> Boolean, onKeyPress: (KeyEvent) -> Boolean, pressedKeys: List<ShortcutKey>, ) { ) { val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val isFocused by interactionSource.collectIsFocusedAsState() Loading @@ -148,6 +153,9 @@ fun SelectedKeyCombinationContainer( if (!isFocused) MaterialTheme.colorScheme.outline if (!isFocused) MaterialTheme.colorScheme.outline else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.primary val focusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { focusRequester.requestFocus() } ClickableShortcutSurface( ClickableShortcutSurface( onClick = {}, onClick = {}, Loading @@ -157,22 +165,19 @@ fun SelectedKeyCombinationContainer( Modifier.padding(all = 16.dp) Modifier.padding(all = 16.dp) .sizeIn(minWidth = 332.dp, minHeight = 56.dp) .sizeIn(minWidth = 332.dp, minHeight = 56.dp) .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp)) .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp)) .onPreviewKeyEvent { onKeyPress(it) }, .onKeyEvent { onKeyPress(it) } .focusRequester(focusRequester), interactionSource = interactionSource, interactionSource = interactionSource, ) { ) { Row( Row( modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically, ) { ) { Text( if (pressedKeys.isEmpty()) { text = keyCombination, PressKeyPrompt() style = MaterialTheme.typography.headlineSmall, } else { fontSize = 16.sp, PressedKeysTextContainer(pressedKeys) lineHeight = 24.sp, } fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.width(252.dp), ) Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f)) if (shouldShowErrorMessage) { if (shouldShowErrorMessage) { Icon( Icon( Loading @@ -186,6 +191,67 @@ fun SelectedKeyCombinationContainer( } } } } @Composable private fun RowScope.PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) { pressedKeys.forEachIndexed { keyIndex, key -> if (keyIndex > 0) { ShortcutKeySeparator() } if (key is ShortcutKey.Text) { ShortcutTextKey(key) } else if (key is ShortcutKey.Icon) { ShortcutIconKey(key) } } } @Composable private fun ShortcutKeySeparator() { Text( text = stringResource(id = R.string.shortcut_helper_plus_symbol), style = MaterialTheme.typography.titleSmall, fontSize = 16.sp, lineHeight = 24.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) { Icon( painter = when (key) { is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId) is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable) }, contentDescription = null, modifier = Modifier.align(Alignment.CenterVertically).height(24.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable private fun PressKeyPrompt() { Text( text = stringResource(id = R.string.shortcut_helper_add_shortcut_dialog_placeholder), style = MaterialTheme.typography.titleSmall, fontSize = 16.sp, lineHeight = 24.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable private fun ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, style = MaterialTheme.typography.titleSmall, fontSize = 16.sp, lineHeight = 24.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable @Composable private fun Title(title: String, modifier: Modifier = Modifier) { private fun Title(title: String, modifier: Modifier = Modifier) { Text( Text( Loading @@ -203,8 +269,6 @@ private fun Description(modifier: Modifier = Modifier) { Text( Text( text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title), text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title), style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium, fontSize = 14.sp, lineHeight = 20.sp, modifier = modifier.wrapContentSize(Alignment.Center), modifier = modifier.wrapContentSize(Alignment.Center), color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant, ) ) Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -22,9 +22,9 @@ sealed interface ShortcutCustomizationUiState { data class AddShortcutDialog( data class AddShortcutDialog( val shortcutLabel: String, val shortcutLabel: String, val shouldShowErrorMessage: Boolean, val shouldShowErrorMessage: Boolean, val isValidKeyCombination: Boolean, val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, val isDialogShowing: Boolean, val isDialogShowing: Boolean, val pressedKeys: List<ShortcutKey> = emptyList(), ) : ShortcutCustomizationUiState ) : ShortcutCustomizationUiState data object Inactive : ShortcutCustomizationUiState data object Inactive : ShortcutCustomizationUiState Loading packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt +57 −5 Original line number Original line Diff line number Diff line Loading @@ -17,14 +17,22 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel package com.android.systemui.keyboard.shortcut.ui.viewmodel import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.isMetaPressed import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.nativeKeyCode import androidx.compose.ui.input.key.type import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update class ShortcutCustomizationViewModel class ShortcutCustomizationViewModel Loading @@ -35,7 +43,21 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn private val _shortcutCustomizationUiState = private val _shortcutCustomizationUiState = MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive) MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive) val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow() val shortcutCustomizationUiState = shortcutCustomizationInteractor.pressedKeys .map { keys -> // Note that Action Key is excluded as it's already displayed on the UI keys.filter { it != shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey() } } .combine(_shortcutCustomizationUiState) { keys, uiState -> if (uiState is ShortcutCustomizationUiState.AddShortcutDialog) { uiState.copy(pressedKeys = keys) } else { uiState } } fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { when (requestInfo) { when (requestInfo) { Loading @@ -44,10 +66,10 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn ShortcutCustomizationUiState.AddShortcutDialog( ShortcutCustomizationUiState.AddShortcutDialog( shortcutLabel = requestInfo.label, shortcutLabel = requestInfo.label, shouldShowErrorMessage = false, shouldShowErrorMessage = false, isValidKeyCombination = false, defaultCustomShortcutModifierKey = defaultCustomShortcutModifierKey = shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), isDialogShowing = false, isDialogShowing = false, pressedKeys = emptyList(), ) ) _shortcutBeingCustomized.value = requestInfo _shortcutBeingCustomized.value = requestInfo } } Loading @@ -62,18 +84,48 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn } } } } fun onAddShortcutDialogDismissed() { fun onDialogDismissed() { _shortcutBeingCustomized.value = null _shortcutBeingCustomized.value = null _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null) } } fun onKeyPressed(keyEvent: KeyEvent): Boolean { fun onKeyPressed(keyEvent: KeyEvent): Boolean { // TODO Not yet implemented b/373638584 if ((keyEvent.isMetaPressed && keyEvent.type == KeyEventType.KeyDown)) { updatePressedKeys(keyEvent) return true } return false return false } } private fun updatePressedKeys(keyEvent: KeyEvent) { val isModifier = SUPPORTED_MODIFIERS.contains(keyEvent.key) val keyCombination = KeyCombination( modifiers = keyEvent.nativeKeyEvent.modifiers, keyCode = if (!isModifier) keyEvent.key.nativeKeyCode else null, ) shortcutCustomizationInteractor.updateUserSelectedKeyCombination(keyCombination) } @AssistedFactory @AssistedFactory interface Factory { interface Factory { fun create(): ShortcutCustomizationViewModel fun create(): ShortcutCustomizationViewModel } } companion object { private val SUPPORTED_MODIFIERS = listOf( Key.MetaLeft, Key.MetaRight, Key.CtrlRight, Key.CtrlLeft, Key.AltLeft, Key.AltRight, Key.ShiftLeft, Key.ShiftRight, Key.Function, Key.Symbol, ) } } } Loading
packages/SystemUI/res/values/strings.xml +4 −0 Original line number Original line Diff line number Diff line Loading @@ -3827,6 +3827,10 @@ assigning a new custom key combination to a shortcut in shortcut helper. The helper is a assigning a new custom key combination to a shortcut in shortcut helper. The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string> <string name="shortcut_helper_customize_dialog_error_message">Key combination already in use. Try another key.</string> <!-- Plus sign, used in keyboard shortcut helper to combine keys for shortcut. E.g. Ctrl + A The helper is a component that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] --> <string name="shortcut_helper_plus_symbol">+</string> <!-- Keyboard touchpad tutorial scheduler--> <!-- Keyboard touchpad tutorial scheduler--> Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/ShortcutCustomizationDialogStarter.kt +13 −3 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.systemui.keyboard.shortcut.ui package com.android.systemui.keyboard.shortcut.ui import android.app.Dialog import android.app.Dialog import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight Loading @@ -33,6 +34,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.statusbar.phone.create import com.android.systemui.statusbar.phone.create import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation class ShortcutCustomizationDialogStarter class ShortcutCustomizationDialogStarter @AssistedInject @AssistedInject Loading @@ -57,6 +59,7 @@ constructor( dialog = null dialog = null } } } } awaitCancellation() } } fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { Loading @@ -66,14 +69,21 @@ constructor( private fun createAddShortcutDialog(): Dialog { private fun createAddShortcutDialog(): Dialog { return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog -> -> val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle() val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle( initialValue = ShortcutCustomizationUiState.Inactive ) AssignNewShortcutDialog( AssignNewShortcutDialog( uiState = uiState, uiState = uiState, modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp), modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp), onKeyPress = { viewModel.onKeyPressed(it) }, onKeyPress = { viewModel.onKeyPressed(it) }, onCancel = { dialog.dismiss() }, onCancel = { dialog.dismiss() }, ) ) dialog.setOnDismissListener { viewModel.onAddShortcutDialogDismissed() } dialog.setOnDismissListener { viewModel.onDialogDismissed() } // By default, apps cannot intercept action key. The system always handles it. This // flag is needed to enable customisation dialog window to intercept action key dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) } } } } Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutCustomizer.kt +82 −18 Original line number Original line Diff line number Diff line Loading @@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding Loading @@ -39,18 +40,22 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp import com.android.compose.ui.graphics.painter.rememberDrawablePainter import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.shared.model.ShortcutKey import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.res.R import com.android.systemui.res.R Loading Loading @@ -81,15 +86,16 @@ fun AssignNewShortcutDialog( SelectedKeyCombinationContainer( SelectedKeyCombinationContainer( shouldShowErrorMessage = uiState.shouldShowErrorMessage, shouldShowErrorMessage = uiState.shouldShowErrorMessage, onKeyPress = onKeyPress, onKeyPress = onKeyPress, pressedKeys = uiState.pressedKeys, ) ) KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage) KeyCombinationAlreadyInUseErrorMessage(uiState.shouldShowErrorMessage) DialogButtons(onCancel, isValidKeyCombination = uiState.isValidKeyCombination) DialogButtons(onCancel, isSetShortcutButtonEnabled = uiState.pressedKeys.isNotEmpty()) } } } } } } @Composable @Composable fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) { fun DialogButtons(onCancel: () -> Unit, isSetShortcutButtonEnabled: Boolean) { Row( Row( modifier = modifier = Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp) Modifier.padding(top = 24.dp, start = 24.dp, end = 24.dp) Loading @@ -113,7 +119,7 @@ fun DialogButtons(onCancel: () -> Unit, isValidKeyCombination: Boolean) { contentColor = MaterialTheme.colorScheme.onPrimary, contentColor = MaterialTheme.colorScheme.onPrimary, text = text = stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label), stringResource(R.string.shortcut_helper_customize_dialog_set_shortcut_button_label), enabled = isValidKeyCombination, enabled = isSetShortcutButtonEnabled, ) ) } } } } Loading @@ -137,10 +143,9 @@ fun KeyCombinationAlreadyInUseErrorMessage(shouldShowErrorMessage: Boolean) { @Composable @Composable fun SelectedKeyCombinationContainer( fun SelectedKeyCombinationContainer( keyCombination: String = stringResource(R.string.shortcut_helper_add_shortcut_dialog_placeholder), shouldShowErrorMessage: Boolean, shouldShowErrorMessage: Boolean, onKeyPress: (KeyEvent) -> Boolean, onKeyPress: (KeyEvent) -> Boolean, pressedKeys: List<ShortcutKey>, ) { ) { val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() } val isFocused by interactionSource.collectIsFocusedAsState() val isFocused by interactionSource.collectIsFocusedAsState() Loading @@ -148,6 +153,9 @@ fun SelectedKeyCombinationContainer( if (!isFocused) MaterialTheme.colorScheme.outline if (!isFocused) MaterialTheme.colorScheme.outline else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error else if (shouldShowErrorMessage) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.primary val focusRequester = remember { FocusRequester() } LaunchedEffect(Unit) { focusRequester.requestFocus() } ClickableShortcutSurface( ClickableShortcutSurface( onClick = {}, onClick = {}, Loading @@ -157,22 +165,19 @@ fun SelectedKeyCombinationContainer( Modifier.padding(all = 16.dp) Modifier.padding(all = 16.dp) .sizeIn(minWidth = 332.dp, minHeight = 56.dp) .sizeIn(minWidth = 332.dp, minHeight = 56.dp) .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp)) .border(width = 2.dp, color = outlineColor, shape = RoundedCornerShape(50.dp)) .onPreviewKeyEvent { onKeyPress(it) }, .onKeyEvent { onKeyPress(it) } .focusRequester(focusRequester), interactionSource = interactionSource, interactionSource = interactionSource, ) { ) { Row( Row( modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 16.dp, bottom = 16.dp), verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically, ) { ) { Text( if (pressedKeys.isEmpty()) { text = keyCombination, PressKeyPrompt() style = MaterialTheme.typography.headlineSmall, } else { fontSize = 16.sp, PressedKeysTextContainer(pressedKeys) lineHeight = 24.sp, } fontWeight = FontWeight.W500, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.width(252.dp), ) Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f)) if (shouldShowErrorMessage) { if (shouldShowErrorMessage) { Icon( Icon( Loading @@ -186,6 +191,67 @@ fun SelectedKeyCombinationContainer( } } } } @Composable private fun RowScope.PressedKeysTextContainer(pressedKeys: List<ShortcutKey>) { pressedKeys.forEachIndexed { keyIndex, key -> if (keyIndex > 0) { ShortcutKeySeparator() } if (key is ShortcutKey.Text) { ShortcutTextKey(key) } else if (key is ShortcutKey.Icon) { ShortcutIconKey(key) } } } @Composable private fun ShortcutKeySeparator() { Text( text = stringResource(id = R.string.shortcut_helper_plus_symbol), style = MaterialTheme.typography.titleSmall, fontSize = 16.sp, lineHeight = 24.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable private fun RowScope.ShortcutIconKey(key: ShortcutKey.Icon) { Icon( painter = when (key) { is ShortcutKey.Icon.ResIdIcon -> painterResource(key.drawableResId) is ShortcutKey.Icon.DrawableIcon -> rememberDrawablePainter(drawable = key.drawable) }, contentDescription = null, modifier = Modifier.align(Alignment.CenterVertically).height(24.dp), tint = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable private fun PressKeyPrompt() { Text( text = stringResource(id = R.string.shortcut_helper_add_shortcut_dialog_placeholder), style = MaterialTheme.typography.titleSmall, fontSize = 16.sp, lineHeight = 24.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable private fun ShortcutTextKey(key: ShortcutKey.Text) { Text( text = key.value, style = MaterialTheme.typography.titleSmall, fontSize = 16.sp, lineHeight = 24.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, ) } @Composable @Composable private fun Title(title: String, modifier: Modifier = Modifier) { private fun Title(title: String, modifier: Modifier = Modifier) { Text( Text( Loading @@ -203,8 +269,6 @@ private fun Description(modifier: Modifier = Modifier) { Text( Text( text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title), text = stringResource(id = R.string.shortcut_helper_customize_mode_sub_title), style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium, fontSize = 14.sp, lineHeight = 20.sp, modifier = modifier.wrapContentSize(Alignment.Center), modifier = modifier.wrapContentSize(Alignment.Center), color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant, ) ) Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/model/ShortcutCustomizationUiState.kt +1 −1 Original line number Original line Diff line number Diff line Loading @@ -22,9 +22,9 @@ sealed interface ShortcutCustomizationUiState { data class AddShortcutDialog( data class AddShortcutDialog( val shortcutLabel: String, val shortcutLabel: String, val shouldShowErrorMessage: Boolean, val shouldShowErrorMessage: Boolean, val isValidKeyCombination: Boolean, val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, val defaultCustomShortcutModifierKey: ShortcutKey.Icon.ResIdIcon, val isDialogShowing: Boolean, val isDialogShowing: Boolean, val pressedKeys: List<ShortcutKey> = emptyList(), ) : ShortcutCustomizationUiState ) : ShortcutCustomizationUiState data object Inactive : ShortcutCustomizationUiState data object Inactive : ShortcutCustomizationUiState Loading
packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutCustomizationViewModel.kt +57 −5 Original line number Original line Diff line number Diff line Loading @@ -17,14 +17,22 @@ package com.android.systemui.keyboard.shortcut.ui.viewmodel package com.android.systemui.keyboard.shortcut.ui.viewmodel import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.isMetaPressed import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.nativeKeyCode import androidx.compose.ui.input.key.type import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor import com.android.systemui.keyboard.shortcut.domain.interactor.ShortcutCustomizationInteractor import com.android.systemui.keyboard.shortcut.shared.model.KeyCombination import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update class ShortcutCustomizationViewModel class ShortcutCustomizationViewModel Loading @@ -35,7 +43,21 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn private val _shortcutCustomizationUiState = private val _shortcutCustomizationUiState = MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive) MutableStateFlow<ShortcutCustomizationUiState>(ShortcutCustomizationUiState.Inactive) val shortcutCustomizationUiState = _shortcutCustomizationUiState.asStateFlow() val shortcutCustomizationUiState = shortcutCustomizationInteractor.pressedKeys .map { keys -> // Note that Action Key is excluded as it's already displayed on the UI keys.filter { it != shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey() } } .combine(_shortcutCustomizationUiState) { keys, uiState -> if (uiState is ShortcutCustomizationUiState.AddShortcutDialog) { uiState.copy(pressedKeys = keys) } else { uiState } } fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { when (requestInfo) { when (requestInfo) { Loading @@ -44,10 +66,10 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn ShortcutCustomizationUiState.AddShortcutDialog( ShortcutCustomizationUiState.AddShortcutDialog( shortcutLabel = requestInfo.label, shortcutLabel = requestInfo.label, shouldShowErrorMessage = false, shouldShowErrorMessage = false, isValidKeyCombination = false, defaultCustomShortcutModifierKey = defaultCustomShortcutModifierKey = shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), shortcutCustomizationInteractor.getDefaultCustomShortcutModifierKey(), isDialogShowing = false, isDialogShowing = false, pressedKeys = emptyList(), ) ) _shortcutBeingCustomized.value = requestInfo _shortcutBeingCustomized.value = requestInfo } } Loading @@ -62,18 +84,48 @@ constructor(private val shortcutCustomizationInteractor: ShortcutCustomizationIn } } } } fun onAddShortcutDialogDismissed() { fun onDialogDismissed() { _shortcutBeingCustomized.value = null _shortcutBeingCustomized.value = null _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive _shortcutCustomizationUiState.value = ShortcutCustomizationUiState.Inactive shortcutCustomizationInteractor.updateUserSelectedKeyCombination(null) } } fun onKeyPressed(keyEvent: KeyEvent): Boolean { fun onKeyPressed(keyEvent: KeyEvent): Boolean { // TODO Not yet implemented b/373638584 if ((keyEvent.isMetaPressed && keyEvent.type == KeyEventType.KeyDown)) { updatePressedKeys(keyEvent) return true } return false return false } } private fun updatePressedKeys(keyEvent: KeyEvent) { val isModifier = SUPPORTED_MODIFIERS.contains(keyEvent.key) val keyCombination = KeyCombination( modifiers = keyEvent.nativeKeyEvent.modifiers, keyCode = if (!isModifier) keyEvent.key.nativeKeyCode else null, ) shortcutCustomizationInteractor.updateUserSelectedKeyCombination(keyCombination) } @AssistedFactory @AssistedFactory interface Factory { interface Factory { fun create(): ShortcutCustomizationViewModel fun create(): ShortcutCustomizationViewModel } } companion object { private val SUPPORTED_MODIFIERS = listOf( Key.MetaLeft, Key.MetaRight, Key.CtrlRight, Key.CtrlLeft, Key.AltLeft, Key.AltRight, Key.ShiftLeft, Key.ShiftRight, Key.Function, Key.Symbol, ) } } }