mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-15 01:35:07 +08:00
Pills : finalize avatar retrieval
This commit is contained in:
parent
fffdf4b8c1
commit
ef3fb561e9
@ -20,11 +20,16 @@ import android.content.Context
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import androidx.annotation.UiThread
|
import androidx.annotation.UiThread
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.amulyakhare.textdrawable.TextDrawable
|
import com.amulyakhare.textdrawable.TextDrawable
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
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.Matrix
|
||||||
import im.vector.matrix.android.api.MatrixPatterns
|
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.RoomMember
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
import im.vector.riotredesign.R
|
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.GlideRequest
|
||||||
import im.vector.riotredesign.core.glide.GlideRequests
|
import im.vector.riotredesign.core.glide.GlideRequests
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This helper centralise ways to retrieve avatar into ImageView or even generic Target<Drawable>
|
||||||
|
*/
|
||||||
object AvatarRenderer {
|
object AvatarRenderer {
|
||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
@ -46,23 +54,53 @@ object AvatarRenderer {
|
|||||||
|
|
||||||
@UiThread
|
@UiThread
|
||||||
fun render(avatarUrl: String?, name: String?, imageView: ImageView) {
|
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()) {
|
if (name.isNullOrEmpty()) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val placeholder = buildPlaceholderDrawable(imageView.context, name)
|
val placeholder = buildPlaceholderDrawable(context, name)
|
||||||
buildGlideRequest(GlideApp.with(imageView), avatarUrl)
|
buildGlideRequest(glideRequest, avatarUrl, size)
|
||||||
.placeholder(placeholder)
|
.placeholder(placeholder)
|
||||||
.into(imageView)
|
.into(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildGlideRequest(glideRequest: GlideRequests, avatarUrl: String?): GlideRequest<Drawable> {
|
@WorkerThread
|
||||||
val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
|
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
|
return glideRequest
|
||||||
.load(resolvedUrl)
|
.load(resolvedUrl)
|
||||||
.apply(RequestOptions.circleCropTransform())
|
.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)
|
val avatarColor = ContextCompat.getColor(context, R.color.pale_teal)
|
||||||
return if (text.isEmpty()) {
|
return if (text.isEmpty()) {
|
||||||
TextDrawable.builder().buildRound("", avatarColor)
|
TextDrawable.builder().buildRound("", avatarColor)
|
||||||
|
@ -48,11 +48,6 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
|||||||
findPillsAndProcess { it.bind(holder.messageView) }
|
findPillsAndProcess { it.bind(holder.messageView) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun unbind(holder: Holder) {
|
|
||||||
findPillsAndProcess { it.unbind() }
|
|
||||||
super.unbind(holder)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findPillsAndProcess(processBlock: (span: PillImageSpan) -> Unit) {
|
private fun findPillsAndProcess(processBlock: (span: PillImageSpan) -> Unit) {
|
||||||
GlobalScope.launch(Dispatchers.Main) {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
val pillImageSpans: Array<PillImageSpan>? = withContext(Dispatchers.IO) {
|
val pillImageSpans: Array<PillImageSpan>? = withContext(Dispatchers.IO) {
|
||||||
|
@ -148,7 +148,7 @@ private class MxLinkHandler(private val glideRequests: GlideRequests,
|
|||||||
val permalinkData = PermalinkParser.parse(link)
|
val permalinkData = PermalinkParser.parse(link)
|
||||||
when (permalinkData) {
|
when (permalinkData) {
|
||||||
is PermalinkData.UserLink -> {
|
is PermalinkData.UserLink -> {
|
||||||
val user = session.getUser(permalinkData.userId) ?: return
|
val user = session.getUser(permalinkData.userId)
|
||||||
val span = PillImageSpan(glideRequests, context, permalinkData.userId, user)
|
val span = PillImageSpan(glideRequests, context, permalinkData.userId, user)
|
||||||
SpannableBuilder.setSpans(
|
SpannableBuilder.setSpans(
|
||||||
visitor.builder(),
|
visitor.builder(),
|
||||||
|
@ -22,7 +22,7 @@ import android.graphics.Paint
|
|||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.text.style.ReplacementSpan
|
import android.text.style.ReplacementSpan
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.UiThread
|
||||||
import com.bumptech.glide.request.target.SimpleTarget
|
import com.bumptech.glide.request.target.SimpleTarget
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.google.android.material.chip.ChipDrawable
|
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 im.vector.riotredesign.features.home.AvatarRenderer
|
||||||
import java.lang.ref.WeakReference
|
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,
|
class PillImageSpan(private val glideRequests: GlideRequests,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val userId: String,
|
private val userId: String,
|
||||||
private val user: User?) : ReplacementSpan() {
|
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 val target = PillImageSpanTarget(this)
|
||||||
private var tv: WeakReference<TextView>? = null
|
private var tv: WeakReference<TextView>? = null
|
||||||
|
|
||||||
@MainThread
|
@UiThread
|
||||||
fun bind(textView: TextView) {
|
fun bind(textView: TextView) {
|
||||||
tv = WeakReference(textView)
|
tv = WeakReference(textView)
|
||||||
AvatarRenderer.buildGlideRequest(glideRequests, user?.avatarUrl).into(target)
|
AvatarRenderer.render(context, glideRequests, user?.avatarUrl, displayName, PILL_AVATAR_SIZE, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
// ReplacementSpan *****************************************************************************
|
||||||
fun unbind() {
|
|
||||||
glideRequests.clear(target)
|
|
||||||
tv = null
|
|
||||||
}
|
|
||||||
|
|
||||||
@MainThread
|
|
||||||
private fun updateAvatarDrawable(drawable: Drawable?) {
|
|
||||||
pillDrawable.apply {
|
|
||||||
chipIcon = drawable
|
|
||||||
}
|
|
||||||
tv?.get()?.apply {
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getSize(paint: Paint, text: CharSequence,
|
override fun getSize(paint: Paint, text: CharSequence,
|
||||||
start: Int,
|
start: Int,
|
||||||
@ -85,7 +82,6 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
|||||||
y: Int,
|
y: Int,
|
||||||
bottom: Int,
|
bottom: Int,
|
||||||
paint: Paint) {
|
paint: Paint) {
|
||||||
|
|
||||||
canvas.save()
|
canvas.save()
|
||||||
val transY = bottom - pillDrawable.bounds.bottom
|
val transY = bottom - pillDrawable.bounds.bottom
|
||||||
canvas.translate(x, transY.toFloat())
|
canvas.translate(x, transY.toFloat())
|
||||||
@ -93,37 +89,50 @@ class PillImageSpan(private val glideRequests: GlideRequests,
|
|||||||
canvas.restore()
|
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 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 {
|
return ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
|
||||||
setText(displayName)
|
setText(displayName)
|
||||||
textEndPadding = textPadding
|
textEndPadding = textPadding
|
||||||
textStartPadding = textPadding
|
textStartPadding = textPadding
|
||||||
setChipMinHeightResource(R.dimen.pill_min_height)
|
setChipMinHeightResource(R.dimen.pill_min_height)
|
||||||
setChipIconSizeResource(R.dimen.pill_avatar_size)
|
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)
|
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>?) {
|
private val pillImageSpan = WeakReference(pillImageSpan)
|
||||||
updateWith(drawable)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {
|
override fun onResourceReady(drawable: Drawable, transition: Transition<in Drawable>?) {
|
||||||
updateWith(placeholder)
|
updateWith(drawable)
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateWith(drawable: Drawable?) {
|
|
||||||
pillImageSpan.get()?.apply {
|
|
||||||
this.updateAvatarDrawable(drawable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
updateWith(placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateWith(drawable: Drawable?) {
|
||||||
|
pillImageSpan.get()?.apply {
|
||||||
|
updateAvatarDrawable(drawable)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user