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

198 wiersze
5.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 androidx.cardview.widget.CardView
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 org.signal.core.util.DimensionUnit
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.transitions.CrossfaderTransition
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
class StoriesSharedElementCrossFaderView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : CardView(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)
private val targetView: ImageView = findViewById(R.id.target_image)
private var isSourceReady: Boolean = false
private var isTargetReady: Boolean = 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
notifyIfReady()
}
)
.placeholder(storyTextPostModel.getPlaceholder())
.dontAnimate()
.centerCrop()
.into(sourceView)
}
fun setSourceView(uri: Uri) {
if (latestSource == uri) {
return
}
latestSource = uri
GlideApp.with(sourceView)
.load(DecryptableStreamUriLoader.DecryptableUri(uri))
.addListener(
OnReadyListener {
isSourceReady = true
notifyIfReady()
}
)
.dontAnimate()
.centerCrop()
.into(sourceView)
}
fun setTargetView(messageRecord: MmsMessageRecord): Boolean {
val thumbUri = messageRecord.slideDeck.thumbnailSlide?.uri
when {
messageRecord.storyType.isTextStory -> setTargetView(StoryTextPostModel.parseFrom(messageRecord))
thumbUri != null -> setTargetView(thumbUri)
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
notifyIfReady()
}
)
.dontAnimate()
.placeholder(storyTextPostModel.getPlaceholder())
.centerCrop()
.into(targetView)
}
private fun setTargetView(uri: Uri) {
if (latestTarget == uri) {
return
}
latestTarget = uri
GlideApp.with(targetView)
.load(DecryptableStreamUriLoader.DecryptableUri(uri))
.addListener(
OnReadyListener {
isTargetReady = true
notifyIfReady()
}
)
.dontAnimate()
.centerCrop()
.into(targetView)
}
private fun notifyIfReady() {
if (isSourceReady && isTargetReady) {
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
targetView.alpha = 1f - progress
radius = CORNER_RADIUS_EVALUATOR.evaluate(progress, CORNER_RADIUS_END, CORNER_RADIUS_START)
} else {
sourceView.alpha = 1f - progress
targetView.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
targetView.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
}
animate().alpha(0f)
callback?.onAnimationFinished()
}
interface Callback {
fun onReadyToAnimate()
fun onAnimationStarted()
fun onAnimationFinished()
}
}