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

Commit 6d256929 authored by Miranda Kephart's avatar Miranda Kephart Committed by Android (Google) Code Review
Browse files

Merge "Add swipe dismissal for screenshot shelf view" into main

parents 7b05434d 385976a3
Loading
Loading
Loading
Loading
+21 −2
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHE
import com.android.systemui.screenshot.scroll.ScrollCaptureController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.SwipeGestureListener
import com.android.systemui.screenshot.ui.binder.ScreenshotShelfViewBinder
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
@@ -75,9 +76,17 @@ constructor(
    override var isPendingSharedTransition = false

    private val animationController = ScreenshotAnimationController(view)
    private val swipeGestureListener =
        SwipeGestureListener(
            view,
            onDismiss = { requestDismissal(ScreenshotEvent.SCREENSHOT_SWIPE_DISMISSED, it) },
            onCancel = { animationController.getSwipeReturnAnimation().start() }
        )

    init {
        ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context))
        ScreenshotShelfViewBinder.bind(view, viewModel, LayoutInflater.from(context)) {
            swipeGestureListener.onMotionEvent(it)
        }
        addPredictiveBackListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
        setOnKeyListener { requestDismissal(SCREENSHOT_DISMISSED_OTHER) }
        debugLog(DEBUG_WINDOW) { "adding OnComputeInternalInsetsListener" }
@@ -111,6 +120,10 @@ constructor(
    override fun setChipIntents(imageData: SavedImageData) {}

    override fun requestDismissal(event: ScreenshotEvent?) {
        requestDismissal(event, getDismissalVelocity())
    }

    private fun requestDismissal(event: ScreenshotEvent?, velocity: Float) {
        debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" }

        // If we're already animating out, don't restart the animation
@@ -119,7 +132,7 @@ constructor(
            return
        }
        event?.let { logger.log(it, 0, packageName) }
        val animator = animationController.getExitAnimation()
        val animator = animationController.getSwipeDismissAnimation(velocity)
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
@@ -222,6 +235,12 @@ constructor(
        )
    }

    private fun getDismissalVelocity(): Float {
        val isLTR = view.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
        // dismiss to the left in LTR locales, to the right in RTL
        return if (isLTR) -1.5f else 1.5f
    }

    @AssistedFactory
    interface Factory : ScreenshotViewProxy.Factory {
        override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
+29 −13
Original line number Diff line number Diff line
@@ -20,9 +20,12 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.view.View
import com.android.systemui.res.R
import kotlin.math.abs

class ScreenshotAnimationController(private val view: View) {
class ScreenshotAnimationController(private val view: ScreenshotShelfView) {
    private var animator: Animator? = null
    private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)

    fun getEntranceAnimation(): Animator {
        val animator = ValueAnimator.ofFloat(0f, 1f)
@@ -41,19 +44,32 @@ class ScreenshotAnimationController(private val view: View) {
        return animator
    }

    fun getExitAnimation(): Animator {
        val animator = ValueAnimator.ofFloat(1f, 0f)
        animator.addUpdateListener { view.alpha = it.animatedValue as Float }
        animator.addListener(
            object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
                    view.alpha = 1f
    fun getSwipeReturnAnimation(): Animator {
        animator?.cancel()
        val animator = ValueAnimator.ofFloat(view.translationX, 0f)
        animator.addUpdateListener { view.translationX = it.animatedValue as Float }
        this.animator = animator
        return animator
    }
                override fun onAnimationEnd(animator: Animator) {
                    view.alpha = 0f

    fun getSwipeDismissAnimation(velocity: Float): Animator {
        val screenWidth = view.resources.displayMetrics.widthPixels
        // translation at which point the visible UI is fully off the screen (in the direction
        // according to velocity)
        val endX =
            if (velocity < 0) {
                -1f * actionContainer.right
            } else {
                (screenWidth - actionContainer.left).toFloat()
            }
        val distance = endX - view.translationX
        val animator = ValueAnimator.ofFloat(view.translationX, endX)
        animator.addUpdateListener {
            view.translationX = it.animatedValue as Float
            view.alpha = 1f - it.animatedFraction
        }
        )
        animator.duration = ((abs(distance / velocity))).toLong()

        this.animator = animator
        return animator
    }
+9 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.graphics.Insets
import android.graphics.Rect
import android.graphics.Region
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
@@ -30,6 +31,7 @@ import com.android.systemui.screenshot.FloatingWindowUtil
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
    ConstraintLayout(context, attrs) {
    lateinit var screenshotPreview: ImageView
    var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null

    private val displayMetrics = context.resources.displayMetrics
    private val tmpRect = Rect()
@@ -83,4 +85,11 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
    companion object {
        private const val TOUCH_PADDING_DP = 12f
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        if (onTouchInterceptListener?.invoke(ev) == true) {
            return true
        }
        return super.onInterceptTouchEvent(ev)
    }
}
+74 −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.screenshot.ui

import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import com.android.systemui.screenshot.FloatingWindowUtil
import kotlin.math.abs
import kotlin.math.sign

class SwipeGestureListener(
    private val view: View,
    private val onDismiss: (Float) -> Unit,
    private val onCancel: () -> Unit
) {
    private val velocityTracker = VelocityTracker.obtain()
    private val displayMetrics = view.resources.displayMetrics

    private var startX = 0f

    fun onMotionEvent(ev: MotionEvent): Boolean {
        ev.offsetLocation(view.translationX, 0f)
        when (ev.actionMasked) {
            MotionEvent.ACTION_DOWN -> {
                velocityTracker.addMovement(ev)
                startX = ev.rawX
            }
            MotionEvent.ACTION_UP -> {
                velocityTracker.computeCurrentVelocity(1)
                val xVelocity = velocityTracker.xVelocity
                if (
                    abs(xVelocity) > FloatingWindowUtil.dpToPx(displayMetrics, FLING_THRESHOLD_DP)
                ) {
                    onDismiss.invoke(xVelocity)
                    return true
                } else if (
                    abs(view.translationX) >
                        FloatingWindowUtil.dpToPx(displayMetrics, DISMISS_THRESHOLD_DP)
                ) {
                    onDismiss.invoke(1.5f * sign(view.translationX))
                    return true
                } else {
                    velocityTracker.clear()
                    onCancel.invoke()
                }
            }
            MotionEvent.ACTION_MOVE -> {
                velocityTracker.addMovement(ev)
                view.translationX = ev.rawX - startX
            }
        }
        return false
    }

    companion object {
        private const val DISMISS_THRESHOLD_DP = 80f
        private const val FLING_THRESHOLD_DP = .8f // dp per ms
    }
}
+6 −2
Original line number Diff line number Diff line
@@ -17,8 +17,8 @@
package com.android.systemui.screenshot.ui.binder

import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.lifecycle.Lifecycle
@@ -26,16 +26,20 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import com.android.systemui.screenshot.ui.ScreenshotShelfView
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.util.children
import kotlinx.coroutines.launch

object ScreenshotShelfViewBinder {
    fun bind(
        view: ViewGroup,
        view: ScreenshotShelfView,
        viewModel: ScreenshotViewModel,
        layoutInflater: LayoutInflater,
        onTouchListener: (MotionEvent) -> Boolean,
    ) {
        view.onTouchInterceptListener = onTouchListener

        val previewView: ImageView = view.requireViewById(R.id.screenshot_preview)
        val previewBorder = view.requireViewById<View>(R.id.screenshot_preview_border)
        previewView.clipToOutline = true