enhancement: clickable markdown and link management (#263); closes #259

* chore: cleanup markdown click event

* chore: prevent clicks on markdown while handling links; closes #259
This commit is contained in:
Diego Beraldin 2023-12-09 14:11:40 +01:00 committed by GitHub
parent 239aecb83c
commit a4615d964f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 35 additions and 20 deletions

View File

@ -34,7 +34,6 @@ import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownColo
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownPadding import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownPadding
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownTypography import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.MarkdownTypography
import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.ReferenceLinkHandlerImpl import com.github.diegoberaldin.raccoonforlemmy.core.markdown.model.ReferenceLinkHandlerImpl
import com.github.diegoberaldin.raccoonforlemmy.core.utils.compose.onClick
import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.DateTime import com.github.diegoberaldin.raccoonforlemmy.core.utils.datetime.DateTime
import io.noties.markwon.image.AsyncDrawableSpan import io.noties.markwon.image.AsyncDrawableSpan
import org.intellij.markdown.flavours.MarkdownFlavourDescriptor import org.intellij.markdown.flavours.MarkdownFlavourDescriptor
@ -75,10 +74,7 @@ actual fun CustomMarkdown(
) )
} }
BoxWithConstraints( BoxWithConstraints(
modifier = modifier.onClick( modifier = modifier
onClick = onClick ?: {},
onDoubleClick = onDoubleClick ?: {},
)
) { ) {
val style = LocalMarkdownTypography.current.text val style = LocalMarkdownTypography.current.text
val fontScale = LocalDensity.current.fontScale * 1.25f val fontScale = LocalDensity.current.fontScale * 1.25f
@ -116,7 +112,10 @@ actual fun CustomMarkdown(
val currentTime = DateTime.epochMillis() val currentTime = DateTime.epochMillis()
if ((currentTime - lastClickTime) < 300) return false if ((currentTime - lastClickTime) < 300) return false
lastClickTime = currentTime lastClickTime = currentTime
if (!markwonProvider.isHandlingLink.value) {
cancelPendingInputEvents()
onClick?.invoke() onClick?.invoke()
}
return true return true
} }
@ -124,7 +123,10 @@ actual fun CustomMarkdown(
val currentTime = DateTime.epochMillis() val currentTime = DateTime.epochMillis()
if ((currentTime - lastClickTime) < 300) return false if ((currentTime - lastClickTime) < 300) return false
lastClickTime = currentTime lastClickTime = currentTime
if (!markwonProvider.isHandlingLink.value) {
cancelPendingInputEvents()
onDoubleClick?.invoke() onDoubleClick?.invoke()
}
return true return true
} }
@ -133,7 +135,12 @@ actual fun CustomMarkdown(
} }
} }
) )
setOnTouchListener { _, evt -> gestureDetector.onTouchEvent(evt) } setOnTouchListener { v, evt ->
if (evt.action == MotionEvent.ACTION_UP) {
v.performClick()
}
gestureDetector.onTouchEvent(evt)
}
} }
}, },
update = { textView -> update = { textView ->
@ -157,8 +164,6 @@ private fun createTextView(
typeface: Typeface? = null, typeface: Typeface? = null,
style: TextStyle, style: TextStyle,
@IdRes viewId: Int? = null, @IdRes viewId: Int? = null,
onClick: (() -> Unit)? = null,
onLongClick: ((View) -> Boolean)? = null,
): TextView { ): TextView {
val mergedStyle = style.merge( val mergedStyle = style.merge(
TextStyle( TextStyle(
@ -168,8 +173,6 @@ private fun createTextView(
), ),
) )
return TextView(context).apply { return TextView(context).apply {
onClick?.let { setOnClickListener { onClick() } }
onLongClick?.let { setOnLongClickListener(it) }
setTextColor(textColor.toArgb()) setTextColor(textColor.toArgb())
setTextSize(TypedValue.COMPLEX_UNIT_SP, mergedStyle.fontSize.value) setTextSize(TypedValue.COMPLEX_UNIT_SP, mergedStyle.fontSize.value)
width = maxWidth width = maxWidth

View File

@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
import org.commonmark.node.Image import org.commonmark.node.Image
import org.commonmark.node.Node import org.commonmark.node.Node
private const val TRIGGER_UPDATE_INTERVAL = 250L private const val TRIGGER_UPDATE_INTERVAL = 500L
class ClickableImagesPlugin private constructor( class ClickableImagesPlugin private constructor(
private val context: Context, private val context: Context,

View File

@ -14,6 +14,12 @@ import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.image.ImagesPlugin import io.noties.markwon.image.ImagesPlugin
import io.noties.markwon.image.gif.GifMediaDecoder import io.noties.markwon.image.gif.GifMediaDecoder
import io.noties.markwon.linkify.LinkifyPlugin import io.noties.markwon.linkify.LinkifyPlugin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class DefaultMarkwonProvider( class DefaultMarkwonProvider(
context: Context, context: Context,
@ -21,7 +27,10 @@ class DefaultMarkwonProvider(
onOpenImage: ((String) -> Unit)?, onOpenImage: ((String) -> Unit)?,
onImageTriggerUpdate: (() -> Unit)?, onImageTriggerUpdate: (() -> Unit)?,
) : MarkwonProvider { ) : MarkwonProvider {
override val markwon: Markwon override val markwon: Markwon
override val isHandlingLink = MutableStateFlow(false)
private val scope = CoroutineScope(SupervisorJob())
init { init {
markwon = Markwon.builder(context) markwon = Markwon.builder(context)
@ -31,13 +40,9 @@ class DefaultMarkwonProvider(
.usePlugin(TablePlugin.create(context)) .usePlugin(TablePlugin.create(context))
.usePlugin(HtmlPlugin.create()) .usePlugin(HtmlPlugin.create())
.usePlugin( .usePlugin(
ImagesPlugin.create( ImagesPlugin.create { plugin ->
object : ImagesPlugin.ImagesConfigure {
override fun configureImages(plugin: ImagesPlugin) {
plugin.addMediaDecoder(GifMediaDecoder.create(true)) plugin.addMediaDecoder(GifMediaDecoder.create(true))
}
}, },
),
) )
.usePlugin(MarkwonSpoilerPlugin.create(true)) .usePlugin(MarkwonSpoilerPlugin.create(true))
.usePlugin( .usePlugin(
@ -53,7 +58,12 @@ class DefaultMarkwonProvider(
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link -> builder.linkResolver { view, link ->
view.cancelPendingInputEvents() view.cancelPendingInputEvents()
isHandlingLink.value = true
onOpenUrl?.invoke(link) onOpenUrl?.invoke(link)
scope.launch {
delay(300)
isHandlingLink.value = false
}
} }
} }
}, },

View File

@ -1,7 +1,9 @@
package com.github.diegoberaldin.raccoonforlemmy.core.markdown.provider package com.github.diegoberaldin.raccoonforlemmy.core.markdown.provider
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
import kotlinx.coroutines.flow.StateFlow
interface MarkwonProvider { interface MarkwonProvider {
val markwon: Markwon val markwon: Markwon
val isHandlingLink: StateFlow<Boolean>
} }