Fix / click|longclick link interference

+ some missing long click (image content wrapper)
+ update markwon version
This commit is contained in:
Valere 2019-06-03 17:53:04 +02:00
parent 466be1dca5
commit 471170a3e0
11 changed files with 119 additions and 37 deletions

View File

@ -91,7 +91,7 @@ dependencies {
def moshi_version = '1.8.0' def moshi_version = '1.8.0'
def lifecycle_version = '2.0.0' def lifecycle_version = '2.0.0'
def coroutines_version = "1.0.1" 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 fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

View File

@ -57,25 +57,25 @@ object MatrixLinkify {
return hasMatch return hasMatch
} }


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

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


/** /**
* Add linkMovementMethod on textview if not already set * Add linkMovementMethod on textview if not already set

View File

@ -121,7 +121,7 @@ dependencies {
def epoxy_version = "3.3.0" def epoxy_version = "3.3.0"
def arrow_version = "0.8.2" def arrow_version = "0.8.2"
def coroutines_version = "1.0.1" 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 big_image_viewer_version = '1.5.6'
def glide_version = '4.9.0' def glide_version = '4.9.0'
def moshi_version = '1.8.0' def moshi_version = '1.8.0'
@ -173,6 +173,7 @@ dependencies {
implementation 'me.gujun.android:span:1.7' implementation 'me.gujun.android:span:1.7'
implementation "ru.noties.markwon:core:$markwon_version" implementation "ru.noties.markwon:core:$markwon_version"
implementation "ru.noties.markwon:html:$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' implementation 'com.otaliastudios:autocomplete:1.1.0'



View File

@ -63,7 +63,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
var body: CharSequence = messageContent?.body ?: "" var body: CharSequence = messageContent?.body ?: ""
if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
val parser = Parser.builder().build() 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() // val renderer = HtmlRenderer.builder().build()
body = Markwon.builder(viewModelContext.activity) body = Markwon.builder(viewModelContext.activity)
.usePlugin(HtmlPlugin.create()).build().render(document) .usePlugin(HtmlPlugin.create()).build().render(document)

View File

@ -288,7 +288,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
callback: TimelineEventController.Callback?): MessageTextItem? { callback: TimelineEventController.Callback?): MessageTextItem? {


val bodyToUse = messageContent.formattedBody?.let { val bodyToUse = messageContent.formattedBody?.let {
htmlRenderer.render(it) htmlRenderer.render(it.trim())
} ?: messageContent.body } ?: messageContent.body


val linkifiedBody = linkifyBody(bodyToUse, callback) val linkifiedBody = linkifyBody(bodyToUse, callback)
@ -312,11 +312,6 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onMemberNameClicked(informationData) callback?.onMemberNameClicked(informationData)
})) }))
//click on the text
.clickListener(
DebouncedClickListener(View.OnClickListener { view ->
callback?.onEventCellClicked(informationData, messageContent, view)
}))
.cellClickListener( .cellClickListener(
DebouncedClickListener(View.OnClickListener { view -> DebouncedClickListener(View.OnClickListener { view ->
callback?.onEventCellClicked(informationData, messageContent, view) callback?.onEventCellClicked(informationData, messageContent, view)

View File

@ -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.EmptyItem_
import im.vector.riotredesign.core.epoxy.VectorEpoxyModel import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
import timber.log.Timber


class TimelineItemFactory(private val messageItemFactory: MessageItemFactory, class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
private val roomNameItemFactory: RoomNameItemFactory, private val roomNameItemFactory: RoomNameItemFactory,
@ -55,6 +56,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
else -> null else -> null
} }
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e,"failed to create message item")
defaultItemFactory.create(event, e) defaultItemFactory.create(event, e)
} }
return (computedModel ?: EmptyItem_()) return (computedModel ?: EmptyItem_())

View File

@ -43,6 +43,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
ContentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout) ContentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
holder.imageView.setOnClickListener(clickListener) holder.imageView.setOnClickListener(clickListener)
holder.imageView.setOnLongClickListener(longClickListener) holder.imageView.setOnLongClickListener(longClickListener)
holder.mediaContentView.setOnClickListener(cellClickListener)
holder.mediaContentView.setOnLongClickListener(longClickListener)
holder.imageView.renderSendState() holder.imageView.renderSendState()
holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE holder.playContentView.visibility = if (playable) View.VISIBLE else View.GONE
} }
@ -62,6 +64,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
val imageView by bind<ImageView>(R.id.messageThumbnailView) val imageView by bind<ImageView>(R.id.messageThumbnailView)
val playContentView by bind<ImageView>(R.id.messageMediaPlayView) val playContentView by bind<ImageView>(R.id.messageMediaPlayView)


val mediaContentView by bind<ViewGroup>(R.id.messageContentMedia)

} }





View File

@ -16,23 +16,19 @@


package im.vector.riotredesign.features.home.room.detail.timeline.item 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.appcompat.widget.AppCompatTextView
import androidx.core.text.PrecomputedTextCompat import androidx.core.text.PrecomputedTextCompat
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.widget.TextViewCompat import androidx.core.widget.TextViewCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.matrix.android.api.permalinks.MatrixLinkify
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.features.html.PillImageSpan import im.vector.riotredesign.features.html.PillImageSpan
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.saket.bettermovementmethod.BetterLinkMovementMethod


@EpoxyModelClass(layout = R.layout.item_timeline_event_base) @EpoxyModelClass(layout = R.layout.item_timeline_event_base)
abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() { abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
@ -41,18 +37,29 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
var message: CharSequence? = null var message: CharSequence? = null
@EpoxyAttribute @EpoxyAttribute
override lateinit var informationData: MessageInformationData 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) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
MatrixLinkify.addLinkMovementMethod(holder.messageView)
holder.messageView.movementMethod = mvmtMethod

val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "", val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "",
TextViewCompat.getTextMetricsParams(holder.messageView), TextViewCompat.getTextMetricsParams(holder.messageView),
null) null)
holder.messageView.setTextFuture(textFuture) holder.messageView.setTextFuture(textFuture)
holder.messageView.renderSendState() holder.messageView.renderSendState()
holder.messageView.setOnClickListener (clickListener) holder.messageView.setOnClickListener(cellClickListener)
holder.messageView.setOnLongClickListener(longClickListener) holder.messageView.setOnLongClickListener(longClickListener)
findPillsAndProcess { it.bind(holder.messageView) } findPillsAndProcess { it.bind(holder.messageView) }
} }

View File

@ -19,6 +19,8 @@
package im.vector.riotredesign.features.html package im.vector.riotredesign.features.html


import android.content.Context 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.PermalinkData
import im.vector.matrix.android.api.permalinks.PermalinkParser import im.vector.matrix.android.api.permalinks.PermalinkParser
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
@ -82,6 +84,9 @@ private class MatrixPlugin private constructor(private val glideRequests: GlideR
.setHandler( .setHandler(
"blockquote", "blockquote",
BlockquoteHandler()) BlockquoteHandler())
.setHandler(
"font",
FontTagHandler())
.setHandler( .setHandler(
"sub", "sub",
SubScriptHandler()) SubScriptHandler())
@ -156,6 +161,13 @@ private class MxLinkHandler(private val glideRequests: GlideRequests,
tag.start(), tag.start(),
tag.end() tag.end()
) )
//also add clickable span
SpannableBuilder.setSpans(
visitor.builder(),
URLSpan(link),
tag.start(),
tag.end()
)
} }
else -> linkHandler.handle(visitor, renderer, tag) else -> linkHandler.handle(visitor, renderer, tag)
} }

View File

@ -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
}
}
}
}

View File

@ -72,6 +72,7 @@
<ViewStub <ViewStub
android:id="@+id/messageContentMediaStub" android:id="@+id/messageContentMediaStub"
style="@style/TimelineContentStubLayoutParams" style="@style/TimelineContentStubLayoutParams"
android:inflatedId="@+id/messageContentMedia"
android:layout="@layout/item_timeline_event_media_message_stub" android:layout="@layout/item_timeline_event_media_message_stub"
tools:ignore="MissingConstraints" /> tools:ignore="MissingConstraints" />