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

Commit 3b282f6a authored by Michal Brzezinski's avatar Michal Brzezinski
Browse files

Introducing GestureUiState with extra animation info

We need more info in GestureState that is UI-specific so instead of adding it there, let's add extra layer between ActionState (state for touchpad + keyboard tutorial) and GestureState (formerly state for touchpad tutorial).
Because we need to do do different mapping for each touchpad gesture, introduced GestureFlowAdapter that wraps callback code into flow - this makes it less repetitive (and error prone) and we can subscribe it to it easily from Compose code.

Using conflatedCallbackFlow enforces using awaitClose() so also added new method to clear callback - it shouldn't be needed because we subscribe only when creating new GestureRecognizer but just to make it more explicit there's no memory leak.

Bug: 369817369
Test: small refactoring, tests pass
Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial
Change-Id: I0e9536af9a0fe475807721838383ad5cbe3912a3
parent 81c6cdc7
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -27,7 +27,10 @@ import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenCon
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.BackGestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

@Composable
fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -48,7 +51,17 @@ fun BackGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Uni
                ),
        )
    val recognizer = rememberBackGestureRecognizer(LocalContext.current.resources)
    GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
    val gestureUiState: Flow<GestureUiState> =
        remember(recognizer) {
            GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
                it.toGestureUiState(
                    progressStartMark = "",
                    progressEndMark = "",
                    successAnimation = R.raw.trackpad_back_success,
                )
            }
        }
    GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
}

@Composable
+37 −12
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.touchpad.tutorial.ui.composable

import androidx.activity.compose.BackHandler
import androidx.annotation.RawRes
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
@@ -31,23 +32,49 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.EasterEggGestureMonitor
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler
import kotlinx.coroutines.flow.Flow

fun GestureState.toTutorialActionState(): TutorialActionState {
sealed interface GestureUiState {
    data object NotStarted : GestureUiState

    data class Finished(@RawRes val successAnimation: Int) : GestureUiState

    data class InProgress(
        val progress: Float = 0f,
        val progressStartMark: String = "",
        val progressEndMark: String = "",
    ) : GestureUiState
}

fun GestureState.toGestureUiState(
    progressStartMark: String,
    progressEndMark: String,
    successAnimation: Int,
): GestureUiState {
    return when (this) {
        GestureState.NotStarted -> NotStarted
        is GestureState.InProgress ->
            GestureUiState.InProgress(this.progress, progressStartMark, progressEndMark)
        is GestureState.Finished -> GestureUiState.Finished(successAnimation)
    }
}

fun GestureUiState.toTutorialActionState(): TutorialActionState {
    return when (this) {
        NotStarted -> TutorialActionState.NotStarted
        // progress is disabled for now as views are not ready to handle varying progress
        is InProgress -> TutorialActionState.InProgress(0f)
        Finished -> TutorialActionState.Finished
        is GestureUiState.InProgress -> TutorialActionState.InProgress(progress = 0f)
        is Finished -> TutorialActionState.Finished
    }
}

@@ -55,15 +82,13 @@ fun GestureState.toTutorialActionState(): TutorialActionState {
fun GestureTutorialScreen(
    screenConfig: TutorialScreenConfig,
    gestureRecognizer: GestureRecognizer,
    gestureUiStateFlow: Flow<GestureUiState>,
    onDoneButtonClicked: () -> Unit,
    onBack: () -> Unit,
) {
    BackHandler(onBack = onBack)
    var gestureState: GestureState by remember { mutableStateOf(NotStarted) }
    var easterEggTriggered by remember { mutableStateOf(false) }
    LaunchedEffect(gestureRecognizer) {
        gestureRecognizer.addGestureStateCallback { gestureState = it }
    }
    val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted)
    val easterEggMonitor = EasterEggGestureMonitor { easterEggTriggered = true }
    val gestureHandler =
        remember(gestureRecognizer) { TouchpadGestureHandler(gestureRecognizer, easterEggMonitor) }
@@ -84,7 +109,7 @@ fun GestureTutorialScreen(
@Composable
private fun TouchpadGesturesHandlingBox(
    gestureHandler: TouchpadGestureHandler,
    gestureState: GestureState,
    gestureState: GestureUiState,
    easterEggTriggered: Boolean,
    resetEasterEggFlag: () -> Unit,
    modifier: Modifier = Modifier,
@@ -110,7 +135,7 @@ private fun TouchpadGesturesHandlingBox(
                .pointerInteropFilter(
                    onTouchEvent = { event ->
                        // FINISHED is the final state so we don't need to process touches anymore
                        if (gestureState == Finished) {
                        if (gestureState is Finished) {
                            false
                        } else {
                            gestureHandler.onMotionEvent(event)
+14 −1
Original line number Diff line number Diff line
@@ -25,8 +25,11 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.HomeGestureRecognizer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

@Composable
fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -47,7 +50,17 @@ fun HomeGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Uni
                ),
        )
    val recognizer = rememberHomeGestureRecognizer(LocalContext.current.resources)
    GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
    val gestureUiState: Flow<GestureUiState> =
        remember(recognizer) {
            GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
                it.toGestureUiState(
                    progressStartMark = "",
                    progressEndMark = "",
                    successAnimation = R.raw.trackpad_home_success,
                )
            }
        }
    GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
}

@Composable
+14 −1
Original line number Diff line number Diff line
@@ -25,8 +25,11 @@ import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
import com.android.systemui.inputdevice.tutorial.ui.composable.rememberColorFilterProperty
import com.android.systemui.res.R
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureFlowAdapter
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureRecognizer
import com.android.systemui.touchpad.tutorial.ui.gesture.RecentAppsGestureRecognizer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

@Composable
fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
@@ -47,7 +50,17 @@ fun RecentAppsGestureTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: ()
                ),
        )
    val recognizer = rememberRecentAppsGestureRecognizer(LocalContext.current.resources)
    GestureTutorialScreen(screenConfig, recognizer, onDoneButtonClicked, onBack)
    val gestureUiState: Flow<GestureUiState> =
        remember(recognizer) {
            GestureFlowAdapter(recognizer).gestureStateAsFlow.map {
                it.toGestureUiState(
                    progressStartMark = "",
                    progressEndMark = "",
                    successAnimation = R.raw.trackpad_recent_apps_success,
                )
            }
        }
    GestureTutorialScreen(screenConfig, recognizer, gestureUiState, onDoneButtonClicked, onBack)
}

@Composable
+4 −0
Original line number Diff line number Diff line
@@ -33,6 +33,10 @@ class BackGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu
        gestureStateChangedCallback = callback
    }

    override fun clearGestureStateCallback() {
        gestureStateChangedCallback = {}
    }

    override fun accept(event: MotionEvent) {
        if (!isThreeFingerTouchpadSwipe(event)) return
        val gestureState = distanceTracker.processEvent(event)
Loading