• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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