From 998d9f2c5996f6abc85bb8c260fe718f60fe2e53 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 3 Dec 2019 14:48:41 +0100
Subject: [PATCH 1/6] Bugfix: Text after the last pill was not send

---
 .../internal/session/room/send/pills/TextPillsUtils.kt        | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt
index 02f48e5800..580e49b2ce 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/pills/TextPillsUtils.kt
@@ -62,10 +62,14 @@ internal class TextPillsUtils @Inject constructor(
             var currIndex = 0
             pills.forEachIndexed { _, (urlSpan, start, end) ->
                 // We want to replace with the pill with a html link
+                // append text before pill
                 append(text, currIndex, start)
+                // append the pill
                 append(String.format(template, urlSpan.userId, urlSpan.displayName))
                 currIndex = end
             }
+            // append text after the last pill
+            append(text, currIndex, text.length)
         }
     }
 

From 71de8fdad3cfeedec0c74ace7b4ca2769338aaf8 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 3 Dec 2019 15:05:41 +0100
Subject: [PATCH 2/6] Display pills Avatar in the message preview

---
 .../BottomSheetItemMessagePreview.kt          |  2 ++
 .../detail/timeline/item/MessageTextItem.kt   | 20 ++---------
 .../timeline/tools/EventRenderingTools.kt     | 34 +++++++++++++++++++
 3 files changed, 38 insertions(+), 18 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt

diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
index 999068b289..3e3f1d3cf2 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
@@ -25,6 +25,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
 import im.vector.riotx.core.epoxy.VectorEpoxyModel
 import im.vector.riotx.core.extensions.setTextOrHide
 import im.vector.riotx.features.home.AvatarRenderer
+import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProcess
 
 /**
  * A message preview for bottom sheet.
@@ -49,6 +50,7 @@ abstract class BottomSheetItemMessagePreview : VectorEpoxyModel<BottomSheetItemM
         avatarRenderer.render(avatarUrl, senderId, senderName, holder.avatar)
         holder.sender.setTextOrHide(senderName)
         holder.body.text = body
+        body.findPillsAndProcess { it.bind(holder.body) }
         holder.timestamp.setTextOrHide(time)
     }
 
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
index 45a6e2e743..cbdc192425 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -19,18 +19,13 @@ package im.vector.riotx.features.home.room.detail.timeline.item
 import android.view.MotionEvent
 import androidx.appcompat.widget.AppCompatTextView
 import androidx.core.text.PrecomputedTextCompat
-import androidx.core.text.toSpannable
 import androidx.core.widget.TextViewCompat
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.riotx.R
 import im.vector.riotx.core.utils.isValidUrl
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
-import im.vector.riotx.features.html.PillImageSpan
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProcess
 import me.saket.bettermovementmethod.BetterLinkMovementMethod
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@@ -76,7 +71,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
         holder.messageView.setOnClickListener(attributes.itemClickListener)
         holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
         if (searchForPills) {
-            findPillsAndProcess { it.bind(holder.messageView) }
+            message?.findPillsAndProcess { it.bind(holder.messageView) }
         }
         val textFuture = PrecomputedTextCompat.getTextFuture(
                 message ?: "",
@@ -85,17 +80,6 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
         holder.messageView.setTextFuture(textFuture)
     }
 
-    private fun findPillsAndProcess(processBlock: (span: PillImageSpan) -> Unit) {
-        GlobalScope.launch(Dispatchers.Main) {
-            val pillImageSpans: Array<PillImageSpan>? = withContext(Dispatchers.IO) {
-                message?.toSpannable()?.let { spannable ->
-                    spannable.getSpans(0, spannable.length, PillImageSpan::class.java)
-                }
-            }
-            pillImageSpans?.forEach { processBlock(it) }
-        }
-    }
-
     override fun getViewType() = STUB_ID
 
     class Holder : AbsMessageItem.Holder(STUB_ID) {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt
new file mode 100644
index 0000000000..685799cd32
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.riotx.features.home.room.detail.timeline.tools
+
+import androidx.core.text.toSpannable
+import im.vector.riotx.features.html.PillImageSpan
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+fun CharSequence.findPillsAndProcess(processBlock: (PillImageSpan) -> Unit) {
+    GlobalScope.launch(Dispatchers.Main) {
+        withContext(Dispatchers.IO) {
+            toSpannable().let { spannable ->
+                spannable.getSpans(0, spannable.length, PillImageSpan::class.java)
+            }
+        }.forEach { processBlock(it) }
+    }
+}

From 6d7f2670df15ef32baa00497f122b46f2945273e Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 3 Dec 2019 16:02:07 +0100
Subject: [PATCH 3/6] Make url clickable on the preview of event in the bottom
 sheet

---
 .../BottomSheetItemMessagePreview.kt          |  5 +++
 .../home/room/detail/RoomDetailFragment.kt    |  6 +++
 .../timeline/action/EventSharedAction.kt      |  8 ++++
 .../action/MessageActionsBottomSheet.kt       | 12 ++++++
 .../action/MessageActionsEpoxyController.kt   |  7 +++-
 .../timeline/factory/MessageItemFactory.kt    | 34 +++++----------
 .../detail/timeline/item/MessageTextItem.kt   | 25 +----------
 .../timeline/tools/EventRenderingTools.kt     | 42 +++++++++++++++++++
 vector/src/main/res/values/styles_riot.xml    |  3 ++
 9 files changed, 93 insertions(+), 49 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
index 3e3f1d3cf2..7f6ff6fc78 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
@@ -25,6 +25,8 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
 import im.vector.riotx.core.epoxy.VectorEpoxyModel
 import im.vector.riotx.core.extensions.setTextOrHide
 import im.vector.riotx.features.home.AvatarRenderer
+import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
+import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProcess
 
 /**
@@ -45,10 +47,13 @@ abstract class BottomSheetItemMessagePreview : VectorEpoxyModel<BottomSheetItemM
     lateinit var body: CharSequence
     @EpoxyAttribute
     var time: CharSequence? = null
+    @EpoxyAttribute
+    var urlClickCallback: TimelineEventController.UrlClickCallback? = null
 
     override fun bind(holder: Holder) {
         avatarRenderer.render(avatarUrl, senderId, senderName, holder.avatar)
         holder.sender.setTextOrHide(senderName)
+        holder.body.movementMethod = createLinkMovementMethod(urlClickCallback)
         holder.body.text = body
         body.findPillsAndProcess { it.bind(holder.body) }
         holder.timestamp.setTextOrHide(time)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
index 59437273d6..31278a1fff 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt
@@ -1163,6 +1163,12 @@ class RoomDetailFragment @Inject constructor(
             is EventSharedAction.IgnoreUser                 -> {
                 roomDetailViewModel.handle(RoomDetailAction.IgnoreUser(action.senderId))
             }
+            is EventSharedAction.OnUrlClicked               -> {
+                onUrlClicked(action.url)
+            }
+            is EventSharedAction.OnUrlLongClicked           -> {
+                onUrlLongClicked(action.url)
+            }
             else                                            -> {
                 Toast.makeText(context, "Action $action is not implemented yet", Toast.LENGTH_LONG).show()
             }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt
index 37d96ad62c..8077786d06 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/EventSharedAction.kt
@@ -88,4 +88,12 @@ sealed class EventSharedAction(@StringRes val titleRes: Int, @DrawableRes val ic
 
     data class ViewEditHistory(val messageInformationData: MessageInformationData) :
             EventSharedAction(R.string.message_view_edit_history, R.drawable.ic_view_edit_history)
+
+    // An url in the event preview has been clicked
+    data class OnUrlClicked(val url: String) :
+            EventSharedAction(0, 0)
+
+    // An url in the event preview has been long clicked
+    data class OnUrlLongClicked(val url: String) :
+            EventSharedAction(0, 0)
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
index 3f4171f733..a5bf6f8558 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
@@ -68,6 +68,18 @@ class MessageActionsBottomSheet : VectorBaseBottomSheetDialogFragment(), Message
         messageActionsEpoxyController.listener = this
     }
 
+    override fun onUrlClicked(url: String): Boolean {
+        sharedActionViewModel.post(EventSharedAction.OnUrlClicked(url))
+        // Always consume
+        return true
+    }
+
+    override fun onUrlLongClicked(url: String): Boolean {
+        sharedActionViewModel.post(EventSharedAction.OnUrlLongClicked(url))
+        // Always consume
+        return true
+    }
+
     override fun didSelectMenuAction(eventAction: EventSharedAction) {
         if (eventAction is EventSharedAction.ReportContent) {
             // Toggle report menu
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
index b561a6df3c..53a7ce0354 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
@@ -23,6 +23,8 @@ import im.vector.riotx.R
 import im.vector.riotx.core.epoxy.bottomsheet.*
 import im.vector.riotx.core.resources.StringProvider
 import im.vector.riotx.features.home.AvatarRenderer
+import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
+import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
 import javax.inject.Inject
 
 /**
@@ -44,7 +46,8 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
                 avatarUrl(state.informationData.avatarUrl ?: "")
                 senderId(state.informationData.senderId)
                 senderName(state.senderName())
-                body(body)
+                urlClickCallback(listener)
+                body(body.linkify(listener))
                 time(state.time())
             }
         }
@@ -127,7 +130,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
         }
     }
 
-    interface MessageActionsEpoxyControllerListener {
+    interface MessageActionsEpoxyControllerListener : TimelineEventController.UrlClickCallback {
         fun didSelectMenuAction(eventAction: EventSharedAction)
     }
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index ac6c563099..417f8d2f9a 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -24,8 +24,6 @@ import android.text.style.ClickableSpan
 import android.text.style.ForegroundColorSpan
 import android.view.View
 import dagger.Lazy
-import im.vector.matrix.android.api.permalinks.MatrixLinkify
-import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
 import im.vector.matrix.android.api.session.events.model.RelationType
 import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.api.session.room.model.message.*
@@ -35,7 +33,6 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
 import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
 import im.vector.riotx.R
 import im.vector.riotx.core.epoxy.VectorEpoxyModel
-import im.vector.riotx.core.linkify.VectorLinkify
 import im.vector.riotx.core.resources.ColorProvider
 import im.vector.riotx.core.resources.StringProvider
 import im.vector.riotx.core.utils.DebouncedClickListener
@@ -45,8 +42,9 @@ import im.vector.riotx.core.utils.isLocalFile
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotx.features.home.room.detail.timeline.helper.*
 import im.vector.riotx.features.home.room.detail.timeline.item.*
-import im.vector.riotx.features.html.EventHtmlRenderer
+import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
 import im.vector.riotx.features.html.CodeVisitor
+import im.vector.riotx.features.html.EventHtmlRenderer
 import im.vector.riotx.features.media.ImageContentRenderer
 import im.vector.riotx.features.media.VideoContentRenderer
 import me.gujun.android.span.span
@@ -89,7 +87,7 @@ class MessageItemFactory @Inject constructor(
             return defaultItemFactory.create(malformedText, informationData, highlight, callback)
         }
         if (messageContent.relatesTo?.type == RelationType.REPLACE
-            || event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
+                || event.isEncrypted() && event.root.content.toModel<EncryptedEventContent>()?.relatesTo?.type == RelationType.REPLACE
         ) {
             // This is an edit event, we should it when debugging as a notice event
             return noticeItemFactory.create(event, highlight, readMarkerVisible, callback)
@@ -195,8 +193,7 @@ class MessageItemFactory @Inject constructor(
         val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
         val thumbnailData = ImageContentRenderer.Data(
                 filename = messageContent.body,
-                url = messageContent.videoInfo?.thumbnailFile?.url
-                      ?: messageContent.videoInfo?.thumbnailUrl,
+                url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
                 elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
                 height = messageContent.videoInfo?.height,
                 maxHeight = maxHeight,
@@ -258,7 +255,7 @@ class MessageItemFactory @Inject constructor(
                                      highlight: Boolean,
                                      callback: TimelineEventController.Callback?,
                                      attributes: AbsMessageItem.Attributes): MessageTextItem? {
-        val linkifiedBody = linkifyBody(body, callback)
+        val linkifiedBody = body.linkify(callback)
 
         return MessageTextItem_().apply {
             if (informationData.hasBeenEdited) {
@@ -326,9 +323,9 @@ class MessageItemFactory @Inject constructor(
                 // nop
             }
         },
-                          editStart,
-                          editEnd,
-                          Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
+                editStart,
+                editEnd,
+                Spanned.SPAN_INCLUSIVE_EXCLUSIVE)
         return spannable
     }
 
@@ -344,7 +341,7 @@ class MessageItemFactory @Inject constructor(
                 textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
                 textStyle = "italic"
             }
-            linkifyBody(formattedBody, callback)
+            formattedBody.linkify(callback)
         }
         return MessageTextItem_()
                 .leftGuideline(avatarSizeProvider.leftGuideline)
@@ -361,7 +358,7 @@ class MessageItemFactory @Inject constructor(
                                       attributes: AbsMessageItem.Attributes): MessageTextItem? {
         val message = messageContent.body.let {
             val formattedBody = "* ${informationData.memberName} $it"
-            linkifyBody(formattedBody, callback)
+            formattedBody.linkify(callback)
         }
         return MessageTextItem_()
                 .apply {
@@ -386,17 +383,6 @@ class MessageItemFactory @Inject constructor(
                 .highlighted(highlight)
     }
 
-    private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence {
-        val spannable = SpannableStringBuilder(body)
-        MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
-            override fun onUrlClicked(url: String) {
-                callback?.onUrlClicked(url)
-            }
-        })
-        VectorLinkify.addLinks(spannable, true)
-        return spannable
-    }
-
     companion object {
         private const val MAX_NUMBER_OF_EMOJI_FOR_BIG_FONT = 5
     }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
index cbdc192425..15aa5aa4cb 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -16,17 +16,15 @@
 
 package im.vector.riotx.features.home.room.detail.timeline.item
 
-import android.view.MotionEvent
 import androidx.appcompat.widget.AppCompatTextView
 import androidx.core.text.PrecomputedTextCompat
 import androidx.core.widget.TextViewCompat
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.riotx.R
-import im.vector.riotx.core.utils.isValidUrl
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
+import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProcess
-import me.saket.bettermovementmethod.BetterLinkMovementMethod
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
 abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@@ -40,28 +38,9 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
     @EpoxyAttribute
     var urlClickCallback: TimelineEventController.UrlClickCallback? = null
 
-    // Better link movement methods fixes the issue when
-    // long pressing to open the context menu on a TextView also triggers an autoLink click.
-    private val mvmtMethod = BetterLinkMovementMethod.newInstance().also {
-        it.setOnLinkClickListener { _, url ->
-            // Return false to let android manage the click on the link, or true if the link is handled by the application
-            url.isValidUrl() && urlClickCallback?.onUrlClicked(url) == true
-        }
-        // We need also to fix the case when long click on link will trigger long click on cell
-        it.setOnLinkLongClickListener { tv, url ->
-            // Long clicks are handled by parent, return true to block android to do something with url
-            if (url.isValidUrl() && urlClickCallback?.onUrlLongClicked(url) == true) {
-                tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0))
-                true
-            } else {
-                false
-            }
-        }
-    }
-
     override fun bind(holder: Holder) {
         super.bind(holder)
-        holder.messageView.movementMethod = mvmtMethod
+        holder.messageView.movementMethod = createLinkMovementMethod(urlClickCallback)
         if (useBigFont) {
             holder.messageView.textSize = 44F
         } else {
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt
index 685799cd32..492248985e 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/tools/EventRenderingTools.kt
@@ -16,12 +16,20 @@
 
 package im.vector.riotx.features.home.room.detail.timeline.tools
 
+import android.text.SpannableStringBuilder
+import android.view.MotionEvent
 import androidx.core.text.toSpannable
+import im.vector.matrix.android.api.permalinks.MatrixLinkify
+import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
+import im.vector.riotx.core.linkify.VectorLinkify
+import im.vector.riotx.core.utils.isValidUrl
+import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotx.features.html.PillImageSpan
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
+import me.saket.bettermovementmethod.BetterLinkMovementMethod
 
 fun CharSequence.findPillsAndProcess(processBlock: (PillImageSpan) -> Unit) {
     GlobalScope.launch(Dispatchers.Main) {
@@ -32,3 +40,37 @@ fun CharSequence.findPillsAndProcess(processBlock: (PillImageSpan) -> Unit) {
         }.forEach { processBlock(it) }
     }
 }
+
+fun CharSequence.linkify(callback: TimelineEventController.UrlClickCallback?): CharSequence {
+    val spannable = SpannableStringBuilder(this)
+    MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
+        override fun onUrlClicked(url: String) {
+            callback?.onUrlClicked(url)
+        }
+    })
+    VectorLinkify.addLinks(spannable, true)
+    return spannable
+}
+
+// Better link movement methods fixes the issue when
+// long pressing to open the context menu on a TextView also triggers an autoLink click.
+fun createLinkMovementMethod(urlClickCallback: TimelineEventController.UrlClickCallback?): BetterLinkMovementMethod {
+    return BetterLinkMovementMethod.newInstance()
+            .apply {
+                setOnLinkClickListener { _, url ->
+                    // Return false to let android manage the click on the link, or true if the link is handled by the application
+                    url.isValidUrl() && urlClickCallback?.onUrlClicked(url) == true
+                }
+
+                // We need also to fix the case when long click on link will trigger long click on cell
+                setOnLinkLongClickListener { tv, url ->
+                    // Long clicks are handled by parent, return true to block android to do something with url
+                    if (url.isValidUrl() && urlClickCallback?.onUrlLongClicked(url) == true) {
+                        tv.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0))
+                        true
+                    } else {
+                        false
+                    }
+                }
+            }
+}
diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml
index 798c7ced87..ea41a3c7ca 100644
--- a/vector/src/main/res/values/styles_riot.xml
+++ b/vector/src/main/res/values/styles_riot.xml
@@ -266,6 +266,7 @@
         <item name="android:textColorSecondary">@color/riot_secondary_text_color_dark</item>
         <!-- Default color for text View -->
         <item name="android:textColorTertiary">@color/riot_tertiary_text_color_dark</item>
+        <item name="android:textColorLink">@color/riotx_links</item>
     </style>
 
     <style name="Vector.BottomSheet.Light" parent="Theme.Design.Light.BottomSheetDialog">
@@ -273,6 +274,7 @@
         <item name="android:textColorSecondary">@color/riot_secondary_text_color_light</item>
         <!-- Default color for text View -->
         <item name="android:textColorTertiary">@color/riot_tertiary_text_color_light</item>
+        <item name="android:textColorLink">@color/riotx_links</item>
     </style>
 
     <style name="Vector.BottomSheet.Status" parent="Theme.Design.Light.BottomSheetDialog">
@@ -280,6 +282,7 @@
         <item name="android:textColorSecondary">@color/riot_secondary_text_color_status</item>
         <!-- Default color for text View -->
         <item name="android:textColorTertiary">@color/riot_tertiary_text_color_status</item>
+        <item name="android:textColorLink">@color/link_color_status</item>
     </style>
 
 

From c69852c849f548b7f7bf7f9ce94e35fb6ece4fab Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 3 Dec 2019 16:17:49 +0100
Subject: [PATCH 4/6] Make url clickable on the preview of event in the bottom
 sheet - avoid instantiating objects in the bind() method

---
 .../epoxy/bottomsheet/BottomSheetItemMessagePreview.kt     | 7 +++----
 .../timeline/action/MessageActionsEpoxyController.kt       | 3 ++-
 .../room/detail/timeline/factory/EncryptedItemFactory.kt   | 7 ++++---
 .../room/detail/timeline/factory/MessageItemFactory.kt     | 7 ++++---
 .../home/room/detail/timeline/item/MessageTextItem.kt      | 7 +++----
 5 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
index 7f6ff6fc78..fdad24c34e 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
@@ -16,6 +16,7 @@
  */
 package im.vector.riotx.core.epoxy.bottomsheet
 
+import android.text.method.MovementMethod
 import android.widget.ImageView
 import android.widget.TextView
 import com.airbnb.epoxy.EpoxyAttribute
@@ -25,8 +26,6 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
 import im.vector.riotx.core.epoxy.VectorEpoxyModel
 import im.vector.riotx.core.extensions.setTextOrHide
 import im.vector.riotx.features.home.AvatarRenderer
-import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
-import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProcess
 
 /**
@@ -48,12 +47,12 @@ abstract class BottomSheetItemMessagePreview : VectorEpoxyModel<BottomSheetItemM
     @EpoxyAttribute
     var time: CharSequence? = null
     @EpoxyAttribute
-    var urlClickCallback: TimelineEventController.UrlClickCallback? = null
+    var movementMethod: MovementMethod? = null
 
     override fun bind(holder: Holder) {
         avatarRenderer.render(avatarUrl, senderId, senderName, holder.avatar)
         holder.sender.setTextOrHide(senderName)
-        holder.body.movementMethod = createLinkMovementMethod(urlClickCallback)
+        holder.body.movementMethod = movementMethod
         holder.body.text = body
         body.findPillsAndProcess { it.bind(holder.body) }
         holder.timestamp.setTextOrHide(time)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
index 53a7ce0354..59ee21adfe 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
@@ -24,6 +24,7 @@ import im.vector.riotx.core.epoxy.bottomsheet.*
 import im.vector.riotx.core.resources.StringProvider
 import im.vector.riotx.features.home.AvatarRenderer
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
+import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
 import javax.inject.Inject
 
@@ -46,7 +47,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
                 avatarUrl(state.informationData.avatarUrl ?: "")
                 senderId(state.informationData.senderId)
                 senderName(state.senderName())
-                urlClickCallback(listener)
+                movementMethod(createLinkMovementMethod(listener))
                 body(body.linkify(listener))
                 time(state.time())
             }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
index e67507d7bb..c7aca768dc 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/EncryptedItemFactory.kt
@@ -25,9 +25,10 @@ import im.vector.riotx.core.resources.ColorProvider
 import im.vector.riotx.core.resources.StringProvider
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotx.features.home.room.detail.timeline.helper.AvatarSizeProvider
-import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
 import im.vector.riotx.features.home.room.detail.timeline.helper.MessageInformationDataFactory
 import im.vector.riotx.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
+import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
+import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import me.gujun.android.span.span
 import javax.inject.Inject
 
@@ -57,7 +58,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
                         }
 
                 val message = stringProvider.getString(R.string.encrypted_message).takeIf { cryptoError == null }
-                              ?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
+                        ?: stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
                 val spannableStr = span(message) {
                     textStyle = "italic"
                     textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_secondary)
@@ -72,7 +73,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
                         .highlighted(highlight)
                         .attributes(attributes)
                         .message(spannableStr)
-                        .urlClickCallback(callback)
+                        .movementMethod(createLinkMovementMethod(callback))
             }
             else                                             -> null
         }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 417f8d2f9a..de2686de04 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -42,6 +42,7 @@ import im.vector.riotx.core.utils.isLocalFile
 import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotx.features.home.room.detail.timeline.helper.*
 import im.vector.riotx.features.home.room.detail.timeline.item.*
+import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.riotx.features.home.room.detail.timeline.tools.linkify
 import im.vector.riotx.features.html.CodeVisitor
 import im.vector.riotx.features.html.EventHtmlRenderer
@@ -270,7 +271,7 @@ class MessageItemFactory @Inject constructor(
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .attributes(attributes)
                 .highlighted(highlight)
-                .urlClickCallback(callback)
+                .movementMethod(createLinkMovementMethod(callback))
     }
 
     private fun buildCodeBlockItem(formattedBody: CharSequence,
@@ -348,7 +349,7 @@ class MessageItemFactory @Inject constructor(
                 .attributes(attributes)
                 .message(message)
                 .highlighted(highlight)
-                .urlClickCallback(callback)
+                .movementMethod(createLinkMovementMethod(callback))
     }
 
     private fun buildEmoteMessageItem(messageContent: MessageEmoteContent,
@@ -372,7 +373,7 @@ class MessageItemFactory @Inject constructor(
                 .leftGuideline(avatarSizeProvider.leftGuideline)
                 .attributes(attributes)
                 .highlighted(highlight)
-                .urlClickCallback(callback)
+                .movementMethod(createLinkMovementMethod(callback))
     }
 
     private fun buildRedactedItem(attributes: AbsMessageItem.Attributes,
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
index 15aa5aa4cb..5ee0576be7 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -16,14 +16,13 @@
 
 package im.vector.riotx.features.home.room.detail.timeline.item
 
+import android.text.method.MovementMethod
 import androidx.appcompat.widget.AppCompatTextView
 import androidx.core.text.PrecomputedTextCompat
 import androidx.core.widget.TextViewCompat
 import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.riotx.R
-import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
-import im.vector.riotx.features.home.room.detail.timeline.tools.createLinkMovementMethod
 import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProcess
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
@@ -36,11 +35,11 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
     @EpoxyAttribute
     var useBigFont: Boolean = false
     @EpoxyAttribute
-    var urlClickCallback: TimelineEventController.UrlClickCallback? = null
+    var movementMethod: MovementMethod? = null
 
     override fun bind(holder: Holder) {
         super.bind(holder)
-        holder.messageView.movementMethod = createLinkMovementMethod(urlClickCallback)
+        holder.messageView.movementMethod = movementMethod
         if (useBigFont) {
             holder.messageView.textSize = 44F
         } else {

From 69f923383cfffd85cbeb84ded6676138e0cced6d Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 3 Dec 2019 16:32:25 +0100
Subject: [PATCH 5/6] Rename some classes with "Item" suffix, as a convention
 (ooi)

---
 .../features/debug/sas/SasEmojiController.kt  |  2 +-
 .../sas/{ItemSasEmoji.kt => SasEmojiItem.kt}  |  2 +-
 ...ItemAction.kt => BottomSheetActionItem.kt} |  2 +-
 ...ew.kt => BottomSheetMessagePreviewItem.kt} |  2 +-
 ...ns.kt => BottomSheetQuickReactionsItem.kt} |  2 +-
 ...eview.kt => BottomSheetRoomPreviewItem.kt} |  2 +-
 ...ndState.kt => BottomSheetSendStateItem.kt} |  2 +-
 ...parator.kt => BottomSheetSeparatorItem.kt} |  2 +-
 .../action/MessageActionsEpoxyController.kt   | 20 +++++++++----------
 .../RoomListQuickActionsEpoxyController.kt    | 14 ++++++-------
 10 files changed, 25 insertions(+), 25 deletions(-)
 rename vector/src/debug/java/im/vector/riotx/features/debug/sas/{ItemSasEmoji.kt => SasEmojiItem.kt} (96%)
 rename vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/{BottomSheetItemAction.kt => BottomSheetActionItem.kt} (97%)
 rename vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/{BottomSheetItemMessagePreview.kt => BottomSheetMessagePreviewItem.kt} (95%)
 rename vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/{BottomSheetItemQuickReactions.kt => BottomSheetQuickReactionsItem.kt} (96%)
 rename vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/{BottomSheetItemRoomPreview.kt => BottomSheetRoomPreviewItem.kt} (95%)
 rename vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/{BottomSheetItemSendState.kt => BottomSheetSendStateItem.kt} (94%)
 rename vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/{BottomSheetItemSeparator.kt => BottomSheetSeparatorItem.kt} (90%)

diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiController.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiController.kt
index daf432fb45..6804828b20 100644
--- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiController.kt
+++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiController.kt
@@ -29,7 +29,7 @@ class SasEmojiController : TypedEpoxyController<SasState>() {
         if (data == null) return
 
         data.emojiList.forEachIndexed { idx, emojiRepresentation ->
-            itemSasEmoji {
+            sasEmojiItem {
                 id(idx)
                 index(idx)
                 emojiRepresentation(emojiRepresentation)
diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/sas/ItemSasEmoji.kt b/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt
similarity index 96%
rename from vector/src/debug/java/im/vector/riotx/features/debug/sas/ItemSasEmoji.kt
rename to vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt
index 92d9bc0b11..cf35873f6b 100644
--- a/vector/src/debug/java/im/vector/riotx/features/debug/sas/ItemSasEmoji.kt
+++ b/vector/src/debug/java/im/vector/riotx/features/debug/sas/SasEmojiItem.kt
@@ -25,7 +25,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
 import im.vector.riotx.core.epoxy.VectorEpoxyModel
 
 @EpoxyModelClass(layout = im.vector.riotx.R.layout.item_sas_emoji)
-abstract class ItemSasEmoji : VectorEpoxyModel<ItemSasEmoji.Holder>() {
+abstract class SasEmojiItem : VectorEpoxyModel<SasEmojiItem.Holder>() {
 
     @EpoxyAttribute
     var index: Int = 0
diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemAction.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetActionItem.kt
similarity index 97%
rename from vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemAction.kt
rename to vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetActionItem.kt
index 483650a434..c55dbdde8a 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemAction.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetActionItem.kt
@@ -37,7 +37,7 @@ import im.vector.riotx.features.themes.ThemeUtils
  * A action for bottom sheet.
  */
 @EpoxyModelClass(layout = R.layout.item_bottom_sheet_action)
-abstract class BottomSheetItemAction : VectorEpoxyModel<BottomSheetItemAction.Holder>() {
+abstract class BottomSheetActionItem : VectorEpoxyModel<BottomSheetActionItem.Holder>() {
 
     @EpoxyAttribute
     @DrawableRes
diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
similarity index 95%
rename from vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
rename to vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
index fdad24c34e..8105d7a7c0 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemMessagePreview.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetMessagePreviewItem.kt
@@ -32,7 +32,7 @@ import im.vector.riotx.features.home.room.detail.timeline.tools.findPillsAndProc
  * A message preview for bottom sheet.
  */
 @EpoxyModelClass(layout = R.layout.item_bottom_sheet_message_preview)
-abstract class BottomSheetItemMessagePreview : VectorEpoxyModel<BottomSheetItemMessagePreview.Holder>() {
+abstract class BottomSheetMessagePreviewItem : VectorEpoxyModel<BottomSheetMessagePreviewItem.Holder>() {
 
     @EpoxyAttribute
     lateinit var avatarRenderer: AvatarRenderer
diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemQuickReactions.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt
similarity index 96%
rename from vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemQuickReactions.kt
rename to vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt
index 4483340d02..aad033601d 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemQuickReactions.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetQuickReactionsItem.kt
@@ -29,7 +29,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
  * A quick reaction list for bottom sheet.
  */
 @EpoxyModelClass(layout = R.layout.item_bottom_sheet_quick_reaction)
-abstract class BottomSheetItemQuickReactions : VectorEpoxyModel<BottomSheetItemQuickReactions.Holder>() {
+abstract class BottomSheetQuickReactionsItem : VectorEpoxyModel<BottomSheetQuickReactionsItem.Holder>() {
 
     @EpoxyAttribute
     lateinit var fontProvider: EmojiCompatFontProvider
diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemRoomPreview.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt
similarity index 95%
rename from vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemRoomPreview.kt
rename to vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt
index 9b9d0fc380..1a5b4e2f66 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemRoomPreview.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetRoomPreviewItem.kt
@@ -31,7 +31,7 @@ import im.vector.riotx.features.home.AvatarRenderer
  * A room preview for bottom sheet.
  */
 @EpoxyModelClass(layout = R.layout.item_bottom_sheet_room_preview)
-abstract class BottomSheetItemRoomPreview : VectorEpoxyModel<BottomSheetItemRoomPreview.Holder>() {
+abstract class BottomSheetRoomPreviewItem : VectorEpoxyModel<BottomSheetRoomPreviewItem.Holder>() {
 
     @EpoxyAttribute
     lateinit var avatarRenderer: AvatarRenderer
diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemSendState.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt
similarity index 94%
rename from vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemSendState.kt
rename to vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt
index 08d727cfa9..8f830ba706 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemSendState.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetSendStateItem.kt
@@ -30,7 +30,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyModel
  * A send state for bottom sheet.
  */
 @EpoxyModelClass(layout = R.layout.item_bottom_sheet_message_status)
-abstract class BottomSheetItemSendState : VectorEpoxyModel<BottomSheetItemSendState.Holder>() {
+abstract class BottomSheetSendStateItem : VectorEpoxyModel<BottomSheetSendStateItem.Holder>() {
 
     @EpoxyAttribute
     var showProgress: Boolean = false
diff --git a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemSeparator.kt b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetSeparatorItem.kt
similarity index 90%
rename from vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemSeparator.kt
rename to vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetSeparatorItem.kt
index fddf507bf9..dd41d5dd66 100644
--- a/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetItemSeparator.kt
+++ b/vector/src/main/java/im/vector/riotx/core/epoxy/bottomsheet/BottomSheetSeparatorItem.kt
@@ -22,7 +22,7 @@ import im.vector.riotx.core.epoxy.VectorEpoxyHolder
 import im.vector.riotx.core.epoxy.VectorEpoxyModel
 
 @EpoxyModelClass(layout = R.layout.item_bottom_sheet_divider)
-abstract class BottomSheetItemSeparator : VectorEpoxyModel<BottomSheetItemSeparator.Holder>() {
+abstract class BottomSheetSeparatorItem : VectorEpoxyModel<BottomSheetSeparatorItem.Holder>() {
 
     class Holder : VectorEpoxyHolder()
 }
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
index 59ee21adfe..efbfd3434c 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt
@@ -41,7 +41,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
         // Message preview
         val body = state.messageBody
         if (body != null) {
-            bottomSheetItemMessagePreview {
+            bottomSheetMessagePreviewItem {
                 id("preview")
                 avatarRenderer(avatarRenderer)
                 avatarUrl(state.informationData.avatarUrl ?: "")
@@ -55,13 +55,13 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
 
         // Send state
         if (state.informationData.sendState.isSending()) {
-            bottomSheetItemSendState {
+            bottomSheetSendStateItem {
                 id("send_state")
                 showProgress(true)
                 text(stringProvider.getString(R.string.event_status_sending_message))
             }
         } else if (state.informationData.sendState.hasFailed()) {
-            bottomSheetItemSendState {
+            bottomSheetSendStateItem {
                 id("send_state")
                 showProgress(false)
                 text(stringProvider.getString(R.string.unable_to_send_message))
@@ -72,16 +72,16 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
         // Quick reactions
         if (state.canReact() && state.quickStates is Success) {
             // Separator
-            bottomSheetItemSeparator {
+            bottomSheetSeparatorItem {
                 id("reaction_separator")
             }
 
-            bottomSheetItemQuickReactions {
+            bottomSheetQuickReactionsItem {
                 id("quick_reaction")
                 fontProvider(fontProvider)
                 texts(state.quickStates()?.map { it.reaction }.orEmpty())
                 selecteds(state.quickStates.invoke().map { it.isSelected })
-                listener(object : BottomSheetItemQuickReactions.Listener {
+                listener(object : BottomSheetQuickReactionsItem.Listener {
                     override fun didSelect(emoji: String, selected: Boolean) {
                         listener?.didSelectMenuAction(EventSharedAction.QuickReact(state.eventId, emoji, selected))
                     }
@@ -90,18 +90,18 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
         }
 
         // Separator
-        bottomSheetItemSeparator {
+        bottomSheetSeparatorItem {
             id("actions_separator")
         }
 
         // Action
         state.actions()?.forEachIndexed { index, action ->
             if (action is EventSharedAction.Separator) {
-                bottomSheetItemSeparator {
+                bottomSheetSeparatorItem {
                     id("separator_$index")
                 }
             } else {
-                bottomSheetItemAction {
+                bottomSheetActionItem {
                     id("action_$index")
                     iconRes(action.iconResId)
                     textRes(action.titleRes)
@@ -118,7 +118,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid
                             EventSharedAction.ReportContentInappropriate(action.eventId, action.senderId),
                             EventSharedAction.ReportContentCustom(action.eventId, action.senderId)
                     ).forEachIndexed { indexReport, actionReport ->
-                        bottomSheetItemAction {
+                        bottomSheetActionItem {
                             id("actionReport_$indexReport")
                             subMenuItem(true)
                             iconRes(actionReport.iconResId)
diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt
index 2e17464cc6..84fd5bc6f2 100644
--- a/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/actions/RoomListQuickActionsEpoxyController.kt
@@ -18,9 +18,9 @@ package im.vector.riotx.features.home.room.list.actions
 import android.view.View
 import com.airbnb.epoxy.TypedEpoxyController
 import im.vector.matrix.android.api.session.room.notification.RoomNotificationState
-import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemAction
-import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemRoomPreview
-import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetItemSeparator
+import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetActionItem
+import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetRoomPreviewItem
+import im.vector.riotx.core.epoxy.bottomsheet.bottomSheetSeparatorItem
 import im.vector.riotx.features.home.AvatarRenderer
 import javax.inject.Inject
 
@@ -36,7 +36,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
         val roomSummary = state.roomSummary() ?: return
 
         // Preview
-        bottomSheetItemRoomPreview {
+        bottomSheetRoomPreviewItem {
             id("preview")
             avatarRenderer(avatarRenderer)
             roomName(roomSummary.displayName)
@@ -46,7 +46,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
         }
 
         // Notifications
-        bottomSheetItemSeparator {
+        bottomSheetSeparatorItem {
             id("notifications_separator")
         }
 
@@ -57,7 +57,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
         RoomListQuickActionsSharedAction.NotificationsMute(roomSummary.roomId).toBottomSheetItem(3, selectedRoomState)
 
         // Leave
-        bottomSheetItemSeparator {
+        bottomSheetSeparatorItem {
             id("leave_separator")
         }
         RoomListQuickActionsSharedAction.Leave(roomSummary.roomId).toBottomSheetItem(5)
@@ -72,7 +72,7 @@ class RoomListQuickActionsEpoxyController @Inject constructor(private val avatar
             is RoomListQuickActionsSharedAction.Settings,
             is RoomListQuickActionsSharedAction.Leave                     -> false
         }
-        return bottomSheetItemAction {
+        return bottomSheetActionItem {
             id("action_$index")
             selected(selected)
             iconRes(iconResId)

From ff267ba9bc6d63f87fe712bb25cbd28658e7e419 Mon Sep 17 00:00:00 2001
From: Benoit Marty <benoitm@matrix.org>
Date: Tue, 3 Dec 2019 16:36:44 +0100
Subject: [PATCH 6/6] Update changelog

---
 CHANGES.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGES.md b/CHANGES.md
index 64de4ac68d..3ead09faac 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,7 @@ Features ✨:
 
 Improvements 🙌:
  - Send mention Pills from composer
+ - Links in message preview in the bottom sheet are now active. 
 
 Other changes:
  - Fix a small grammatical error when an empty room list is shown.