Merge pull request #161 from vector-im/feature/fix_timeline_clicks

Fix / click|longclick link interference
This commit is contained in:
Valere 2019-06-07 14:43:04 +02:00 committed by GitHub
commit d3518c4944
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 54 deletions

View File

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

View File

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

View File

@ -132,7 +132,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'
@ -184,6 +184,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'

View File

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

View File

@ -295,7 +295,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)
@ -319,11 +319,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)

View File

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

View File

@ -43,6 +43,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
ContentUploadStateTrackerBinder.bind(informationData.eventId, mediaData, holder.progressLayout)
holder.imageView.setOnClickListener(clickListener)
holder.imageView.setOnLongClickListener(longClickListener)
holder.mediaContentView.setOnClickListener(cellClickListener)
holder.mediaContentView.setOnLongClickListener(longClickListener)
holder.imageView.renderSendState()
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 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
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<MessageTextItem.Holder>() {
@ -41,18 +37,29 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
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 true 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) }
}

View File

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

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

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