Signal-Android/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewPlayerControlVi...

191 wiersze
6.1 KiB
Kotlin
Czysty Zwykły widok Historia

2022-10-25 19:22:38 +00:00
package org.thoughtcrime.securesms.mediapreview
import android.animation.Animator
import android.animation.Animator.AnimatorListener
import android.annotation.SuppressLint
2022-10-25 19:22:38 +00:00
import android.content.Context
import android.os.Build
2022-10-25 19:22:38 +00:00
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.PathInterpolator
2022-10-25 19:22:38 +00:00
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat
2022-10-25 19:22:38 +00:00
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
2022-10-25 19:22:38 +00:00
import com.google.android.exoplayer2.ui.PlayerControlView
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.util.MediaUtil
2022-11-17 20:33:15 +00:00
import org.thoughtcrime.securesms.util.visible
import kotlin.time.DurationUnit
import kotlin.time.toDuration
2022-10-25 19:22:38 +00:00
/**
* The bottom bar for the media preview. This includes the standard seek bar as well as playback controls,
* but adds forward and share buttons as well as a recyclerview that can be populated with a rail of thumbnails.
*/
class MediaPreviewPlayerControlView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
playbackAttrs: AttributeSet? = null
) : PlayerControlView(context, attrs, defStyleAttr, playbackAttrs) {
val recyclerView: RecyclerView = findViewById(R.id.media_preview_album_rail)
private val durationBar: LinearLayout = findViewById(R.id.exo_duration_viewgroup)
private val videoControls: LinearLayout = findViewById(R.id.exo_button_viewgroup)
private val durationLabel: TextView = findViewById(R.id.exo_duration)
2022-10-25 19:22:38 +00:00
private val shareButton: ImageButton = findViewById(R.id.exo_share)
private val forwardButton: ImageButton = findViewById(R.id.exo_forward)
enum class MediaMode {
IMAGE, VIDEO;
companion object {
@JvmStatic
fun fromString(contentType: String): MediaMode {
if (MediaUtil.isVideo(contentType)) return VIDEO
if (MediaUtil.isImageType(contentType)) return IMAGE
throw IllegalArgumentException("Unknown content type: $contentType")
}
}
}
init {
setShowPreviousButton(false)
setShowNextButton(false)
showShuffleButton = false
showVrButton = false
showTimeoutMs = -1
}
@SuppressLint("SetTextI18n")
fun setMediaMode(mediaMode: MediaMode) {
2022-11-17 20:33:15 +00:00
durationBar.visible = mediaMode == MediaMode.VIDEO
2022-10-25 19:22:38 +00:00
videoControls.visibility = if (mediaMode == MediaMode.VIDEO) VISIBLE else INVISIBLE
if (mediaMode == MediaMode.VIDEO) {
setProgressUpdateListener { position, _ ->
val finalPlayer = player ?: return@setProgressUpdateListener
val remainingDuration = (finalPlayer.duration - position).toDuration(DurationUnit.MILLISECONDS)
val minutes: Long = remainingDuration.inWholeMinutes
val seconds: Long = remainingDuration.inWholeSeconds % 60
durationLabel.text = "${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}"
}
} else {
setProgressUpdateListener(null)
}
2022-10-25 19:22:38 +00:00
}
fun setShareButtonListener(listener: OnClickListener?) = shareButton.setOnClickListener(listener)
fun setForwardButtonListener(listener: OnClickListener?) = forwardButton.setOnClickListener(listener)
}
class LottieAnimatedButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : LottieAnimationView(context, attrs) {
init {
addValueCallback(KeyPath("**"), LottieProperty.COLOR) { ContextCompat.getColor(context, R.color.signal_colorOnSurface) }
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
speed = 1f
playAnimation()
}
MotionEvent.ACTION_UP -> {
if (isAnimating) {
addAnimatorListener(object : AnimatorListener {
2023-01-13 14:53:44 +00:00
override fun onAnimationEnd(animation: Animator) {
removeAllAnimatorListeners()
playAnimationReverse()
}
2023-01-13 14:53:44 +00:00
override fun onAnimationStart(animation: Animator) {}
override fun onAnimationCancel(animation: Animator) {}
override fun onAnimationRepeat(animation: Animator) {}
})
} else {
playAnimationReverse()
}
}
}
return super.onTouchEvent(event)
}
private fun playAnimationReverse() {
speed = -1f
playAnimation()
}
}
class AnimatedInOutImageButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : AppCompatImageButton(context, attrs) {
private val rotationWhenVisible: Float
private val rotationWhenHidden: Float
init {
val styledAttrs = getContext().obtainStyledAttributes(attrs, R.styleable.AnimatedInOutImageButton)
rotationWhenVisible = styledAttrs.getFloat(R.styleable.AnimatedInOutImageButton_rotationWhenVisible, 0f)
rotationWhenHidden = styledAttrs.getFloat(R.styleable.AnimatedInOutImageButton_rotationWhenHidden, 0f)
styledAttrs.recycle()
}
override fun setVisibility(visibility: Int) {
if (visibility == VISIBLE) {
super.setVisibility(visibility)
animateIn()
} else {
animateOut { super.setVisibility(visibility) }
}
}
private fun animateIn() {
this.rotation = rotationWhenHidden
this.scaleX = 0.5f
this.scaleY = 0.5f
this.alpha = 0f
val animator = this.animate()
.setDuration(animationDurationMs)
.alpha(1f)
.rotation(rotationWhenVisible)
.scaleX(1f)
.scaleY(1f)
if (Build.VERSION.SDK_INT >= 21) {
animator.interpolator = PathInterpolator(0.4f, 0.0f, 0.2f, 1f)
}
animator.start()
}
private fun animateOut(endAction: Runnable) {
val animator = this.animate()
.setDuration(animationDurationMs)
.alpha(0f)
.rotation(rotationWhenHidden)
.scaleX(0.5f)
.scaleY(0.5f)
.withEndAction(endAction)
if (Build.VERSION.SDK_INT >= 21) {
animator.interpolator = PathInterpolator(0.4f, 0.0f, 0.2f, 1f)
}
animator.start()
}
companion object {
const val animationDurationMs: Long = 150
}
}