2022-04-07 19:08:05 +00:00
|
|
|
package org.thoughtcrime.securesms.stories.viewer.reply
|
|
|
|
|
2022-04-13 18:12:38 +00:00
|
|
|
import android.animation.FloatEvaluator
|
2022-04-07 19:08:05 +00:00
|
|
|
import android.content.Context
|
|
|
|
import android.graphics.drawable.Drawable
|
|
|
|
import android.net.Uri
|
|
|
|
import android.util.AttributeSet
|
|
|
|
import android.widget.ImageView
|
2022-04-13 18:12:38 +00:00
|
|
|
import androidx.cardview.widget.CardView
|
2022-04-07 19:08:05 +00:00
|
|
|
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
|
2022-04-13 18:12:38 +00:00
|
|
|
import org.signal.core.util.DimensionUnit
|
2022-04-07 19:08:05 +00:00
|
|
|
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
|
2022-04-07 19:08:05 +00:00
|
|
|
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
|
2022-04-15 12:45:51 +00:00
|
|
|
import kotlin.reflect.KProperty
|
2022-04-07 19:08:05 +00:00
|
|
|
|
|
|
|
class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
|
|
|
|
context: Context,
|
|
|
|
attrs: AttributeSet? = null
|
2022-04-13 18:12:38 +00:00
|
|
|
) : CardView(context, attrs), CrossfaderTransition.Crossfadeable {
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
val CORNER_RADIUS_START = DimensionUnit.DP.toPixels(12f)
|
2022-04-13 18:30:37 +00:00
|
|
|
val CORNER_RADIUS_END = DimensionUnit.DP.toPixels(18f)
|
2022-04-13 18:12:38 +00:00
|
|
|
val CORNER_RADIUS_EVALUATOR = FloatEvaluator()
|
|
|
|
}
|
2022-04-07 19:08:05 +00:00
|
|
|
|
|
|
|
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)
|
2022-04-07 19:08:05 +00:00
|
|
|
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)
|
2022-04-07 19:08:05 +00:00
|
|
|
|
2022-04-15 12:45:51 +00:00
|
|
|
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)
|
2022-04-07 19:08:05 +00:00
|
|
|
|
|
|
|
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-07 19:08:05 +00:00
|
|
|
}
|
|
|
|
|
2022-04-14 18:11:05 +00:00
|
|
|
fun setSourceView(uri: Uri, blur: BlurHash?) {
|
2022-04-07 19:08:05 +00:00
|
|
|
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)
|
|
|
|
}
|
2022-04-07 19:08:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2022-04-07 19:08:05 +00:00
|
|
|
when {
|
|
|
|
messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
|
2022-04-14 18:11:05 +00:00
|
|
|
thumbUri != null -> setTargetView(thumbUri, thumbBlur)
|
2022-04-07 19:08:05 +00:00
|
|
|
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(sourceBlurView).clear(sourceBlurView)
|
|
|
|
isTargetBlurReady = true
|
2022-04-07 19:08:05 +00:00
|
|
|
}
|
|
|
|
|
2022-04-14 18:11:05 +00:00
|
|
|
private fun setTargetView(uri: Uri, blur: BlurHash?) {
|
2022-04-07 19:08:05 +00:00
|
|
|
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()
|
2022-04-07 19:08:05 +00:00
|
|
|
.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)
|
|
|
|
}
|
2022-04-07 19:08:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun notifyIfReady() {
|
2022-04-14 18:11:05 +00:00
|
|
|
if (isSourceReady && isTargetReady && isSourceBlurReady && isTargetBlurReady) {
|
2022-04-07 19:08:05 +00:00
|
|
|
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
|
2022-04-07 19:08:05 +00:00
|
|
|
targetView.alpha = 1f - progress
|
2022-04-14 18:11:05 +00:00
|
|
|
targetBlurView.alpha = 1f - progress
|
2022-04-13 18:12:38 +00:00
|
|
|
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
|
2022-04-07 19:08:05 +00:00
|
|
|
} else {
|
|
|
|
sourceView.alpha = 1f - progress
|
2022-04-14 18:11:05 +00:00
|
|
|
sourceBlurView.alpha = 1f - progress
|
2022-04-07 19:08:05 +00:00
|
|
|
targetView.alpha = progress
|
2022-04-14 18:11:05 +00:00
|
|
|
targetBlurView.alpha = progress
|
2022-04-13 18:12:38 +00:00
|
|
|
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_START, CORNER_RADIUS_END)
|
2022-04-07 19:08:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2022-04-07 19:08:05 +00:00
|
|
|
targetView.alpha = if (reverse) 1f else 0f
|
2022-04-14 18:11:05 +00:00
|
|
|
targetBlurView.alpha = if (reverse) 1f else 0f
|
2022-04-07 19:08:05 +00:00
|
|
|
|
2022-04-13 18:12:38 +00:00
|
|
|
radius = if (reverse) CORNER_RADIUS_END else CORNER_RADIUS_START
|
|
|
|
|
2022-04-07 19:08:05 +00:00
|
|
|
callback?.onAnimationStarted()
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCrossfadeFinished(reverse: Boolean) {
|
|
|
|
if (reverse) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
animate().alpha(0f)
|
|
|
|
|
|
|
|
callback?.onAnimationFinished()
|
|
|
|
}
|
|
|
|
|
2022-04-15 12:45:51 +00:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-07 19:08:05 +00:00
|
|
|
interface Callback {
|
|
|
|
fun onReadyToAnimate()
|
|
|
|
fun onAnimationStarted()
|
|
|
|
fun onAnimationFinished()
|
|
|
|
}
|
|
|
|
}
|