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

Commit 59190047 authored by Michal Brzezinski's avatar Michal Brzezinski
Browse files

Handling case when touchpad gesture is in progress

Introducing new GestureState that adds IN_PROGRESS.
While gesture is in progress, original education animation should jumpcut to 1st frame and stay there (freeze).

Bug: 346579074
Test: Open back gesture screen, start doing gesture and see animation freezing
Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial
Change-Id: I5bc62e7050bd53524f289e5c114590d7bbbc97e9
parent 1df8fc9f
Loading
Loading
Loading
Loading
+59 −17
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -49,6 +50,7 @@ import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
@@ -61,6 +63,9 @@ import com.airbnb.lottie.compose.rememberLottieDynamicProperties
import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.res.R
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.IN_PROGRESS
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGesture.BACK
import com.android.systemui.touchpad.tutorial.ui.gesture.TouchpadGestureHandler

@@ -78,23 +83,49 @@ fun BackGestureTutorialScreen(
) {
    val screenColors = rememberScreenColors()
    BackHandler(onBack = onBack)
    var gestureDone by remember { mutableStateOf(false) }
    var gestureState by remember { mutableStateOf(GestureState.NOT_STARTED) }
    val swipeDistanceThresholdPx =
        LocalContext.current.resources.getDimensionPixelSize(
            com.android.internal.R.dimen.system_gestures_distance_threshold
        )
    val gestureHandler =
        remember(swipeDistanceThresholdPx) {
            TouchpadGestureHandler(BACK, swipeDistanceThresholdPx, onDone = { gestureDone = true })
            TouchpadGestureHandler(
                BACK,
                swipeDistanceThresholdPx,
                onGestureStateChanged = { gestureState = it }
            )
        }
    TouchpadGesturesHandlingBox(gestureHandler, gestureState) {
        GestureTutorialContent(gestureState, onDoneButtonClicked, screenColors)
    }
}

@Composable
private fun TouchpadGesturesHandlingBox(
    gestureHandler: TouchpadGestureHandler,
    gestureState: GestureState,
    modifier: Modifier = Modifier,
    content: @Composable BoxScope.() -> Unit
) {
    Box(
        modifier =
            Modifier.fillMaxSize()
            modifier
                .fillMaxSize()
                // we need to use pointerInteropFilter because some info about touchpad gestures is
                // only available in MotionEvent
                .pointerInteropFilter(onTouchEvent = gestureHandler::onMotionEvent)
                .pointerInteropFilter(
                    onTouchEvent = { event ->
                        // FINISHED is the final state so we don't need to process touches anymore
                        if (gestureState != FINISHED) {
                            gestureHandler.onMotionEvent(event)
                        } else {
                            false
                        }
                    }
                )
    ) {
        GestureTutorialContent(gestureDone, onDoneButtonClicked, screenColors)
        content()
    }
}

@@ -126,14 +157,14 @@ private fun rememberScreenColors(): TutorialScreenColors {

@Composable
private fun GestureTutorialContent(
    gestureDone: Boolean,
    gestureState: GestureState,
    onDoneButtonClicked: () -> Unit,
    screenColors: TutorialScreenColors
) {
    val animatedColor by
        animateColorAsState(
            targetValue =
                if (gestureDone) screenColors.successBackgroundColor
                if (gestureState == FINISHED) screenColors.successBackgroundColor
                else screenColors.backgroundColor,
            animationSpec = tween(durationMillis = 150, easing = LinearEasing),
            label = "backgroundColor"
@@ -148,7 +179,7 @@ private fun GestureTutorialContent(
        Row(modifier = Modifier.fillMaxWidth().weight(1f)) {
            TutorialDescription(
                titleTextId =
                    if (gestureDone) R.string.touchpad_tutorial_gesture_done
                    if (gestureState == FINISHED) R.string.touchpad_tutorial_gesture_done
                    else R.string.touchpad_back_gesture_action_title,
                titleColor = screenColors.titleColor,
                bodyTextId = R.string.touchpad_back_gesture_guidance,
@@ -156,7 +187,7 @@ private fun GestureTutorialContent(
            )
            Spacer(modifier = Modifier.width(76.dp))
            TutorialAnimation(
                gestureDone,
                gestureState,
                screenColors.animationProperties,
                modifier = Modifier.weight(1f).padding(top = 8.dp)
            )
@@ -189,26 +220,37 @@ fun TutorialDescription(

@Composable
fun TutorialAnimation(
    gestureDone: Boolean,
    gestureState: GestureState,
    animationProperties: LottieDynamicProperties,
    modifier: Modifier = Modifier
) {
    Column(modifier = modifier.fillMaxWidth()) {
        val resId = if (gestureDone) R.raw.trackpad_back_success else R.raw.trackpad_back_edu
        val resId =
            if (gestureState == FINISHED) R.raw.trackpad_back_success else R.raw.trackpad_back_edu
        val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(resId))
        val progress by
            animateLottieCompositionAsState(
                composition,
                iterations = if (gestureDone) 1 else LottieConstants.IterateForever
            )
        val progress = progressForGestureState(composition, gestureState)
        LottieAnimation(
            composition = composition,
            progress = { progress },
            progress = progress,
            dynamicProperties = animationProperties
        )
    }
}

@Composable
private fun progressForGestureState(
    composition: LottieComposition?,
    gestureState: GestureState
): () -> Float {
    if (gestureState == IN_PROGRESS) {
        return { 0f } // when gesture is in progress, animation should freeze on 1st frame
    } else {
        val iterations = if (gestureState == FINISHED) 1 else LottieConstants.IterateForever
        val animationState by animateLottieCompositionAsState(composition, iterations = iterations)
        return { animationState }
    }
}

@Composable
fun rememberColorFilterProperty(
    layerName: String,
+11 −5
Original line number Diff line number Diff line
@@ -17,23 +17,26 @@
package com.android.systemui.touchpad.tutorial.ui.gesture

import android.view.MotionEvent
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.FINISHED
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.IN_PROGRESS
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NOT_STARTED
import kotlin.math.abs

/**
 * Monitor for touchpad gestures that calls [gestureDoneCallback] when gesture was successfully
 * done. All tracked motion events should be passed to [processTouchpadEvent]
 * Monitor for touchpad gestures that calls [gestureStateChangedCallback] when [GestureState]
 * changes. All tracked motion events should be passed to [processTouchpadEvent]
 */
interface TouchpadGestureMonitor {

    val gestureDistanceThresholdPx: Int
    val gestureDoneCallback: () -> Unit
    val gestureStateChangedCallback: (GestureState) -> Unit

    fun processTouchpadEvent(event: MotionEvent)
}

class BackGestureMonitor(
    override val gestureDistanceThresholdPx: Int,
    override val gestureDoneCallback: () -> Unit
    override val gestureStateChangedCallback: (GestureState) -> Unit
) : TouchpadGestureMonitor {

    private var xStart = 0f
@@ -44,13 +47,16 @@ class BackGestureMonitor(
            MotionEvent.ACTION_DOWN -> {
                if (isThreeFingerTouchpadSwipe(event)) {
                    xStart = event.x
                    gestureStateChangedCallback(IN_PROGRESS)
                }
            }
            MotionEvent.ACTION_UP -> {
                if (isThreeFingerTouchpadSwipe(event)) {
                    val distance = abs(event.x - xStart)
                    if (distance >= gestureDistanceThresholdPx) {
                        gestureDoneCallback()
                        gestureStateChangedCallback(FINISHED)
                    } else {
                        gestureStateChangedCallback(NOT_STARTED)
                    }
                }
            }
+23 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.touchpad.tutorial.ui.gesture

enum class GestureState {
    NOT_STARTED,
    IN_PROGRESS,
    FINISHED
}
+2 −2
Original line number Diff line number Diff line
@@ -22,10 +22,10 @@ enum class TouchpadGesture {

    fun toMonitor(
        swipeDistanceThresholdPx: Int,
        gestureDoneCallback: () -> Unit
        onStateChanged: (GestureState) -> Unit
    ): TouchpadGestureMonitor {
        return when (this) {
            BACK -> BackGestureMonitor(swipeDistanceThresholdPx, gestureDoneCallback)
            BACK -> BackGestureMonitor(swipeDistanceThresholdPx, onStateChanged)
            else -> throw IllegalArgumentException("Not implemented yet")
        }
    }
+2 −2
Original line number Diff line number Diff line
@@ -26,11 +26,11 @@ import android.view.MotionEvent
class TouchpadGestureHandler(
    touchpadGesture: TouchpadGesture,
    swipeDistanceThresholdPx: Int,
    onDone: () -> Unit
    onGestureStateChanged: (GestureState) -> Unit
) {

    private val gestureRecognition =
        touchpadGesture.toMonitor(swipeDistanceThresholdPx, gestureDoneCallback = onDone)
        touchpadGesture.toMonitor(swipeDistanceThresholdPx, onStateChanged = onGestureStateChanged)

    fun onMotionEvent(event: MotionEvent): Boolean {
        // events from touchpad have SOURCE_MOUSE and not SOURCE_TOUCHPAD because of legacy reasons
Loading