Pills : finalize avatar retrieval

This commit is contained in:
ganfra 2019-03-04 16:52:44 +01:00
parent fffdf4b8c1
commit ef3fb561e9
4 changed files with 91 additions and 49 deletions

View File

@ -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<Drawable>
*/
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<Drawable>) {
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<Drawable> {
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<Drawable> {
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)

View File

@ -48,11 +48,6 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
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<PillImageSpan>? = withContext(Dispatchers.IO) {

View File

@ -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(),

View File

@ -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<TextView>? = 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<Drawable>() {
}

private val pillImageSpan = WeakReference(pillImageSpan)
/**
* Glide target to handle avatar retrieval into [PillImageSpan].
*/
private class PillImageSpanTarget(pillImageSpan: PillImageSpan) : SimpleTarget<Drawable>() {

override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) {
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<in Drawable>?) {
updateWith(drawable)
}

override fun onLoadCleared(placeholder: Drawable?) {
updateWith(placeholder)
}

private fun updateWith(drawable: Drawable?) {
pillImageSpan.get()?.apply {
updateAvatarDrawable(drawable)
}
}
}