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

Commit 6a359f58 authored by Joshua Mokut's avatar Joshua Mokut Committed by Android (Google) Code Review
Browse files

Merge changes I79008ba1,Iac6718b5 into main

* changes:
  Minor UI Changes
  Capturing user's selected custom key combination for shortcuts
parents b98adbf0 482cb781
Loading
Loading
Loading
Loading
+4 −0
Original line number Original line Diff line number Diff line
@@ -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-->
+13 −3
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -57,6 +59,7 @@ constructor(
                dialog = null
                dialog = null
            }
            }
        }
        }
        awaitCancellation()
    }
    }


    fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
    fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) {
@@ -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)
        }
        }
    }
    }


+82 −18
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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)
@@ -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,
        )
        )
    }
    }
}
}
@@ -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()
@@ -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 = {},
@@ -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(
@@ -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(
@@ -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,
    )
    )
+1 −1
Original line number Original line Diff line number Diff line
@@ -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
+57 −5
Original line number Original line Diff line number Diff line
@@ -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
@@ -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) {
@@ -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
            }
            }
@@ -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