1 /* <lambda>null2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.keyboard.shortcut.ui 18 19 import android.app.Dialog 20 import android.content.res.Resources 21 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS 22 import androidx.compose.foundation.layout.padding 23 import androidx.compose.foundation.layout.width 24 import androidx.compose.foundation.layout.wrapContentHeight 25 import androidx.compose.runtime.getValue 26 import androidx.compose.runtime.rememberCoroutineScope 27 import androidx.compose.ui.Modifier 28 import androidx.compose.ui.unit.dp 29 import androidx.lifecycle.compose.collectAsStateWithLifecycle 30 import com.android.systemui.dagger.qualifiers.Main 31 import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCustomizationRequestInfo 32 import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutCustomizationDialog 33 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState 34 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.AddShortcutDialog 35 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.DeleteShortcutDialog 36 import com.android.systemui.keyboard.shortcut.ui.model.ShortcutCustomizationUiState.ResetShortcutDialog 37 import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutCustomizationViewModel 38 import com.android.systemui.lifecycle.ExclusiveActivatable 39 import com.android.systemui.res.R 40 import com.android.systemui.statusbar.phone.SystemUIDialog 41 import com.android.systemui.statusbar.phone.SystemUIDialogFactory 42 import com.android.systemui.statusbar.phone.create 43 import dagger.assisted.AssistedFactory 44 import dagger.assisted.AssistedInject 45 import kotlinx.coroutines.awaitCancellation 46 import kotlinx.coroutines.coroutineScope 47 import kotlinx.coroutines.launch 48 49 class ShortcutCustomizationDialogStarter 50 @AssistedInject 51 constructor( 52 viewModelFactory: ShortcutCustomizationViewModel.Factory, 53 private val dialogFactory: SystemUIDialogFactory, 54 @Main private val resources: Resources, 55 ) : ExclusiveActivatable() { 56 57 private var dialog: Dialog? = null 58 private val viewModel = viewModelFactory.create() 59 60 override suspend fun onActivated(): Nothing { 61 coroutineScope { 62 launch { 63 viewModel.shortcutCustomizationUiState.collect { uiState -> 64 when (uiState) { 65 is AddShortcutDialog, 66 is DeleteShortcutDialog, 67 is ResetShortcutDialog -> { 68 if (dialog == null) { 69 dialog = createDialog().also { it.show() } 70 } 71 } 72 is ShortcutCustomizationUiState.Inactive -> { 73 dialog?.dismiss() 74 dialog = null 75 } 76 } 77 } 78 } 79 launch { viewModel.activate() } 80 } 81 awaitCancellation() 82 } 83 84 fun onShortcutCustomizationRequested(requestInfo: ShortcutCustomizationRequestInfo) { 85 viewModel.onShortcutCustomizationRequested(requestInfo) 86 } 87 88 private fun createDialog(): Dialog { 89 return dialogFactory.create(dialogDelegate = ShortcutCustomizationDialogDelegate()) { dialog 90 -> 91 val uiState by viewModel.shortcutCustomizationUiState.collectAsStateWithLifecycle() 92 val coroutineScope = rememberCoroutineScope() 93 ShortcutCustomizationDialog( 94 uiState = uiState, 95 modifier = Modifier.width(364.dp).wrapContentHeight().padding(vertical = 24.dp), 96 onShortcutKeyCombinationSelected = { 97 viewModel.onShortcutKeyCombinationSelected(it) 98 }, 99 onCancel = { dialog.dismiss() }, 100 onConfirmSetShortcut = { coroutineScope.launch { viewModel.onSetShortcut() } }, 101 onConfirmDeleteShortcut = { 102 coroutineScope.launch { viewModel.deleteShortcutCurrentlyBeingCustomized() } 103 }, 104 onConfirmResetShortcut = { 105 coroutineScope.launch { viewModel.resetAllCustomShortcuts() } 106 }, 107 onClearSelectedKeyCombination = { viewModel.clearSelectedKeyCombination() }, 108 ) 109 setDialogProperties(dialog, uiState) 110 } 111 } 112 113 private fun setDialogProperties(dialog: SystemUIDialog, uiState: ShortcutCustomizationUiState) { 114 dialog.setOnDismissListener { viewModel.onDialogDismissed() } 115 dialog.setTitle("${getDialogTitle(uiState)}. ${getDialogDescription(uiState)}") 116 // By default, apps cannot intercept action key. The system always handles it. This 117 // flag is needed to enable customisation dialog window to intercept action key 118 dialog.window?.addPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) 119 } 120 121 private fun getDialogTitle(uiState: ShortcutCustomizationUiState): String { 122 return when (uiState) { 123 is AddShortcutDialog -> uiState.shortcutLabel 124 is DeleteShortcutDialog -> 125 resources.getString(R.string.shortcut_customize_mode_remove_shortcut_dialog_title) 126 else -> 127 resources.getString(R.string.shortcut_customize_mode_reset_shortcut_dialog_title) 128 } 129 } 130 131 private fun getDialogDescription(uiState: ShortcutCustomizationUiState): String { 132 return resources.getString( 133 when (uiState) { 134 is AddShortcutDialog -> R.string.shortcut_customize_mode_add_shortcut_description 135 is DeleteShortcutDialog -> 136 R.string.shortcut_customize_mode_remove_shortcut_description 137 else -> R.string.shortcut_customize_mode_reset_shortcut_description 138 } 139 ) 140 } 141 142 @AssistedFactory 143 interface Factory { 144 fun create(): ShortcutCustomizationDialogStarter 145 } 146 } 147