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.touchpad.tutorial.ui.composable
18
19 import android.content.res.Configuration
20 import androidx.compose.foundation.background
21 import androidx.compose.foundation.focusable
22 import androidx.compose.foundation.layout.Arrangement
23 import androidx.compose.foundation.layout.Column
24 import androidx.compose.foundation.layout.Row
25 import androidx.compose.foundation.layout.Spacer
26 import androidx.compose.foundation.layout.fillMaxSize
27 import androidx.compose.foundation.layout.height
28 import androidx.compose.foundation.layout.padding
29 import androidx.compose.foundation.layout.safeDrawingPadding
30 import androidx.compose.foundation.layout.size
31 import androidx.compose.foundation.layout.width
32 import androidx.compose.foundation.shape.RoundedCornerShape
33 import androidx.compose.material.icons.Icons
34 import androidx.compose.material.icons.automirrored.outlined.ArrowBack
35 import androidx.compose.material3.Button
36 import androidx.compose.material3.ButtonDefaults
37 import androidx.compose.material3.Icon
38 import androidx.compose.material3.MaterialTheme
39 import androidx.compose.material3.Text
40 import androidx.compose.runtime.Composable
41 import androidx.compose.runtime.LaunchedEffect
42 import androidx.compose.runtime.remember
43 import androidx.compose.ui.Alignment
44 import androidx.compose.ui.Modifier
45 import androidx.compose.ui.focus.FocusRequester
46 import androidx.compose.ui.focus.focusRequester
47 import androidx.compose.ui.graphics.Color
48 import androidx.compose.ui.graphics.vector.ImageVector
49 import androidx.compose.ui.input.pointer.pointerInteropFilter
50 import androidx.compose.ui.platform.LocalConfiguration
51 import androidx.compose.ui.res.stringResource
52 import androidx.compose.ui.res.vectorResource
53 import androidx.compose.ui.text.style.TextAlign
54 import androidx.compose.ui.unit.dp
55 import com.android.systemui.inputdevice.tutorial.ui.composable.DoneButton
56 import com.android.systemui.keyboard.shortcut.ui.composable.hasCompactWindowSize
57 import com.android.systemui.res.R
58 import com.android.systemui.touchpad.tutorial.ui.gesture.isFourFingerTouchpadSwipe
59 import com.android.systemui.touchpad.tutorial.ui.gesture.isThreeFingerTouchpadSwipe
60 import com.android.systemui.touchpad.tutorial.ui.viewmodel.Screen
61
62 @Composable
63 fun TutorialSelectionScreen(
64 onBackTutorialClicked: () -> Unit,
65 onHomeTutorialClicked: () -> Unit,
66 onRecentAppsTutorialClicked: () -> Unit,
67 onSwitchAppsTutorialClicked: () -> Unit,
68 onDoneButtonClicked: () -> Unit,
69 lastSelectedScreen: Screen,
70 ) {
71 Column(
72 verticalArrangement = Arrangement.Center,
73 modifier =
74 Modifier.background(color = MaterialTheme.colorScheme.surfaceContainer)
75 .fillMaxSize()
76 .safeDrawingPadding()
77 .pointerInteropFilter(
78 onTouchEvent = { event ->
79 // Because of window flag we're intercepting 3 and 4-finger swipes.
80 // Although we don't handle them in this screen, we want to disable them so
81 // that user is not clicking button by mistake by performing these swipes.
82 isThreeFingerTouchpadSwipe(event) || isFourFingerTouchpadSwipe(event)
83 }
84 ),
85 ) {
86 val isCompactWindow = hasCompactWindowSize()
87 val padding = if (isCompactWindow) 24.dp else 48.dp
88 val configuration = LocalConfiguration.current
89 when (configuration.orientation) {
90 Configuration.ORIENTATION_LANDSCAPE -> {
91 if (isCompactWindow)
92 HorizontalCompactSelectionButtons(
93 onBackTutorialClicked = onBackTutorialClicked,
94 onHomeTutorialClicked = onHomeTutorialClicked,
95 onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
96 onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
97 lastSelectedScreen,
98 modifier = Modifier.weight(1f).padding(padding),
99 )
100 else
101 HorizontalSelectionButtons(
102 onBackTutorialClicked = onBackTutorialClicked,
103 onHomeTutorialClicked = onHomeTutorialClicked,
104 onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
105 onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
106 lastSelectedScreen,
107 modifier = Modifier.weight(1f).padding(padding),
108 )
109 }
110 else -> {
111 VerticalSelectionButtons(
112 onBackTutorialClicked = onBackTutorialClicked,
113 onHomeTutorialClicked = onHomeTutorialClicked,
114 onRecentAppsTutorialClicked = onRecentAppsTutorialClicked,
115 onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked,
116 lastSelectedScreen,
117 modifier = Modifier.weight(1f).padding(padding),
118 )
119 }
120 }
121 // because other composables have weight 1, Done button will be positioned first
122 DoneButton(
123 onDoneButtonClicked = onDoneButtonClicked,
124 modifier = Modifier.padding(start = padding, top = 0.dp, end = padding, bottom = 32.dp),
125 )
126 }
127 }
128
129 @Composable
HorizontalSelectionButtonsnull130 private fun HorizontalSelectionButtons(
131 onBackTutorialClicked: () -> Unit,
132 onHomeTutorialClicked: () -> Unit,
133 onRecentAppsTutorialClicked: () -> Unit,
134 onSwitchAppsTutorialClicked: () -> Unit,
135 lastSelectedScreen: Screen,
136 modifier: Modifier = Modifier,
137 ) {
138 Column(modifier = modifier) {
139 TwoByTwoTutorialButtons(
140 onBackTutorialClicked,
141 onHomeTutorialClicked,
142 onRecentAppsTutorialClicked,
143 onSwitchAppsTutorialClicked,
144 lastSelectedScreen,
145 modifier = Modifier.weight(1f).fillMaxSize(),
146 )
147 }
148 }
149
150 @Composable
TwoByTwoTutorialButtonsnull151 private fun TwoByTwoTutorialButtons(
152 onBackTutorialClicked: () -> Unit,
153 onHomeTutorialClicked: () -> Unit,
154 onRecentAppsTutorialClicked: () -> Unit,
155 onSwitchAppsTutorialClicked: () -> Unit,
156 lastSelectedScreen: Screen,
157 modifier: Modifier = Modifier,
158 ) {
159 val homeFocusRequester = remember { FocusRequester() }
160 val backFocusRequester = remember { FocusRequester() }
161 val recentAppsFocusRequester = remember { FocusRequester() }
162 val switchAppsFocusRequester = remember { FocusRequester() }
163 LaunchedEffect(Unit) {
164 when (lastSelectedScreen) {
165 Screen.HOME_GESTURE -> homeFocusRequester.requestFocus()
166 Screen.BACK_GESTURE -> backFocusRequester.requestFocus()
167 Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus()
168 Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus()
169 else -> {} // No-Op.
170 }
171 }
172 Column {
173 Row(Modifier.weight(1f)) {
174 TutorialButton(
175 text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
176 icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
177 iconColor = MaterialTheme.colorScheme.onPrimary,
178 onClick = onHomeTutorialClicked,
179 backgroundColor = MaterialTheme.colorScheme.primary,
180 modifier = modifier.focusRequester(homeFocusRequester).focusable().fillMaxSize(),
181 )
182 Spacer(modifier = Modifier.size(16.dp))
183 TutorialButton(
184 text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
185 icon = Icons.AutoMirrored.Outlined.ArrowBack,
186 iconColor = MaterialTheme.colorScheme.onTertiary,
187 onClick = onBackTutorialClicked,
188 backgroundColor = MaterialTheme.colorScheme.tertiary,
189 modifier = modifier.focusRequester(backFocusRequester).focusable().fillMaxSize(),
190 )
191 }
192 Spacer(modifier = Modifier.size(16.dp))
193 Row(Modifier.weight(1f)) {
194 TutorialButton(
195 text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
196 icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
197 iconColor = MaterialTheme.colorScheme.onSecondary,
198 onClick = onRecentAppsTutorialClicked,
199 backgroundColor = MaterialTheme.colorScheme.secondary,
200 modifier =
201 modifier.focusRequester(recentAppsFocusRequester).focusable().fillMaxSize(),
202 )
203 Spacer(modifier = Modifier.size(16.dp))
204 TutorialButton(
205 text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button),
206 icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon),
207 iconColor = MaterialTheme.colorScheme.primary,
208 onClick = onSwitchAppsTutorialClicked,
209 backgroundColor = MaterialTheme.colorScheme.onPrimary,
210 modifier =
211 modifier.focusRequester(switchAppsFocusRequester).focusable().fillMaxSize(),
212 )
213 }
214 }
215 }
216
217 @Composable
HorizontalCompactSelectionButtonsnull218 private fun HorizontalCompactSelectionButtons(
219 onBackTutorialClicked: () -> Unit,
220 onHomeTutorialClicked: () -> Unit,
221 onRecentAppsTutorialClicked: () -> Unit,
222 onSwitchAppsTutorialClicked: () -> Unit,
223 lastSelectedScreen: Screen,
224 modifier: Modifier = Modifier,
225 ) {
226 Row(
227 horizontalArrangement = Arrangement.spacedBy(16.dp),
228 verticalAlignment = Alignment.CenterVertically,
229 modifier = modifier,
230 ) {
231 FourTutorialButtons(
232 onBackTutorialClicked,
233 onHomeTutorialClicked,
234 onRecentAppsTutorialClicked,
235 onSwitchAppsTutorialClicked,
236 lastSelectedScreen,
237 modifier = Modifier.weight(1f).fillMaxSize(),
238 )
239 }
240 }
241
242 @Composable
VerticalSelectionButtonsnull243 private fun VerticalSelectionButtons(
244 onBackTutorialClicked: () -> Unit,
245 onHomeTutorialClicked: () -> Unit,
246 onRecentAppsTutorialClicked: () -> Unit,
247 onSwitchAppsTutorialClicked: () -> Unit,
248 lastSelectedScreen: Screen,
249 modifier: Modifier = Modifier,
250 ) {
251 Column(
252 verticalArrangement = Arrangement.spacedBy(16.dp),
253 horizontalAlignment = Alignment.CenterHorizontally,
254 modifier = modifier,
255 ) {
256 FourTutorialButtons(
257 onBackTutorialClicked,
258 onHomeTutorialClicked,
259 onRecentAppsTutorialClicked,
260 onSwitchAppsTutorialClicked,
261 lastSelectedScreen,
262 modifier = Modifier.weight(1f).fillMaxSize(),
263 )
264 }
265 }
266
267 @Composable
FourTutorialButtonsnull268 private fun FourTutorialButtons(
269 onBackTutorialClicked: () -> Unit,
270 onHomeTutorialClicked: () -> Unit,
271 onRecentAppsTutorialClicked: () -> Unit,
272 onSwitchAppsTutorialClicked: () -> Unit,
273 lastSelectedScreen: Screen,
274 modifier: Modifier = Modifier,
275 ) {
276 val homeFocusRequester = remember { FocusRequester() }
277 val backFocusRequester = remember { FocusRequester() }
278 val recentAppsFocusRequester = remember { FocusRequester() }
279 val switchAppsFocusRequester = remember { FocusRequester() }
280 LaunchedEffect(Unit) {
281 when (lastSelectedScreen) {
282 Screen.HOME_GESTURE -> homeFocusRequester.requestFocus()
283 Screen.BACK_GESTURE -> backFocusRequester.requestFocus()
284 Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus()
285 Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus()
286 else -> {} // No-Op.
287 }
288 }
289 TutorialButton(
290 text = stringResource(R.string.touchpad_tutorial_home_gesture_button),
291 icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon),
292 iconColor = MaterialTheme.colorScheme.onPrimary,
293 onClick = onHomeTutorialClicked,
294 backgroundColor = MaterialTheme.colorScheme.primary,
295 modifier = modifier.focusRequester(homeFocusRequester).focusable(),
296 )
297 TutorialButton(
298 text = stringResource(R.string.touchpad_tutorial_back_gesture_button),
299 icon = Icons.AutoMirrored.Outlined.ArrowBack,
300 iconColor = MaterialTheme.colorScheme.onTertiary,
301 onClick = onBackTutorialClicked,
302 backgroundColor = MaterialTheme.colorScheme.tertiary,
303 modifier = modifier.focusRequester(backFocusRequester).focusable(),
304 )
305 TutorialButton(
306 text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button),
307 icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon),
308 iconColor = MaterialTheme.colorScheme.onSecondary,
309 onClick = onRecentAppsTutorialClicked,
310 backgroundColor = MaterialTheme.colorScheme.secondary,
311 modifier = modifier.focusRequester(recentAppsFocusRequester).focusable(),
312 )
313 TutorialButton(
314 text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button),
315 icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon),
316 iconColor = MaterialTheme.colorScheme.primary,
317 onClick = onSwitchAppsTutorialClicked,
318 backgroundColor = MaterialTheme.colorScheme.onPrimary,
319 modifier = modifier.focusRequester(switchAppsFocusRequester).focusable(),
320 )
321 }
322
323 @Composable
TutorialButtonnull324 private fun TutorialButton(
325 text: String,
326 icon: ImageVector,
327 iconColor: Color,
328 onClick: () -> Unit,
329 backgroundColor: Color,
330 modifier: Modifier = Modifier,
331 ) {
332 Button(
333 onClick = onClick,
334 shape = RoundedCornerShape(16.dp),
335 colors = ButtonDefaults.buttonColors(containerColor = backgroundColor),
336 modifier = modifier,
337 ) {
338 Column(
339 verticalArrangement = Arrangement.Center,
340 horizontalAlignment = Alignment.CenterHorizontally,
341 ) {
342 // contentDescription is set to null because the icon is decorative and we don't want to
343 // repeat the text twice
344 Icon(
345 imageVector = icon,
346 contentDescription = null,
347 modifier = Modifier.width(30.dp).height(30.dp),
348 tint = iconColor,
349 )
350 if (!hasCompactWindowSize()) Spacer(modifier = Modifier.height(16.dp))
351 Text(
352 text = text,
353 textAlign = TextAlign.Center,
354 style = MaterialTheme.typography.headlineLarge,
355 color = iconColor,
356 )
357 }
358 }
359 }
360