Support a swipe down to dismiss video (#2879)
* Support a swipe down to dismiss video Images can already be dismissed with a swipe, this adds the same functionality to videos. - Add a VideoActionsListener interface for the hosting activity to dismiss the fragment - Add a gesture listener for swipes - Dismiss the fragment if a swipe has a greated Y component than X Fixes https://github.com/tuskyapp/Tusky/issues/2833 * Scale the video view when dragging Provides identical visual feedback to the same operation on images.
This commit is contained in:
parent
4de778d7d4
commit
64a06bfbe2
|
@ -52,6 +52,7 @@ import com.keylesspalace.tusky.components.viewthread.ViewThreadActivity
|
||||||
import com.keylesspalace.tusky.databinding.ActivityViewMediaBinding
|
import com.keylesspalace.tusky.databinding.ActivityViewMediaBinding
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.fragment.ViewImageFragment
|
import com.keylesspalace.tusky.fragment.ViewImageFragment
|
||||||
|
import com.keylesspalace.tusky.fragment.ViewVideoFragment
|
||||||
import com.keylesspalace.tusky.pager.ImagePagerAdapter
|
import com.keylesspalace.tusky.pager.ImagePagerAdapter
|
||||||
import com.keylesspalace.tusky.pager.SingleImagePagerAdapter
|
import com.keylesspalace.tusky.pager.SingleImagePagerAdapter
|
||||||
import com.keylesspalace.tusky.util.getTemporaryMediaFilename
|
import com.keylesspalace.tusky.util.getTemporaryMediaFilename
|
||||||
|
@ -68,7 +69,7 @@ import java.util.Locale
|
||||||
|
|
||||||
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
typealias ToolbarVisibilityListener = (isVisible: Boolean) -> Unit
|
||||||
|
|
||||||
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener {
|
class ViewMediaActivity : BaseActivity(), ViewImageFragment.PhotoActionsListener, ViewVideoFragment.VideoActionsListener {
|
||||||
|
|
||||||
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
|
private val binding by viewBinding(ActivityViewMediaBinding::inflate)
|
||||||
|
|
||||||
|
|
|
@ -18,27 +18,36 @@ package com.keylesspalace.tusky.fragment
|
||||||
import android.animation.Animator
|
import android.animation.Animator
|
||||||
import android.animation.AnimatorListenerAdapter
|
import android.animation.AnimatorListenerAdapter
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.text.method.ScrollingMovementMethod
|
import android.text.method.ScrollingMovementMethod
|
||||||
|
import android.view.GestureDetector
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
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.view.ViewGroup
|
||||||
import android.widget.MediaController
|
import android.widget.MediaController
|
||||||
|
import androidx.core.view.GestureDetectorCompat
|
||||||
import com.keylesspalace.tusky.ViewMediaActivity
|
import com.keylesspalace.tusky.ViewMediaActivity
|
||||||
import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding
|
import com.keylesspalace.tusky.databinding.FragmentViewVideoBinding
|
||||||
import com.keylesspalace.tusky.entity.Attachment
|
import com.keylesspalace.tusky.entity.Attachment
|
||||||
import com.keylesspalace.tusky.util.hide
|
import com.keylesspalace.tusky.util.hide
|
||||||
import com.keylesspalace.tusky.util.visible
|
import com.keylesspalace.tusky.util.visible
|
||||||
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
import com.keylesspalace.tusky.view.ExposedPlayPauseVideoView
|
||||||
|
import kotlin.math.abs
|
||||||
|
|
||||||
class ViewVideoFragment : ViewMediaFragment() {
|
class ViewVideoFragment : ViewMediaFragment() {
|
||||||
|
interface VideoActionsListener {
|
||||||
|
fun onDismiss()
|
||||||
|
}
|
||||||
|
|
||||||
private var _binding: FragmentViewVideoBinding? = null
|
private var _binding: FragmentViewVideoBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private lateinit var videoActionsListener: VideoActionsListener
|
||||||
private lateinit var toolbar: View
|
private lateinit var toolbar: View
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
private val hideToolbar = Runnable {
|
private val hideToolbar = Runnable {
|
||||||
|
@ -52,6 +61,11 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
private lateinit var mediaController: MediaController
|
private lateinit var mediaController: MediaController
|
||||||
private var isAudio = false
|
private var isAudio = false
|
||||||
|
|
||||||
|
override fun onAttach(context: Context) {
|
||||||
|
super.onAttach(context)
|
||||||
|
videoActionsListener = context as VideoActionsListener
|
||||||
|
}
|
||||||
|
|
||||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||||
// Start/pause/resume video playback as fragment is shown/hidden
|
// Start/pause/resume video playback as fragment is shown/hidden
|
||||||
super.setUserVisibleHint(isVisibleToUser)
|
super.setUserVisibleHint(isVisibleToUser)
|
||||||
|
@ -168,6 +182,7 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT)
|
val attachment = arguments?.getParcelable<Attachment>(ARG_ATTACHMENT)
|
||||||
|
@ -177,6 +192,54 @@ class ViewVideoFragment : ViewMediaFragment() {
|
||||||
}
|
}
|
||||||
val url = attachment.url
|
val url = attachment.url
|
||||||
isAudio = attachment.type == Attachment.Type.AUDIO
|
isAudio = attachment.type == Attachment.Type.AUDIO
|
||||||
|
|
||||||
|
val gestureDetector = GestureDetectorCompat(
|
||||||
|
requireContext(),
|
||||||
|
object : GestureDetector.SimpleOnGestureListener() {
|
||||||
|
override fun onDown(event: MotionEvent): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFling(
|
||||||
|
e1: MotionEvent,
|
||||||
|
e2: MotionEvent,
|
||||||
|
velocityX: Float,
|
||||||
|
velocityY: Float
|
||||||
|
): Boolean {
|
||||||
|
if (abs(velocityY) > abs(velocityX)) {
|
||||||
|
videoActionsListener.onDismiss()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var lastY = 0f
|
||||||
|
binding.root.setOnTouchListener { _, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
lastY = event.rawY
|
||||||
|
} else if (event.pointerCount == 1 && event.action == MotionEvent.ACTION_MOVE) {
|
||||||
|
val diff = event.rawY - lastY
|
||||||
|
if (binding.videoView.translationY != 0f || abs(diff) > 40) {
|
||||||
|
binding.videoView.translationY += diff
|
||||||
|
val scale = (-abs(binding.videoView.translationY) / 720 + 1).coerceAtLeast(0.5f)
|
||||||
|
binding.videoView.scaleY = scale
|
||||||
|
binding.videoView.scaleX = scale
|
||||||
|
lastY = event.rawY
|
||||||
|
return@setOnTouchListener true
|
||||||
|
}
|
||||||
|
} else if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {
|
||||||
|
if (abs(binding.videoView.translationY) > 180) {
|
||||||
|
videoActionsListener.onDismiss()
|
||||||
|
} else {
|
||||||
|
binding.videoView.animate().translationY(0f).scaleX(1f).scaleY(1f).start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gestureDetector.onTouchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
finalizeViewSetup(url, attachment.previewUrl, attachment.description)
|
finalizeViewSetup(url, attachment.previewUrl, attachment.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue