mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-23 12:38:33 +00:00
Add support for animated images to GlideImage.
Our GlideImage implementation doesn't support animated images, because it loads them as bitmaps and therefore only displays the first image frame as a static image. This change works around that issue by having GlideImage wrap an ImageView to handle cases where we need to display animated images.
This commit is contained in:
committed by
Michelle Tang
parent
fb111619d7
commit
288eda5bb1
@@ -5,8 +5,8 @@
|
|||||||
|
|
||||||
package org.thoughtcrime.securesms.compose
|
package org.thoughtcrime.securesms.compose
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -16,41 +16,97 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ImageBitmap
|
|
||||||
import androidx.compose.ui.graphics.asImageBitmap
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.TransitionOptions
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
|
import org.thoughtcrime.securesms.glide.cache.ApngOptions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Our very own GlideImage.
|
* Our very own GlideImage. The GlideImage composable provided by the bumptech library is not suitable because it was is using our encrypted cache decoder/encoder.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> GlideImage(
|
fun <T> GlideImage(
|
||||||
model: T?,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
model: T?,
|
||||||
imageSize: DpSize? = null,
|
imageSize: DpSize? = null,
|
||||||
|
scaleType: GlideImageScaleType = GlideImageScaleType.FIT_CENTER,
|
||||||
fallback: Drawable? = null,
|
fallback: Drawable? = null,
|
||||||
error: Drawable? = fallback,
|
error: Drawable? = fallback,
|
||||||
|
transition: TransitionOptions<*, Drawable>? = null,
|
||||||
|
diskCacheStrategy: DiskCacheStrategy = DiskCacheStrategy.ALL,
|
||||||
|
enableApngAnimation: Boolean = false
|
||||||
|
) {
|
||||||
|
if (enableApngAnimation) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
AndroidView(
|
||||||
|
factory = { context -> ImageView(context) },
|
||||||
|
update = { imageView ->
|
||||||
|
Glide.with(imageView.context)
|
||||||
|
.load(model)
|
||||||
|
.fallback(fallback)
|
||||||
|
.error(error)
|
||||||
|
.diskCacheStrategy(diskCacheStrategy)
|
||||||
|
.set(ApngOptions.ANIMATE, enableApngAnimation)
|
||||||
|
.apply {
|
||||||
|
scaleType.applyTo(this)
|
||||||
|
transition?.let(this::transition)
|
||||||
|
|
||||||
|
if (imageSize != null) {
|
||||||
|
with(density) {
|
||||||
|
this@apply.override(imageSize.width.toPx().toInt(), imageSize.height.toPx().toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into(imageView)
|
||||||
|
},
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
GlideImage(
|
||||||
|
model = model,
|
||||||
|
imageSize = imageSize,
|
||||||
|
scaleType = scaleType,
|
||||||
|
fallback = fallback,
|
||||||
|
error = error,
|
||||||
|
transition = transition,
|
||||||
|
diskCacheStrategy = diskCacheStrategy,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun <T> GlideImage(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
model: T?,
|
||||||
|
imageSize: DpSize? = null,
|
||||||
|
scaleType: GlideImageScaleType = GlideImageScaleType.FIT_CENTER,
|
||||||
|
fallback: Drawable? = null,
|
||||||
|
error: Drawable? = fallback,
|
||||||
|
transition: TransitionOptions<*, Drawable>? = null,
|
||||||
diskCacheStrategy: DiskCacheStrategy = DiskCacheStrategy.ALL
|
diskCacheStrategy: DiskCacheStrategy = DiskCacheStrategy.ALL
|
||||||
) {
|
) {
|
||||||
var bitmap by remember {
|
var drawable by remember {
|
||||||
mutableStateOf<ImageBitmap?>(null)
|
mutableStateOf<Drawable?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
val target = remember {
|
val target = remember {
|
||||||
object : CustomTarget<Bitmap>() {
|
object : CustomTarget<Drawable>() {
|
||||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||||
bitmap = resource.asImageBitmap()
|
drawable = resource
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
bitmap = null
|
drawable = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,12 +115,14 @@ fun <T> GlideImage(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
DisposableEffect(model, fallback, error, diskCacheStrategy, density, imageSize) {
|
DisposableEffect(model, fallback, error, diskCacheStrategy, density, imageSize) {
|
||||||
val builder = Glide.with(context)
|
val builder = Glide.with(context)
|
||||||
.asBitmap()
|
|
||||||
.load(model)
|
.load(model)
|
||||||
.fallback(fallback)
|
.fallback(fallback)
|
||||||
.error(error)
|
.error(error)
|
||||||
.diskCacheStrategy(diskCacheStrategy)
|
.diskCacheStrategy(diskCacheStrategy)
|
||||||
.fitCenter()
|
.apply {
|
||||||
|
scaleType.applyTo(this)
|
||||||
|
transition?.let(this::transition)
|
||||||
|
}
|
||||||
|
|
||||||
if (imageSize != null) {
|
if (imageSize != null) {
|
||||||
with(density) {
|
with(density) {
|
||||||
@@ -77,18 +135,40 @@ fun <T> GlideImage(
|
|||||||
object : DisposableEffectResult {
|
object : DisposableEffectResult {
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
Glide.with(context).clear(target)
|
Glide.with(context).clear(target)
|
||||||
bitmap = null
|
drawable = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val bm = bitmap
|
if (drawable != null) {
|
||||||
if (bm != null) {
|
|
||||||
Image(
|
Image(
|
||||||
bitmap = bm,
|
painter = rememberDrawablePainter(drawable),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = if (model == null) ContentScale.Inside else ContentScale.Crop,
|
contentScale = if (model == null) ContentScale.Inside else ContentScale.Crop,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class GlideImageScaleType {
|
||||||
|
/** @see [com.bumptech.glide.request.RequestOptions.fitCenter] */
|
||||||
|
FIT_CENTER,
|
||||||
|
|
||||||
|
/** @see [com.bumptech.glide.request.RequestOptions.centerInside] */
|
||||||
|
CENTER_INSIDE,
|
||||||
|
|
||||||
|
/** @see [com.bumptech.glide.request.RequestOptions.centerCrop] */
|
||||||
|
CENTER_CROP,
|
||||||
|
|
||||||
|
/** @see [com.bumptech.glide.request.RequestOptions.circleCrop] */
|
||||||
|
CIRCLE_CROP;
|
||||||
|
|
||||||
|
fun <TranscodeT> applyTo(builder: com.bumptech.glide.RequestBuilder<TranscodeT>): com.bumptech.glide.RequestBuilder<TranscodeT> {
|
||||||
|
return when (this) {
|
||||||
|
FIT_CENTER -> builder.fitCenter()
|
||||||
|
CENTER_INSIDE -> builder.centerInside()
|
||||||
|
CENTER_CROP -> builder.centerCrop()
|
||||||
|
CIRCLE_CROP -> builder.circleCrop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -42,6 +43,7 @@ import org.thoughtcrime.securesms.components.transfercontrols.TransferProgressSt
|
|||||||
import org.thoughtcrime.securesms.compose.GlideImage
|
import org.thoughtcrime.securesms.compose.GlideImage
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
||||||
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
|
import org.thoughtcrime.securesms.stickers.AvailableStickerPack.DownloadStatus
|
||||||
|
import org.thoughtcrime.securesms.util.DeviceProperties
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun StickerPackSectionHeader(
|
fun StickerPackSectionHeader(
|
||||||
@@ -238,6 +240,7 @@ private fun StickerPackInfo(
|
|||||||
) {
|
) {
|
||||||
GlideImage(
|
GlideImage(
|
||||||
model = coverImageUri,
|
model = coverImageUri,
|
||||||
|
enableApngAnimation = DeviceProperties.shouldAllowApngStickerAnimation(LocalContext.current),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(end = 16.dp)
|
.padding(end = 16.dp)
|
||||||
.size(56.dp)
|
.size(56.dp)
|
||||||
|
|||||||
Reference in New Issue
Block a user