From 471170a3e061ece6b4f7e8dfa517e173aa7de7a1 Mon Sep 17 00:00:00 2001 From: Valere Date: Mon, 3 Jun 2019 17:53:04 +0200 Subject: [PATCH 1/2] Fix / click|longclick link interference + some missing long click (image content wrapper) + update markwon version --- matrix-sdk-android/build.gradle | 2 +- .../android/api/permalinks/MatrixLinkify.kt | 38 ++++++------ vector/build.gradle | 3 +- .../action/MessageActionsViewModel.kt | 2 +- .../timeline/factory/MessageItemFactory.kt | 7 +-- .../timeline/factory/TimelineItemFactory.kt | 2 + .../timeline/item/MessageImageVideoItem.kt | 4 ++ .../detail/timeline/item/MessageTextItem.kt | 25 +++++--- .../features/html/EventHtmlRenderer.kt | 12 ++++ .../features/html/FontTagHandler.kt | 60 +++++++++++++++++++ .../res/layout/item_timeline_event_base.xml | 1 + 11 files changed, 119 insertions(+), 37 deletions(-) create mode 100644 vector/src/main/java/im/vector/riotredesign/features/html/FontTagHandler.kt diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 5211a223e6..3fda5a6372 100644 --- a/matrix-sdk-android/build.gradle +++ b/matrix-sdk-android/build.gradle @@ -91,7 +91,7 @@ dependencies { def moshi_version = '1.8.0' def lifecycle_version = '2.0.0' def coroutines_version = "1.0.1" - def markwon_version = '3.0.0-SNAPSHOT' + def markwon_version = '3.0.0' implementation fileTree(dir: 'libs', include: ['*.aar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt index d09db8c476..068b8099fb 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt @@ -57,25 +57,25 @@ object MatrixLinkify { return hasMatch } - fun addLinks(textView: TextView, callback: MatrixPermalinkSpan.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 - } - } +// fun addLinks(textView: TextView, callback: MatrixPermalinkSpan.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 +// } +// } /** * Add linkMovementMethod on textview if not already set diff --git a/vector/build.gradle b/vector/build.gradle index b9e01ed0df..606cdd1915 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -121,7 +121,7 @@ dependencies { def epoxy_version = "3.3.0" def arrow_version = "0.8.2" def coroutines_version = "1.0.1" - def markwon_version = '3.0.0-SNAPSHOT' + def markwon_version = '3.0.0' def big_image_viewer_version = '1.5.6' def glide_version = '4.9.0' def moshi_version = '1.8.0' @@ -173,6 +173,7 @@ dependencies { implementation 'me.gujun.android:span:1.7' implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:html:$markwon_version" + implementation 'me.saket:better-link-movement-method:2.2.0' implementation 'com.otaliastudios:autocomplete:1.1.0' diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index 9198f8ee83..315927e30c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -63,7 +63,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode var body: CharSequence = messageContent?.body ?: "" if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { val parser = Parser.builder().build() - val document = parser.parse(messageContent.formattedBody ?: messageContent.body) + val document = parser.parse(messageContent.formattedBody?.trim() ?: messageContent.body) // val renderer = HtmlRenderer.builder().build() body = Markwon.builder(viewModelContext.activity) .usePlugin(HtmlPlugin.create()).build().render(document) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt index 05fda07463..da5bc66786 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt @@ -288,7 +288,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider, callback: TimelineEventController.Callback?): MessageTextItem? { val bodyToUse = messageContent.formattedBody?.let { - htmlRenderer.render(it) + htmlRenderer.render(it.trim()) } ?: messageContent.body val linkifiedBody = linkifyBody(bodyToUse, callback) @@ -312,11 +312,6 @@ class MessageItemFactory(private val colorProvider: ColorProvider, DebouncedClickListener(View.OnClickListener { view -> callback?.onMemberNameClicked(informationData) })) - //click on the text - .clickListener( - DebouncedClickListener(View.OnClickListener { view -> - callback?.onEventCellClicked(informationData, messageContent, view) - })) .cellClickListener( DebouncedClickListener(View.OnClickListener { view -> callback?.onEventCellClicked(informationData, messageContent, view) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt index d53544346e..8bd3553c97 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt @@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.riotredesign.core.epoxy.EmptyItem_ import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController +import timber.log.Timber class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, private val roomNameItemFactory: RoomNameItemFactory, @@ -55,6 +56,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, else -> null } } catch (e: Exception) { + Timber.e(e,"failed to create message item") defaultItemFactory.create(event, e) } return (computedModel ?: EmptyItem_()) diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt index b21be3ab43..b1c884fd5f 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageImageVideoItem.kt @@ -43,6 +43,8 @@ abstract class MessageImageVideoItem : AbsMessageItem(R.id.messageThumbnailView) val playContentView by bind(R.id.messageMediaPlayView) + val mediaContentView by bind(R.id.messageContentMedia) + } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt index 790243c4d6..3e3924ae46 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -16,23 +16,19 @@ package im.vector.riotredesign.features.home.room.detail.timeline.item -import android.text.Spannable -import android.view.View -import android.widget.ImageView -import android.widget.TextView 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.matrix.android.api.permalinks.MatrixLinkify import im.vector.riotredesign.R import im.vector.riotredesign.features.html.PillImageSpan import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import me.saket.bettermovementmethod.BetterLinkMovementMethod @EpoxyModelClass(layout = R.layout.item_timeline_event_base) abstract class MessageTextItem : AbsMessageItem() { @@ -41,18 +37,29 @@ abstract class MessageTextItem : AbsMessageItem() { var message: CharSequence? = null @EpoxyAttribute override lateinit var informationData: MessageInformationData - @EpoxyAttribute - var clickListener: View.OnClickListener? = null + + val mvmtMethod = BetterLinkMovementMethod.newInstance().also { + it.setOnLinkClickListener { textView, url -> + //Return false to let android manage the click on the link + false + } + it.setOnLinkLongClickListener { textView, url -> + //Long clicks are handled by parent, return false to block android to do something with url + true + } + } override fun bind(holder: Holder) { super.bind(holder) - MatrixLinkify.addLinkMovementMethod(holder.messageView) + + holder.messageView.movementMethod = mvmtMethod + val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "", TextViewCompat.getTextMetricsParams(holder.messageView), null) holder.messageView.setTextFuture(textFuture) holder.messageView.renderSendState() - holder.messageView.setOnClickListener (clickListener) + holder.messageView.setOnClickListener(cellClickListener) holder.messageView.setOnLongClickListener(longClickListener) findPillsAndProcess { it.bind(holder.messageView) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt b/vector/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt index 15843d8a18..d801260748 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/html/EventHtmlRenderer.kt @@ -19,6 +19,8 @@ package im.vector.riotredesign.features.html import android.content.Context +import android.text.style.ClickableSpan +import android.text.style.URLSpan import im.vector.matrix.android.api.permalinks.PermalinkData import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.session.Session @@ -82,6 +84,9 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR .setHandler( "blockquote", BlockquoteHandler()) + .setHandler( + "font", + FontTagHandler()) .setHandler( "sub", SubScriptHandler()) @@ -156,6 +161,13 @@ private class MxLinkHandler(private val glideRequests: GlideRequests, tag.start(), tag.end() ) + //also add clickable span + SpannableBuilder.setSpans( + visitor.builder(), + URLSpan(link), + tag.start(), + tag.end() + ) } else -> linkHandler.handle(visitor, renderer, tag) } diff --git a/vector/src/main/java/im/vector/riotredesign/features/html/FontTagHandler.kt b/vector/src/main/java/im/vector/riotredesign/features/html/FontTagHandler.kt new file mode 100644 index 0000000000..f757155962 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/html/FontTagHandler.kt @@ -0,0 +1,60 @@ +/* + * 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.graphics.Color +import android.text.style.ForegroundColorSpan +import ru.noties.markwon.MarkwonConfiguration +import ru.noties.markwon.RenderProps +import ru.noties.markwon.html.HtmlTag +import ru.noties.markwon.html.tag.SimpleTagHandler + +/** + * custom to matrix for IRC-style font coloring + */ +class FontTagHandler : SimpleTagHandler() { + override fun getSpans(configuration: MarkwonConfiguration, renderProps: RenderProps, tag: HtmlTag): Any? { + val colorString = tag.attributes()["color"]?.let { parseColor(it) } ?: Color.BLACK + return ForegroundColorSpan(colorString) + } + + private fun parseColor(color_name: String): Int { + try { + return Color.parseColor(color_name) + } catch (e: Exception) { + //try other w3c colors? + return when (color_name) { + "white" -> Color.WHITE + "yellow" -> Color.YELLOW + "fuchsia" -> Color.parseColor("#FF00FF") + "red" -> Color.RED + "silver" -> Color.parseColor("#C0C0C0") + "gray" -> Color.GRAY + "olive" -> Color.parseColor("#808000") + "purple" -> Color.parseColor("#800080") + "maroon" -> Color.parseColor("#800000") + "aqua" -> Color.parseColor("#00FFFF") + "lime" -> Color.parseColor("#00FF00") + "teal" -> Color.parseColor("#008080") + "green" -> Color.GREEN + "blue" -> Color.BLUE + "orange" -> Color.parseColor("#FFA500") + "navy" -> Color.parseColor("#000080") + else -> Color.BLACK + } + } + } +} \ No newline at end of file diff --git a/vector/src/main/res/layout/item_timeline_event_base.xml b/vector/src/main/res/layout/item_timeline_event_base.xml index 2ec5cf041a..4841269dba 100644 --- a/vector/src/main/res/layout/item_timeline_event_base.xml +++ b/vector/src/main/res/layout/item_timeline_event_base.xml @@ -72,6 +72,7 @@ From 1b3ec2d0fb665c5772677fc7e00eb3d256726527 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 7 Jun 2019 13:38:58 +0200 Subject: [PATCH 2/2] fix / review --- .../android/api/permalinks/MatrixLinkify.kt | 37 +------------------ .../detail/timeline/item/MessageTextItem.kt | 2 +- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt index 068b8099fb..eed9b7b6d2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/permalinks/MatrixLinkify.kt @@ -17,9 +17,6 @@ package im.vector.matrix.android.api.permalinks import android.text.Spannable -import android.text.SpannableString -import android.text.method.LinkMovementMethod -import android.widget.TextView import im.vector.matrix.android.api.MatrixPatterns /** @@ -56,37 +53,5 @@ object MatrixLinkify { } return hasMatch } - -// fun addLinks(textView: TextView, callback: MatrixPermalinkSpan.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 -// } -// } - - /** - * Add linkMovementMethod on textview if not already set - * @param textView the textView on which the movementMethod is set - */ - fun addLinkMovementMethod(textView: TextView) { - val movementMethod = textView.movementMethod - if (movementMethod == null || movementMethod !is LinkMovementMethod) { - if (textView.linksClickable) { - textView.movementMethod = LinkMovementMethod.getInstance() - } - } - } + } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt index 3e3924ae46..7fa0e48294 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt @@ -44,7 +44,7 @@ abstract class MessageTextItem : AbsMessageItem() { false } it.setOnLinkLongClickListener { textView, url -> - //Long clicks are handled by parent, return false to block android to do something with url + //Long clicks are handled by parent, return true to block android to do something with url true } }