diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle index 5211a223..3fda5a63 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 d09db8c4..068b8099 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 b9e01ed0..606cdd19 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 9198f8ee..315927e3 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 05fda074..da5bc667 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 d5354434..8bd3553c 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 b21be3ab..b1c884fd 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 790243c4..3e3924ae 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 15843d8a..d8012607 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 00000000..f7571559 --- /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 2ec5cf04..4841269d 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 @@