Merge pull request #4225 from k9mail/fix_fallback_image_loading
Fix contact picture flickering in message list
This commit is contained in:
commit
457f27e154
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