diff --git a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt index 267d1a3ad7..275d76136e 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/AvatarRenderer.kt @@ -20,11 +20,16 @@ import android.content.Context import android.graphics.drawable.Drawable import android.widget.ImageView import androidx.annotation.UiThread +import androidx.annotation.WorkerThread import androidx.core.content.ContextCompat import com.amulyakhare.textdrawable.TextDrawable +import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.DrawableImageViewTarget +import com.bumptech.glide.request.target.Target import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixPatterns +import im.vector.matrix.android.api.session.content.ContentUrlResolver import im.vector.matrix.android.api.session.room.model.RoomMember import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.riotredesign.R @@ -32,6 +37,9 @@ import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideRequest import im.vector.riotredesign.core.glide.GlideRequests +/** + * This helper centralise ways to retrieve avatar into ImageView or even generic Target + */ object AvatarRenderer { @UiThread @@ -46,23 +54,53 @@ object AvatarRenderer { @UiThread fun render(avatarUrl: String?, name: String?, imageView: ImageView) { + render(imageView.context, GlideApp.with(imageView), avatarUrl, name, imageView.height, DrawableImageViewTarget(imageView)) + } + + @UiThread + fun render(context: Context, + glideRequest: GlideRequests, + avatarUrl: String?, + name: String?, + size: Int, + target: Target) { if (name.isNullOrEmpty()) { return } - val placeholder = buildPlaceholderDrawable(imageView.context, name) - buildGlideRequest(GlideApp.with(imageView), avatarUrl) + val placeholder = buildPlaceholderDrawable(context, name) + buildGlideRequest(glideRequest, avatarUrl, size) .placeholder(placeholder) - .into(imageView) + .into(target) } - fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest { - val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl) + @WorkerThread + fun getCachedOrPlaceholder(context: Context, + glideRequest: GlideRequests, + avatarUrl: String?, + text: String, + size: Int): Drawable { + val future = buildGlideRequest(glideRequest, avatarUrl, size).onlyRetrieveFromCache(true).submit() + return try { + future.get() + } catch (exception: Exception) { + buildPlaceholderDrawable(context, text) + } + } + + // PRIVATE API ********************************************************************************* + + private fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?, size: Int): GlideRequest { + val resolvedUrl = Matrix.getInstance().currentSession + .contentUrlResolver() + .resolveThumbnail(avatarUrl, size, size, ContentUrlResolver.ThumbnailMethod.SCALE) + return glideRequest .load(resolvedUrl) .apply(RequestOptions.circleCropTransform()) + .diskCacheStrategy(DiskCacheStrategy.DATA) } - fun buildPlaceholderDrawable(context: Context, text: String): Drawable { + private fun buildPlaceholderDrawable(context: Context, text: String): Drawable { val avatarColor = ContextCompat.getColor(context, R.color.pale_teal) return if (text.isEmpty()) { TextDrawable.builder().buildRound("", avatarColor) diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt index 49c754c4dd..9a0bc8ba6f 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageTextItem.kt @@ -48,11 +48,6 @@ abstract class MessageTextItem : AbsMessageItem() { findPillsAndProcess { it.bind(holder.messageView) } } - override fun unbind(holder: Holder) { - findPillsAndProcess { it.unbind() } - super.unbind(holder) - } - private fun findPillsAndProcess(processBlock: (span: PillImageSpan) -> Unit) { GlobalScope.launch(Dispatchers.Main) { val pillImageSpans: Array? = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt b/app/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt index 27e00b54c6..2ecee7954b 100644 --- a/app/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt +++ b/app/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt @@ -148,7 +148,7 @@ private class MxLinkHandler(private val glideRequests: GlideRequests, val permalinkData = PermalinkParser.parse(link) when (permalinkData) { is PermalinkData.UserLink -> { - val user = session.getUser(permalinkData.userId) ?: return + val user = session.getUser(permalinkData.userId) val span = PillImageSpan(glideRequests, context, permalinkData.userId, user) SpannableBuilder.setSpans( visitor.builder(), diff --git a/app/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt b/app/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt index cbc0251a08..3f48043dee 100644 --- a/app/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt +++ b/app/src/main/java/im/vector/riotredesign/features/html/PillImageSpan.kt @@ -22,7 +22,7 @@ import android.graphics.Paint import android.graphics.drawable.Drawable import android.text.style.ReplacementSpan import android.widget.TextView -import androidx.annotation.MainThread +import androidx.annotation.UiThread import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import com.google.android.material.chip.ChipDrawable @@ -32,36 +32,33 @@ import im.vector.riotredesign.core.glide.GlideRequests import im.vector.riotredesign.features.home.AvatarRenderer import java.lang.ref.WeakReference +/** + * This span is able to replace a text by a [ChipDrawable] + * It's needed to call [bind] method to start requesting avatar, otherwise only the placeholder icon will be displayed if not already cached. + */ + +private const val PILL_AVATAR_SIZE = 80 + class PillImageSpan(private val glideRequests: GlideRequests, private val context: Context, private val userId: String, private val user: User?) : ReplacementSpan() { - private val pillDrawable = createChipDrawable(context, userId, user) + private val displayName by lazy { + if (user?.displayName.isNullOrEmpty()) userId else user?.displayName!! + } + + private val pillDrawable = createChipDrawable() private val target = PillImageSpanTarget(this) private var tv: WeakReference? = null - @MainThread + @UiThread fun bind(textView: TextView) { tv = WeakReference(textView) - AvatarRenderer.buildGlideRequest(glideRequests, user?.avatarUrl).into(target) + AvatarRenderer.render(context, glideRequests, user?.avatarUrl, displayName, PILL_AVATAR_SIZE, target) } - @MainThread - fun unbind() { - glideRequests.clear(target) - tv = null - } - - @MainThread - private fun updateAvatarDrawable(drawable: Drawable?) { - pillDrawable.apply { - chipIcon = drawable - } - tv?.get()?.apply { - invalidate() - } - } + // ReplacementSpan ***************************************************************************** override fun getSize(paint: Paint, text: CharSequence, start: Int, @@ -85,7 +82,6 @@ class PillImageSpan(private val glideRequests: GlideRequests, y: Int, bottom: Int, paint: Paint) { - canvas.save() val transY = bottom - pillDrawable.bounds.bottom canvas.translate(x, transY.toFloat()) @@ -93,37 +89,50 @@ class PillImageSpan(private val glideRequests: GlideRequests, canvas.restore() } - private fun createChipDrawable(context: Context, userId: String, user: User?): ChipDrawable { + internal fun updateAvatarDrawable(drawable: Drawable?) { + pillDrawable.apply { + chipIcon = drawable + } + tv?.get()?.apply { + invalidate() + } + } + + // Private methods ***************************************************************************** + + private fun createChipDrawable(): ChipDrawable { val textPadding = context.resources.getDimension(R.dimen.pill_text_padding) - val displayName = if (user?.displayName.isNullOrEmpty()) userId else user?.displayName!! return ChipDrawable.createFromResource(context, R.xml.pill_view).apply { setText(displayName) textEndPadding = textPadding textStartPadding = textPadding setChipMinHeightResource(R.dimen.pill_min_height) setChipIconSizeResource(R.dimen.pill_avatar_size) - chipIcon = AvatarRenderer.buildPlaceholderDrawable(context, displayName) + chipIcon = AvatarRenderer.getCachedOrPlaceholder(context, glideRequests, user?.avatarUrl, displayName, PILL_AVATAR_SIZE) setBounds(0, 0, intrinsicWidth, intrinsicHeight) } } - private class PillImageSpanTarget(pillImageSpan: PillImageSpan) : SimpleTarget() { +} - private val pillImageSpan = WeakReference(pillImageSpan) +/** + * Glide target to handle avatar retrieval into [PillImageSpan]. + */ +private class PillImageSpanTarget(pillImageSpan: PillImageSpan) : SimpleTarget() { - override fun onResourceReady(drawable: Drawable, transition: Transition?) { - updateWith(drawable) - } + private val pillImageSpan = WeakReference(pillImageSpan) - override fun onLoadCleared(placeholder: Drawable?) { - updateWith(placeholder) - } - - private fun updateWith(drawable: Drawable?) { - pillImageSpan.get()?.apply { - this.updateAvatarDrawable(drawable) - } - } + override fun onResourceReady(drawable: Drawable, transition: Transition?) { + updateWith(drawable) } + override fun onLoadCleared(placeholder: Drawable?) { + updateWith(placeholder) + } + + private fun updateWith(drawable: Drawable?) { + pillImageSpan.get()?.apply { + updateAvatarDrawable(drawable) + } + } } \ No newline at end of file