diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt index f19d4f5a..42823a75 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt @@ -23,8 +23,9 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventCo import kotlinx.android.synthetic.main.fragment_room_detail.* import org.koin.android.ext.android.inject import org.koin.core.parameter.parametersOf +import timber.log.Timber -class RoomDetailFragment : RiotFragment() { +class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback { companion object { @@ -80,6 +81,7 @@ class RoomDetailFragment : RiotFragment() { recyclerView.layoutManager = layoutManager timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) } recyclerView.setController(timelineEventController) + timelineEventController.callback = this } private fun renderRoomSummary(roomSummary: RoomSummary?) { @@ -100,4 +102,10 @@ class RoomDetailFragment : RiotFragment() { timelineEventController.timeline = events } + // TimelineEventController.Callback ************************************************************ + + override fun onUrlClicked(url: String) { + Timber.v("Url clicked: $url") + } + } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt index 52ae2159..fb7ac6dd 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItem.kt @@ -3,6 +3,8 @@ package im.vector.riotredesign.features.home.room.detail.timeline import android.view.View import android.widget.ImageView 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.core.epoxy.KotlinModel import im.vector.riotredesign.features.home.AvatarRenderer @@ -12,7 +14,8 @@ data class MessageItem( val time: CharSequence? = null, val avatarUrl: String?, val memberName: CharSequence? = null, - val showInformation: Boolean = true + val showInformation: Boolean = true, + val onUrlClickedListener: ((url: String) -> Unit)? = null ) : KotlinModel(R.layout.item_event_message) { private val avatarImageView by bind(R.id.messageAvatarImageView) @@ -22,6 +25,11 @@ data class MessageItem( override fun bind() { messageView.text = message + MatrixUrlLinkify.addLinks(messageView, object : MatrixURLSpan.Callback { + override fun onUrlClicked(url: String) { + onUrlClickedListener?.invoke(url) + } + }) if (showInformation) { avatarImageView.visibility = View.VISIBLE memberNameView.visibility = View.VISIBLE diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt index a519e4c5..407a4e7a 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/MessageItemFactory.kt @@ -9,7 +9,13 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte private val messagesDisplayedWithInformation = HashSet() - 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 roomMember = event.roomMember if (messageContent == null || roomMember == null) { @@ -20,13 +26,13 @@ class MessageItemFactory(private val timelineDateFormatter: TimelineDateFormatte messagesDisplayedWithInformation.add(event.root.eventId) } val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId) - return MessageItem( message = messageContent.body, avatarUrl = roomMember.avatarUrl, showInformation = showInformation, time = timelineDateFormatter.formatMessageHour(date), - memberName = roomMember.displayName ?: event.root.sender + memberName = roomMember.displayName ?: event.root.sender, + onUrlClickedListener = { callback?.onUrlClicked(it) } ) } diff --git a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt index fd9ecd9c..73888167 100644 --- a/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt +++ b/app/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt @@ -44,6 +44,8 @@ class TimelineEventController(private val roomId: String, buildSnapshotList() } + var callback: Callback? = null + override fun buildModels() { buildModels(snapshotList) } @@ -61,8 +63,8 @@ class TimelineEventController(private val roomId: String, val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val item = when (event.root.type) { - EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date) - else -> textItemFactory.create(event) + EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, addDaySeparator, date, callback) + else -> textItemFactory.create(event) } item ?.onBind { timeline?.loadAround(index) } @@ -87,4 +89,8 @@ class TimelineEventController(private val roomId: String, requestModelBuild() } + interface Callback { + fun onUrlClicked(url: String) + } + } \ No newline at end of file diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt index 991526e0..fe6de865 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixUrlLinkify.kt @@ -1,34 +1,69 @@ package im.vector.matrix.android.api.permalinks import android.text.Spannable -import android.text.SpannableStringBuilder +import android.text.SpannableString +import android.text.method.LinkMovementMethod +import android.widget.TextView object MatrixUrlLinkify { /** * 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 - if (spannableStringBuilder.isEmpty()) { - return + if (spannable.isNullOrEmpty()) { + return false } - val text = spannableStringBuilder.toString() + val text = spannable.toString() + var hasMatch = false for (index in MatrixPatterns.MATRIX_PATTERNS.indices) { val pattern = MatrixPatterns.MATRIX_PATTERNS[index] - val matcher = pattern.matcher(spannableStringBuilder) + val matcher = pattern.matcher(spannable) while (matcher.find()) { + hasMatch = true val startPos = matcher.start(0) if (startPos == 0 || text[startPos - 1] != '/') { val endPos = matcher.end(0) val url = text.substring(matcher.start(0), matcher.end(0)) 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() + } + } }