Use addLinks method on message. Next step is to proceed the url to navigate.

This commit is contained in:
ganfra 2018-12-19 11:50:44 +01:00
parent 58f60eaab4
commit fdd4642cbb
5 changed files with 78 additions and 15 deletions

View File

@ -23,8 +23,9 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventCo
import kotlinx.android.synthetic.main.fragment_room_detail.* import kotlinx.android.synthetic.main.fragment_room_detail.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
import timber.log.Timber


class RoomDetailFragment : RiotFragment() { class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {


companion object { companion object {


@ -80,6 +81,7 @@ class RoomDetailFragment : RiotFragment() {
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) }
recyclerView.setController(timelineEventController) recyclerView.setController(timelineEventController)
timelineEventController.callback = this
} }


private fun renderRoomSummary(roomSummary: RoomSummary?) { private fun renderRoomSummary(roomSummary: RoomSummary?) {
@ -100,4 +102,10 @@ class RoomDetailFragment : RiotFragment() {
timelineEventController.timeline = events timelineEventController.timeline = events
} }


// TimelineEventController.Callback ************************************************************

override fun onUrlClicked(url: String) {
Timber.v("Url clicked: $url")
}

} }

View File

@ -3,6 +3,8 @@ package im.vector.riotredesign.features.home.room.detail.timeline
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import im.vector.matrix.android.api.permalinks.MatrixURLSpan
import im.vector.matrix.android.api.permalinks.MatrixUrlLinkify
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.epoxy.KotlinModel import im.vector.riotredesign.core.epoxy.KotlinModel
import im.vector.riotredesign.features.home.AvatarRenderer import im.vector.riotredesign.features.home.AvatarRenderer
@ -12,7 +14,8 @@ data class MessageItem(
val time: CharSequence? = null, val time: CharSequence? = null,
val avatarUrl: String?, val avatarUrl: String?,
val memberName: CharSequence? = null, val memberName: CharSequence? = null,
val showInformation: Boolean = true val showInformation: Boolean = true,
val onUrlClickedListener: ((url: String) -> Unit)? = null
) : KotlinModel(R.layout.item_event_message) { ) : KotlinModel(R.layout.item_event_message) {


private val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView) private val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
@ -22,6 +25,11 @@ data class MessageItem(


override fun bind() { override fun bind() {
messageView.text = message messageView.text = message
MatrixUrlLinkify.addLinks(messageView, object : MatrixURLSpan.Callback {
override fun onUrlClicked(url: String) {
onUrlClickedListener?.invoke(url)
}
})
if (showInformation) { if (showInformation) {
avatarImageView.visibility = View.VISIBLE avatarImageView.visibility = View.VISIBLE
memberNameView.visibility = View.VISIBLE memberNameView.visibility = View.VISIBLE

View File

@ -9,7 +9,13 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte


private val messagesDisplayedWithInformation = HashSet<String?>() private val messagesDisplayedWithInformation = HashSet<String?>()


fun create(event: EnrichedEvent, nextEvent: EnrichedEvent?, addDaySeparator: Boolean, date: LocalDateTime): MessageItem? { fun create(event: EnrichedEvent,
nextEvent: EnrichedEvent?,
addDaySeparator: Boolean,
date: LocalDateTime,
callback: TimelineEventController.Callback?
): MessageItem? {

val messageContent: MessageContent? = event.root.content.toModel() val messageContent: MessageContent? = event.root.content.toModel()
val roomMember = event.roomMember val roomMember = event.roomMember
if (messageContent == null || roomMember == null) { if (messageContent == null || roomMember == null) {
@ -20,13 +26,13 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte
messagesDisplayedWithInformation.add(event.root.eventId) messagesDisplayedWithInformation.add(event.root.eventId)
} }
val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId) val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId)

return MessageItem( return MessageItem(
message = messageContent.body, message = messageContent.body,
avatarUrl = roomMember.avatarUrl, avatarUrl = roomMember.avatarUrl,
showInformation = showInformation, showInformation = showInformation,
time = timelineDateFormatter.formatMessageHour(date), time = timelineDateFormatter.formatMessageHour(date),
memberName = roomMember.displayName ?: event.root.sender memberName = roomMember.displayName ?: event.root.sender,
onUrlClickedListener = { callback?.onUrlClicked(it) }
) )
} }



View File

@ -44,6 +44,8 @@ class TimelineEventController(private val roomId: String,
buildSnapshotList() buildSnapshotList()
} }


var callback: Callback? = null

override fun buildModels() { override fun buildModels() {
buildModels(snapshotList) buildModels(snapshotList)
} }
@ -61,8 +63,8 @@ class TimelineEventController(private val roomId: String,
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()


val item = when (event.root.type) { val item = when (event.root.type) {
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date) EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback)
else -> textItemFactory.create(event) else -> textItemFactory.create(event)
} }
item item
?.onBind { timeline?.loadAround(index) } ?.onBind { timeline?.loadAround(index) }
@ -87,4 +89,8 @@ class TimelineEventController(private val roomId: String,
requestModelBuild() requestModelBuild()
} }


interface Callback {
fun onUrlClicked(url: String)
}

} }

View File

@ -1,34 +1,69 @@
package im.vector.matrix.android.api.permalinks package im.vector.matrix.android.api.permalinks


import android.text.Spannable import android.text.Spannable
import android.text.SpannableStringBuilder import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.widget.TextView


object MatrixUrlLinkify { object MatrixUrlLinkify {


/** /**
* Find the matrix spans i.e matrix id , user id ... to display them as URL. * Find the matrix spans i.e matrix id , user id ... to display them as URL.
* *
* @param spannableStringBuilder the text in which the matrix items has to be clickable. * @param spannable the text in which the matrix items has to be clickable.
*/ */
fun addLinks(spannableStringBuilder: SpannableStringBuilder, callback: MatrixURLSpan.Callback?) { fun addLinks(spannable: Spannable?, callback: MatrixURLSpan.Callback?): Boolean {
// sanity checks // sanity checks
if (spannableStringBuilder.isEmpty()) { if (spannable.isNullOrEmpty()) {
return return false
} }
val text = spannableStringBuilder.toString() val text = spannable.toString()
var hasMatch = false
for (index in MatrixPatterns.MATRIX_PATTERNS.indices) { for (index in MatrixPatterns.MATRIX_PATTERNS.indices) {
val pattern = MatrixPatterns.MATRIX_PATTERNS[index] val pattern = MatrixPatterns.MATRIX_PATTERNS[index]
val matcher = pattern.matcher(spannableStringBuilder) val matcher = pattern.matcher(spannable)
while (matcher.find()) { while (matcher.find()) {
hasMatch = true
val startPos = matcher.start(0) val startPos = matcher.start(0)
if (startPos == 0 || text[startPos - 1] != '/') { if (startPos == 0 || text[startPos - 1] != '/') {
val endPos = matcher.end(0) val endPos = matcher.end(0)
val url = text.substring(matcher.start(0), matcher.end(0)) val url = text.substring(matcher.start(0), matcher.end(0))
val span = MatrixURLSpan(url, callback) val span = MatrixURLSpan(url, callback)
spannableStringBuilder.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} }
} }
} }
return hasMatch
}

fun addLinks(textView: TextView, callback: MatrixURLSpan.Callback?): Boolean {
val text = textView.text
if (text is Spannable) {
if (addLinks(text, callback)) {
addLinkMovementMethod(textView)
return true
}

return false
} else {
val spannableString = SpannableString.valueOf(text)
if (addLinks(spannableString, callback)) {
addLinkMovementMethod(textView)
textView.text = spannableString
return true
}
return false
}
}


private fun addLinkMovementMethod(textView: TextView) {
val movementMethod = textView.movementMethod
if (movementMethod == null || movementMethod !is LinkMovementMethod) {
if (textView.linksClickable) {
textView.movementMethod = LinkMovementMethod.getInstance()
}
}
} }