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 Original line 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.scroll.ScrollCaptureController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotAnimationController
import com.android.systemui.screenshot.ui.ScreenshotShelfView
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.binder.ScreenshotShelfViewBinder
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel
import dagger.assisted.Assisted
import dagger.assisted.Assisted
@@ -75,9 +76,17 @@ constructor(
    override var isPendingSharedTransition = false
    override var isPendingSharedTransition = false


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


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


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

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


        // If we're already animating out, don't restart the animation
        // If we're already animating out, don't restart the animation
@@ -119,7 +132,7 @@ constructor(
            return
            return
        }
        }
        event?.let { logger.log(it, 0, packageName) }
        event?.let { logger.log(it, 0, packageName) }
        val animator = animationController.getExitAnimation()
        val animator = animationController.getSwipeDismissAnimation(velocity)
        animator.addListener(
        animator.addListener(
            object : AnimatorListenerAdapter() {
            object : AnimatorListenerAdapter() {
                override fun onAnimationStart(animator: Animator) {
                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
    @AssistedFactory
    interface Factory : ScreenshotViewProxy.Factory {
    interface Factory : ScreenshotViewProxy.Factory {
        override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
        override fun getProxy(context: Context, displayId: Int): ScreenshotShelfViewProxy
+29 −13
Original line number Original line Diff line number Diff line
@@ -20,9 +20,12 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.animation.ValueAnimator
import android.view.View
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 var animator: Animator? = null
    private val actionContainer = view.requireViewById<View>(R.id.actions_container_background)


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


    fun getExitAnimation(): Animator {
    fun getSwipeReturnAnimation(): Animator {
        val animator = ValueAnimator.ofFloat(1f, 0f)
        animator?.cancel()
        animator.addUpdateListener { view.alpha = it.animatedValue as Float }
        val animator = ValueAnimator.ofFloat(view.translationX, 0f)
        animator.addListener(
        animator.addUpdateListener { view.translationX = it.animatedValue as Float }
            object : AnimatorListenerAdapter() {
        this.animator = animator
                override fun onAnimationStart(animator: Animator) {
        return animator
                    view.alpha = 1f
    }
    }
                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
        this.animator = animator
        return animator
        return animator
    }
    }
+9 −0
Original line number Original line Diff line number Diff line
@@ -21,6 +21,7 @@ import android.graphics.Insets
import android.graphics.Rect
import android.graphics.Rect
import android.graphics.Region
import android.graphics.Region
import android.util.AttributeSet
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.View
import android.widget.ImageView
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintLayout
@@ -30,6 +31,7 @@ import com.android.systemui.screenshot.FloatingWindowUtil
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
    ConstraintLayout(context, attrs) {
    ConstraintLayout(context, attrs) {
    lateinit var screenshotPreview: ImageView
    lateinit var screenshotPreview: ImageView
    var onTouchInterceptListener: ((MotionEvent) -> Boolean)? = null


    private val displayMetrics = context.resources.displayMetrics
    private val displayMetrics = context.resources.displayMetrics
    private val tmpRect = Rect()
    private val tmpRect = Rect()
@@ -83,4 +85,11 @@ class ScreenshotShelfView(context: Context, attrs: AttributeSet? = null) :
    companion object {
    companion object {
        private const val TOUCH_PADDING_DP = 12f
        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 Original line 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 Original line Diff line number Diff line
@@ -17,8 +17,8 @@
package com.android.systemui.screenshot.ui.binder
package com.android.systemui.screenshot.ui.binder


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


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

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