Html : continue work on Pills. Still need to find how to handle avatar drawable.

This commit is contained in:
ganfra 2019-02-26 19:35:47 +01:00
parent 41b06bca60
commit 63bf4355b9
5 changed files with 136 additions and 25 deletions

View File

@ -16,16 +16,21 @@


package im.vector.riotredesign.features.home package im.vector.riotredesign.features.home


import android.content.Context
import android.graphics.drawable.Drawable
import android.widget.ImageView import android.widget.ImageView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.amulyakhare.textdrawable.TextDrawable import com.amulyakhare.textdrawable.TextDrawable
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
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.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
import im.vector.riotredesign.core.glide.GlideApp import im.vector.riotredesign.core.glide.GlideApp
import im.vector.riotredesign.core.glide.GlideRequest


object AvatarRenderer { object AvatarRenderer {


@ -41,19 +46,52 @@ object AvatarRenderer {
if (name.isNullOrEmpty()) { if (name.isNullOrEmpty()) {
return return
} }
buildGlideRequest(imageView.context, name, avatarUrl).into(imageView)
}

fun load(context: Context, avatarUrl: String?, name: String?, callback: Callback) {
if (name.isNullOrEmpty()) {
return
}
buildGlideRequest(context, name, avatarUrl).into(CallbackTarget(callback))
}

private fun buildGlideRequest(context: Context, name: String, avatarUrl: String?): GlideRequest<Drawable> {
val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl) val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal) val avatarColor = ContextCompat.getColor(context, R.color.pale_teal)
val isNameUserId = MatrixPatterns.isUserId(name) val isNameUserId = MatrixPatterns.isUserId(name)
val firstLetterIndex = if (isNameUserId) 1 else 0 val firstLetterIndex = if (isNameUserId) 1 else 0
val firstLetter = name[firstLetterIndex].toString().toUpperCase() val firstLetter = name[firstLetterIndex].toString().toUpperCase()
val fallbackDrawable = TextDrawable.builder().buildRound(firstLetter, avatarColor) val fallbackDrawable = TextDrawable.builder().buildRound(firstLetter, avatarColor)

return GlideApp
GlideApp .with(context)
.with(imageView)
.load(resolvedUrl) .load(resolvedUrl)
.placeholder(fallbackDrawable) .placeholder(fallbackDrawable)
.apply(RequestOptions.circleCropTransform()) .apply(RequestOptions.circleCropTransform())
.into(imageView) }

interface Callback {
fun onDrawableUpdated(drawable: Drawable?)
fun onDestroy()
}

private class CallbackTarget(private val callback: Callback) : SimpleTarget<Drawable>() {

override fun onDestroy() {
callback.onDestroy()
}

override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
callback.onDrawableUpdated(resource)
}

override fun onLoadStarted(placeholder: Drawable?) {
callback.onDrawableUpdated(placeholder)
}

override fun onLoadFailed(errorDrawable: Drawable?) {
callback.onDrawableUpdated(errorDrawable)
}
} }





View File

@ -16,25 +16,39 @@


package im.vector.riotredesign.features.home package im.vector.riotredesign.features.home


import im.vector.matrix.android.api.Matrix
import im.vector.riotredesign.features.home.group.SelectedGroupStore import im.vector.riotredesign.features.home.group.SelectedGroupStore
import im.vector.riotredesign.features.home.room.VisibleRoomStore import im.vector.riotredesign.features.home.room.VisibleRoomStore
import im.vector.riotredesign.features.home.room.detail.timeline.* import im.vector.riotredesign.features.home.room.detail.timeline.CallItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.RoomHistoryVisibilityItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.RoomMemberItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.RoomNameItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.RoomTopicItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
import im.vector.riotredesign.features.home.room.list.RoomSummaryController import im.vector.riotredesign.features.home.room.list.RoomSummaryController
import im.vector.riotredesign.features.markdown.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import org.koin.dsl.module.module import org.koin.dsl.module.module


class HomeModule(homeActivity: HomeActivity) { class HomeModule(homeActivity: HomeActivity) {


val definition = module(override = true) { val definition = module(override = true) {


single {
Matrix.getInstance().currentSession
}

single { single {
TimelineDateFormatter(get()) TimelineDateFormatter(get())
} }


single { single {
EventHtmlRenderer(homeActivity) EventHtmlRenderer(homeActivity, get())
} }


single { single {

View File

@ -29,7 +29,7 @@ import im.vector.riotredesign.core.epoxy.RiotEpoxyModel
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.markdown.EventHtmlRenderer import im.vector.riotredesign.features.html.EventHtmlRenderer
import im.vector.riotredesign.features.media.MediaContentRenderer import im.vector.riotredesign.features.media.MediaContentRenderer
import me.gujun.android.span.span import me.gujun.android.span.span



View File

@ -16,14 +16,13 @@
* *
*/ */


package im.vector.riotredesign.features.markdown package im.vector.riotredesign.features.html


import android.content.Context import android.content.Context
import android.text.style.ImageSpan import android.text.style.ImageSpan
import com.google.android.material.chip.ChipDrawable
import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.riotredesign.R import im.vector.matrix.android.api.session.Session
import org.commonmark.node.BlockQuote import org.commonmark.node.BlockQuote
import org.commonmark.node.HtmlBlock import org.commonmark.node.HtmlBlock
import org.commonmark.node.HtmlInline import org.commonmark.node.HtmlInline
@ -50,10 +49,11 @@ import ru.noties.markwon.html.tag.SuperScriptHandler
import ru.noties.markwon.html.tag.UnderlineHandler import ru.noties.markwon.html.tag.UnderlineHandler
import java.util.Arrays.asList import java.util.Arrays.asList


class EventHtmlRenderer(private val context: Context) { class EventHtmlRenderer(private val context: Context,
private val session: Session) {


private val markwon = Markwon.builder(context) private val markwon = Markwon.builder(context)
.usePlugin(MatrixPlugin.create(context)) .usePlugin(MatrixPlugin.create(context, session))
.build() .build()


fun render(text: String): CharSequence { fun render(text: String): CharSequence {
@ -62,7 +62,8 @@ class EventHtmlRenderer(private val context: Context) {


} }


private class MatrixPlugin private constructor(private val context: Context) : AbstractMarkwonPlugin() { private class MatrixPlugin private constructor(private val context: Context,
private val session: Session) : AbstractMarkwonPlugin() {


override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.htmlParser(MarkwonHtmlParserImpl.create()) builder.htmlParser(MarkwonHtmlParserImpl.create())
@ -75,7 +76,7 @@ private class MatrixPlugin private constructor(private val context: Context) : A
ImageHandler.create()) ImageHandler.create())
.addHandler( .addHandler(
"a", "a",
MxLinkHandler(context)) MxLinkHandler(context, session))
.addHandler( .addHandler(
"blockquote", "blockquote",
BlockquoteHandler()) BlockquoteHandler())
@ -127,13 +128,13 @@ private class MatrixPlugin private constructor(private val context: Context) : A


companion object { companion object {


fun create(context: Context): MatrixPlugin { fun create(context: Context, session: Session): MatrixPlugin {
return MatrixPlugin(context) return MatrixPlugin(context, session)
} }
} }
} }


private class MxLinkHandler(private val context: Context) : TagHandler() { private class MxLinkHandler(private val context: Context, private val session: Session) : TagHandler() {


private val linkHandler = LinkHandler() private val linkHandler = LinkHandler()


@ -143,12 +144,9 @@ private class MxLinkHandler(private val context: Context) : TagHandler() {
val permalinkData = PermalinkParser.parse(link) val permalinkData = PermalinkParser.parse(link)
when (permalinkData) { when (permalinkData) {
is PermalinkData.UserLink -> { is PermalinkData.UserLink -> {
val chipDrawable = ChipDrawable.createFromResource(context, R.xml.pill_view) val user = session.getUser(permalinkData.userId) ?: return
chipDrawable.setText(permalinkData.userId) val drawable = PillDrawableFactory.create(context, permalinkData.userId, user)
chipDrawable.textEndPadding = 8f val span = ImageSpan(drawable)
chipDrawable.textStartPadding = 8f
chipDrawable.setBounds(0, 0, chipDrawable.intrinsicWidth, (chipDrawable.intrinsicHeight / 1.5f).toInt())
val span = ImageSpan(chipDrawable)
SpannableBuilder.setSpans( SpannableBuilder.setSpans(
visitor.builder(), visitor.builder(),
span, span,

View File

@ -0,0 +1,61 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.riotredesign.features.html

import android.content.Context
import android.graphics.drawable.Drawable
import com.google.android.material.chip.ChipDrawable
import im.vector.matrix.android.api.session.user.model.User
import im.vector.riotredesign.R
import im.vector.riotredesign.features.home.AvatarRenderer
import java.lang.ref.WeakReference

object PillDrawableFactory {

fun create(context: Context, userId: String, user: User?): Drawable {
val chipDrawable = ChipDrawable.createFromResource(context, R.xml.pill_view).apply {
setText(user?.displayName ?: userId)
textEndPadding = 8f
textStartPadding = 8f
setBounds(0, 0, intrinsicWidth, (intrinsicHeight / 1.5f).toInt())
}
val avatarRendererCallback = AvatarRendererChipCallback(chipDrawable)
// TODO: need to work on getting drawable async
//AvatarRenderer.load(context, user?.avatarUrl, user?.displayName, avatarRendererCallback)
return chipDrawable
}

private class AvatarRendererChipCallback(chipDrawable: ChipDrawable) : AvatarRenderer.Callback {

private val weakChipDrawable = WeakReference<ChipDrawable>(chipDrawable)

override fun onDestroy() {
weakChipDrawable.clear()
}

override fun onDrawableUpdated(drawable: Drawable?) {
weakChipDrawable.get()?.apply {
chipIcon = drawable
setBounds(0, 0, intrinsicWidth, (intrinsicHeight / 1.5f).toInt())
}
}

}


}