forked from GitHub-Mirror/riotX-android
Fix / click|longclick link interference
+ some missing long click (image content wrapper) + update markwon version
This commit is contained in:
parent
466be1dca5
commit
471170a3e0
@ -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"
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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_())
|
||||||
|
@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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" />
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user