Signal-Android/app/src/main/java/org/thoughtcrime/securesms/stories/viewer/reply/StoriesSharedElementCrossFa...

254 wiersze
7.3 KiB
Kotlin
Czysty Zwykły widok Historia

package org.thoughtcrime.securesms.stories.viewer.reply
import android.animation.FloatEvaluator
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.util.AttributeSet
import android.widget.ImageView
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.android.material.card.MaterialCardView
import org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
2022-04-14 18:11:05 +00:00
import org.thoughtcrime.securesms.blurhash.BlurHash
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.stories.StoryTextPostModel
import kotlin.reflect.KProperty
class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : MaterialCardView(context, attrs), CrossfaderTransition.Crossfadeable {
companion object {
val CORNER_RADIUS_START = DimensionUnit.DP.toPixels(12f)
val CORNER_RADIUS_END = DimensionUnit.DP.toPixels(18f)
val CORNER_RADIUS_EVALUATOR = FloatEvaluator()
}
init {
inflate(context, R.layout.stories_shared_element_crossfader, this)
}
private val sourceView: ImageView = findViewById(R.id.source_image)
2022-04-14 18:11:05 +00:00
private val sourceBlurView: ImageView = findViewById(R.id.source_image_blur)
private val targetView: ImageView = findViewById(R.id.target_image)
2022-04-14 18:11:05 +00:00
private val targetBlurView: ImageView = findViewById(R.id.target_image_blur)
private var isSourceReady: Boolean by NotifyIfReadyDelegate(false)
private var isSourceBlurReady: Boolean by NotifyIfReadyDelegate(false)
private var isTargetReady: Boolean by NotifyIfReadyDelegate(false)
private var isTargetBlurReady: Boolean by NotifyIfReadyDelegate(false)
private var latestSource: Any? = null
private var latestTarget: Any? = null
var callback: Callback? = null
fun setSourceView(storyTextPostModel: StoryTextPostModel) {
if (latestSource == storyTextPostModel) {
return
}
latestSource = storyTextPostModel
GlideApp.with(sourceView)
.load(storyTextPostModel)
.addListener(
OnReadyListener {
isSourceReady = true
}
)
.placeholder(storyTextPostModel.getPlaceholder())
.dontAnimate()
.centerCrop()
.into(sourceView)
2022-04-14 18:11:05 +00:00
GlideApp.with(sourceBlurView).clear(sourceBlurView)
isSourceBlurReady = true
}
2022-04-14 18:11:05 +00:00
fun setSourceView(uri: Uri, blur: BlurHash?) {
if (latestSource == uri) {
return
}
latestSource = uri
GlideApp.with(sourceView)
.load(DecryptableStreamUriLoader.DecryptableUri(uri))
.addListener(
OnReadyListener {
isSourceReady = true
}
)
.dontAnimate()
.centerCrop()
.into(sourceView)
2022-04-14 18:11:05 +00:00
if (blur == null) {
GlideApp.with(sourceBlurView).clear(sourceBlurView)
isSourceBlurReady = true
} else {
GlideApp.with(sourceBlurView)
.load(blur)
.addListener(
OnReadyListener {
isSourceBlurReady = true
}
)
.dontAnimate()
.centerCrop()
.into(sourceBlurView)
}
}
fun setTargetView(messageRecord: MmsMessageRecord): Boolean {
val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri
2022-04-14 18:11:05 +00:00
val thumbBlur: BlurHash? = messageRecord.slideDeck.thumbnailSlide?.placeholderBlur
when {
messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
2022-04-14 18:11:05 +00:00
thumbUri != null -> setTargetView(thumbUri, thumbBlur)
else -> return false
}
return true
}
private fun setTargetView(storyTextPostModel: StoryTextPostModel) {
if (latestTarget == storyTextPostModel) {
return
}
latestTarget = storyTextPostModel
GlideApp.with(targetView)
.load(storyTextPostModel)
.addListener(
OnReadyListener {
isTargetReady = true
}
)
.dontAnimate()
.placeholder(storyTextPostModel.getPlaceholder())
.centerCrop()
.into(targetView)
2022-04-14 18:11:05 +00:00
GlideApp.with(targetBlurView).clear(targetBlurView)
2022-04-14 18:11:05 +00:00
isTargetBlurReady = true
}
2022-04-14 18:11:05 +00:00
private fun setTargetView(uri: Uri, blur: BlurHash?) {
if (latestTarget == uri) {
return
}
latestTarget = uri
GlideApp.with(targetView)
.load(DecryptableStreamUriLoader.DecryptableUri(uri))
.addListener(
OnReadyListener {
isTargetReady = true
}
)
.dontAnimate()
2022-04-14 18:11:05 +00:00
.fitCenter()
.into(targetView)
2022-04-14 18:11:05 +00:00
if (blur == null) {
GlideApp.with(targetBlurView).clear(targetBlurView)
isTargetBlurReady = true
} else {
GlideApp.with(targetBlurView)
.load(blur)
.addListener(
OnReadyListener {
isTargetBlurReady = true
}
)
.dontAnimate()
.centerCrop()
.into(targetBlurView)
}
}
private fun notifyIfReady() {
2022-04-14 18:11:05 +00:00
if (isSourceReady && isTargetReady && isSourceBlurReady && isTargetBlurReady) {
callback?.onReadyToAnimate()
}
}
private class OnReadyListener(private val onReady: () -> Unit) : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
onReady()
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
onReady()
return false
}
}
override fun onCrossfadeAnimationUpdated(progress: Float, reverse: Boolean) {
if (reverse) {
sourceView.alpha = progress
2022-04-14 18:11:05 +00:00
sourceBlurView.alpha = progress
targetView.alpha = 1f - progress
2022-04-14 18:11:05 +00:00
targetBlurView.alpha = 1f - progress
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
} else {
sourceView.alpha = 1f - progress
2022-04-14 18:11:05 +00:00
sourceBlurView.alpha = 1f - progress
targetView.alpha = progress
2022-04-14 18:11:05 +00:00
targetBlurView.alpha = progress
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END)
}
}
override fun onCrossfadeStarted(reverse: Boolean) {
alpha = 1f
sourceView.alpha = if (reverse) 0f else 1f
2022-04-14 18:11:05 +00:00
sourceBlurView.alpha = if (reverse) 0f else 1f
targetView.alpha = if (reverse) 1f else 0f
2022-04-14 18:11:05 +00:00
targetBlurView.alpha = if (reverse) 1f else 0f
radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START
callback?.onAnimationStarted()
}
override fun onCrossfadeFinished(reverse: Boolean) {
if (reverse) {
return
}
callback?.onAnimationFinished()
}
private inner class NotifyIfReadyDelegate(var value: Boolean) {
operator fun getValue(storiesSharedElementCrossFaderView: StoriesSharedElementCrossFaderView, property: KProperty<*>): Boolean {
return value
}
operator fun setValue(storiesSharedElementCrossFaderView: StoriesSharedElementCrossFaderView, property: KProperty<*>, b: Boolean) {
value = b
notifyIfReady()
}
}
interface Callback {
fun onReadyToAnimate()
fun onAnimationStarted()
fun onAnimationFinished()
}
}