Fix contact picture flickering in message list
Change the way we try to load contact pictures and generate contact letter fallback images afterwards, so Glide caches the result.
This commit is contained in:
parent
7dadab7362
commit
e7d8cda043
1 changed files with 46 additions and 94 deletions
|
@ -3,33 +3,26 @@ package com.fsck.k9.contacts
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Bitmap.CompressFormat
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import androidx.annotation.WorkerThread
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.WorkerThread
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.load.ResourceDecoder
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.data.StreamLocalUriFetcher
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.engine.Resource
|
||||
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapEncoder
|
||||
import com.bumptech.glide.load.resource.bitmap.BitmapResource
|
||||
import com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable
|
||||
import com.bumptech.glide.load.resource.file.FileToStreamDecoder
|
||||
import com.bumptech.glide.load.resource.transcode.BitmapToGlideDrawableTranscoder
|
||||
import com.bumptech.glide.request.FutureTarget
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.fsck.k9.helper.Contacts
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.ui.R
|
||||
import com.fsck.k9.view.RecipientSelectView.Recipient
|
||||
import java.io.InputStream
|
||||
|
||||
import timber.log.Timber
|
||||
import kotlin.math.max
|
||||
|
||||
class ContactPictureLoader(
|
||||
private val context: Context,
|
||||
|
@ -44,53 +37,45 @@ class ContactPictureLoader(
|
|||
|
||||
fun setContactPicture(imageView: ImageView, address: Address) {
|
||||
Glide.with(imageView.context)
|
||||
.using(ContactPictureModelLoader())
|
||||
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
|
||||
.from(Address::class.java)
|
||||
.load(address)
|
||||
.`as`(Bitmap::class.java)
|
||||
.decoder(ContactImageBitmapDecoder())
|
||||
.signature(contactLetterBitmapCreator.signatureOf(address))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.listener(FallbackImageRequestListener(address, imageView))
|
||||
// for some reason, following 2 lines fix loading issues.
|
||||
.load(address)
|
||||
.dontAnimate()
|
||||
.override(pictureSizeInPx, pictureSizeInPx)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
fun setContactPicture(imageView: ImageView, recipient: Recipient) {
|
||||
val contactPictureUri = recipient.photoThumbnailUri
|
||||
if (contactPictureUri != null) {
|
||||
setContactPicture(imageView, contactPictureUri, recipient.address)
|
||||
setContactPicture(imageView, contactPictureUri)
|
||||
} else {
|
||||
setFallbackPicture(imageView, recipient.address)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setContactPicture(imageView: ImageView, contactPictureUri: Uri, address: Address) {
|
||||
private fun setContactPicture(imageView: ImageView, contactPictureUri: Uri) {
|
||||
Glide.with(imageView.context)
|
||||
.load(contactPictureUri)
|
||||
.error(R.drawable.ic_contact_picture)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.listener(FallbackImageRequestListener(address, imageView))
|
||||
// for some reason, following 2 lines fix loading issues.
|
||||
.dontAnimate()
|
||||
.override(pictureSizeInPx, pictureSizeInPx)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
private fun setFallbackPicture(imageView: ImageView, address: Address) {
|
||||
val context = imageView.context
|
||||
Glide.with(context)
|
||||
Glide.with(imageView.context)
|
||||
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
|
||||
.from(Address::class.java)
|
||||
.`as`(Bitmap::class.java)
|
||||
.transcode(BitmapToGlideDrawableTranscoder(context), GlideDrawable::class.java)
|
||||
.decoder(ContactLetterBitmapDecoder())
|
||||
.decoder(ContactImageBitmapDecoder(contactLetterOnly = true))
|
||||
.signature(contactLetterBitmapCreator.signatureOf(address))
|
||||
.encoder(BitmapEncoder(CompressFormat.PNG, 0))
|
||||
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.load(address)
|
||||
// for some reason, following 2 lines fix loading issues.
|
||||
.dontAnimate()
|
||||
.override(pictureSizeInPx, pictureSizeInPx)
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
|
@ -110,6 +95,7 @@ class ContactPictureLoader(
|
|||
return Glide.with(context)
|
||||
.load(contactPictureUri)
|
||||
.asBitmap()
|
||||
.error(R.drawable.ic_contact_picture)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.dontAnimate()
|
||||
.into(pictureSizeInPx, pictureSizeInPx)
|
||||
|
@ -121,9 +107,7 @@ class ContactPictureLoader(
|
|||
.using(AddressModelLoader(backgroundCacheId), Address::class.java)
|
||||
.from(Address::class.java)
|
||||
.`as`(Bitmap::class.java)
|
||||
.decoder(ContactLetterBitmapDecoder())
|
||||
.encoder(BitmapEncoder(CompressFormat.PNG, 0))
|
||||
.cacheDecoder(FileToStreamDecoder(StreamBitmapDecoder(context)))
|
||||
.decoder(ContactImageBitmapDecoder(contactLetterOnly = true))
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.load(address)
|
||||
.dontAnimate()
|
||||
|
@ -131,18 +115,41 @@ class ContactPictureLoader(
|
|||
.getOrNull()
|
||||
}
|
||||
|
||||
private inner class ContactLetterBitmapDecoder : ResourceDecoder<Address, Bitmap> {
|
||||
private inner class ContactImageBitmapDecoder(
|
||||
private val contactLetterOnly: Boolean = false
|
||||
) : ResourceDecoder<Address, Bitmap> {
|
||||
|
||||
override fun decode(address: Address, width: Int, height: Int): Resource<Bitmap> {
|
||||
val pool = Glide.get(context).bitmapPool
|
||||
val bitmap: Bitmap =
|
||||
pool.getDirty(pictureSizeInPx, pictureSizeInPx, Bitmap.Config.ARGB_8888) ?:
|
||||
Bitmap.createBitmap(pictureSizeInPx, pictureSizeInPx, Bitmap.Config.ARGB_8888)
|
||||
|
||||
contactLetterBitmapCreator.drawBitmap(bitmap, pictureSizeInPx, address)
|
||||
val size = max(width, height)
|
||||
|
||||
val bitmap = loadContactPicture(address) ?: createContactLetterBitmap(address, size, pool)
|
||||
|
||||
return BitmapResource.obtain(bitmap, pool)
|
||||
}
|
||||
|
||||
private fun loadContactPicture(address: Address): Bitmap? {
|
||||
if (contactLetterOnly) return null
|
||||
|
||||
val photoUri = contactsHelper.getPhotoUri(address.address) ?: return null
|
||||
return try {
|
||||
context.contentResolver.openInputStream(photoUri).use { inputStream ->
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Couldn't load contact picture: $photoUri")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun createContactLetterBitmap(address: Address, size: Int, pool: BitmapPool): Bitmap {
|
||||
val bitmap = pool.getDirty(size, size, Bitmap.Config.ARGB_8888)
|
||||
?: Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
|
||||
return contactLetterBitmapCreator.drawBitmap(bitmap, size, address)
|
||||
}
|
||||
|
||||
override fun getId(): String {
|
||||
return "fallback-photo"
|
||||
}
|
||||
|
@ -161,61 +168,6 @@ class ContactPictureLoader(
|
|||
}
|
||||
}
|
||||
|
||||
private inner class ContactPictureModelLoader : StreamModelLoader<Address> {
|
||||
override fun getResourceFetcher(address: Address, width: Int, height: Int): DataFetcher<InputStream>? {
|
||||
return ContactPictureDataFetcher(address)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ContactPictureDataFetcher(val address: Address) : DataFetcher<InputStream> {
|
||||
var streamLocalUriFetcher: StreamLocalUriFetcher? = null
|
||||
|
||||
override fun loadData(priority: Priority?): InputStream? {
|
||||
val photoUri = contactsHelper.getPhotoUri(address.address)
|
||||
|
||||
return photoUri?.let {
|
||||
StreamLocalUriFetcher(context, photoUri).also {
|
||||
streamLocalUriFetcher = it
|
||||
}.loadData(priority)
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancel() = Unit
|
||||
|
||||
override fun cleanup() {
|
||||
streamLocalUriFetcher?.cleanup()
|
||||
}
|
||||
|
||||
override fun getId() = "contact:${address.address}"
|
||||
}
|
||||
|
||||
|
||||
private inner class FallbackImageRequestListener<T>(
|
||||
val address: Address,
|
||||
val imageView: ImageView
|
||||
) : RequestListener<T, GlideDrawable> {
|
||||
|
||||
override fun onException(
|
||||
e: Exception?,
|
||||
model: T,
|
||||
target: Target<GlideDrawable>,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
setFallbackPicture(imageView, address)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onResourceReady(
|
||||
resource: GlideDrawable,
|
||||
model: T,
|
||||
target: Target<GlideDrawable>,
|
||||
isFromMemoryCache: Boolean,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> FutureTarget<T>.getOrNull(): T? {
|
||||
return try {
|
||||
get()
|
||||
|
|
Loading…
Reference in a new issue