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

Commit 13b98804 authored by Michał Brzeziński's avatar Michał Brzeziński Committed by Android (Google) Code Review
Browse files

Merge "Handling case when touchpad gesture is in progress" into main

parents 431f916c 59190047
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