• 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.view.MotionEvent
20 import androidx.activity.compose.BackHandler
21 import androidx.compose.animation.core.Animatable
22 import androidx.compose.animation.core.tween
23 import androidx.compose.foundation.layout.Box
24 import androidx.compose.foundation.layout.BoxScope
25 import androidx.compose.foundation.layout.fillMaxSize
26 import androidx.compose.runtime.Composable
27 import androidx.compose.runtime.LaunchedEffect
28 import androidx.compose.runtime.getValue
29 import androidx.compose.runtime.mutableStateOf
30 import androidx.compose.runtime.remember
31 import androidx.compose.runtime.saveable.rememberSaveable
32 import androidx.compose.runtime.setValue
33 import androidx.compose.ui.Modifier
34 import androidx.compose.ui.graphics.graphicsLayer
35 import androidx.compose.ui.input.pointer.pointerInteropFilter
36 import androidx.lifecycle.compose.collectAsStateWithLifecycle
37 import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
38 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
39 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
40 import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
41 import kotlinx.coroutines.flow.Flow
42 
43 @Composable
44 fun GestureTutorialScreen(
45     screenConfig: TutorialScreenConfig,
46     tutorialStateFlow: Flow<TutorialActionState>,
47     motionEventConsumer: (MotionEvent) -> Boolean,
48     easterEggTriggeredFlow: Flow<Boolean>,
49     onEasterEggFinished: () -> Unit,
50     onDoneButtonClicked: () -> Unit,
51     onBack: () -> Unit,
52     onAutoProceed: (suspend () -> Unit)? = null,
53 ) {
54     BackHandler(onBack = onBack)
55     var cachedTutorialState: TutorialActionState by
56         rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
57             mutableStateOf(NotStarted)
58         }
59     val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
60     val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState)
61     cachedTutorialState = tutorialState
62     TouchpadGesturesHandlingBox(
63         motionEventConsumer,
64         tutorialState,
65         easterEggTriggered,
66         onEasterEggFinished,
67     ) {
68         ActionTutorialContent(tutorialState, onDoneButtonClicked, screenConfig, onAutoProceed)
69     }
70 }
71 
72 @Composable
TouchpadGesturesHandlingBoxnull73 private fun TouchpadGesturesHandlingBox(
74     motionEventConsumer: (MotionEvent) -> Boolean,
75     tutorialState: TutorialActionState,
76     easterEggTriggered: Boolean,
77     onEasterEggFinished: () -> Unit,
78     modifier: Modifier = Modifier,
79     content: @Composable BoxScope.() -> Unit,
80 ) {
81     val rotationAnimation = remember { Animatable(0f) }
82     LaunchedEffect(easterEggTriggered) {
83         if (easterEggTriggered || rotationAnimation.isRunning) {
84             rotationAnimation.snapTo(0f)
85             rotationAnimation.animateTo(
86                 targetValue = 360f,
87                 animationSpec = tween(durationMillis = 2000),
88             )
89             onEasterEggFinished()
90         }
91     }
92     Box(
93         modifier =
94             modifier
95                 .fillMaxSize()
96                 // we need to use pointerInteropFilter because some info about touchpad gestures is
97                 // only available in MotionEvent
98                 .pointerInteropFilter(
99                     onTouchEvent = { event ->
100                         // FINISHED is the final state so we don't need to process touches anymore
101                         if (tutorialState is TutorialActionState.Finished) {
102                             false
103                         } else {
104                             motionEventConsumer(event)
105                         }
106                     }
107                 )
108                 .graphicsLayer { rotationZ = rotationAnimation.value }
109     ) {
110         content()
111     }
112 }
113