Skip to content
This repository has been archived by the owner on Sep 27, 2024. It is now read-only.

Commit

Permalink
[Android] Fixes to make EditorStyleText work properly in Compose (#891
Browse files Browse the repository at this point in the history
)

* Fix link click detection.

Before this, the `TextView` would consume the touch event even if no link or mention was clicked.

* Fix race condition that incorrectly rendered the mentions, as they weren't being detected as mentions at all.

* Add `TextStyle.lineHeight` property to modify line height.

* Bump JNA library version.
  • Loading branch information
jmartinesp authored Nov 28, 2023
1 parent b9e98a6 commit 65b72e2
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ fun EditorStyledText(
EditorStyledTextView(context)
},
update = { view ->
view.updateStyle(style.toStyleConfig(view.context), mentionDisplayHandler)
view.applyStyleInCompose(style)
view.typeface = typeface
view.updateStyle(style.toStyleConfig(view.context), mentionDisplayHandler)
if (text is Spanned) {
view.setText(text, TextView.BufferType.SPANNABLE)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,15 @@ object RichTextEditorDefaults {
fun textStyle(
color: Color = MaterialTheme.colorScheme.onSurface,
fontSize: TextUnit = MaterialTheme.typography.bodyLarge.fontSize,
lineHeight: TextUnit = MaterialTheme.typography.bodyLarge.lineHeight,
fontFamily: FontFamily? = MaterialTheme.typography.bodyLarge.fontFamily,
fontWeight: FontWeight? = MaterialTheme.typography.bodyLarge.fontWeight,
fontStyle: FontStyle? = MaterialTheme.typography.bodyLarge.fontStyle,
fontSynthesis: FontSynthesis? = MaterialTheme.typography.bodyLarge.fontSynthesis,
) = TextStyle(
color = color,
fontSize = fontSize,
lineHeight = lineHeight,
fontFamily = fontFamily,
fontWeight = fontWeight,
fontStyle = fontStyle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ data class PillStyle(
data class TextStyle(
val color: Color,
val fontSize: TextUnit,
val lineHeight: TextUnit,
val fontFamily: FontFamily?,
val fontWeight: FontWeight?,
val fontStyle: FontStyle?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ internal fun TextStyle.rememberTypeface(): State<Typeface> {

internal fun TextView.applyStyleInCompose(style: RichTextEditorStyle) {
setTextColor(style.text.color.toArgb())
val lineHeightInPx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
style.text.lineHeight.value,
context.resources.displayMetrics
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lineHeight = lineHeightInPx.roundToInt()
}
setTextSize(TypedValue.COMPLEX_UNIT_SP, style.text.fontSize.value)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val cursorDrawable = ContextCompat.getDrawable(context, R.drawable.cursor)
Expand Down
2 changes: 1 addition & 1 deletion platforms/android/library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ kotlin {

dependencies {

implementation "net.java.dev.jna:jna:5.7.0@aar"
implementation "net.java.dev.jna:jna:5.13.0@aar"

implementation libs.kotlin.coroutines.android
implementation libs.kotlin.coroutines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import androidx.appcompat.widget.AppCompatTextView
import androidx.core.graphics.withTranslation
import androidx.core.text.getSpans
import androidx.core.view.GestureDetectorCompat
import com.sun.jna.internal.Cleaner
import io.element.android.wysiwyg.display.MentionDisplayHandler
import io.element.android.wysiwyg.internal.view.EditorEditTextAttributeReader
import io.element.android.wysiwyg.utils.HtmlConverter
import io.element.android.wysiwyg.utils.RustCleanerTask
import io.element.android.wysiwyg.view.StyleConfig
import io.element.android.wysiwyg.view.inlinebg.SpanBackgroundHelper
import io.element.android.wysiwyg.view.inlinebg.SpanBackgroundHelperFactory
Expand All @@ -28,7 +30,18 @@ import uniffi.wysiwyg_composer.newMentionDetector
*/
open class EditorStyledTextView : AppCompatTextView {

private var mentionDetector: MentionDetector? = null
// Used to automatically clean up the native resources when this instance is GCed
private val cleaner = Cleaner.getCleaner()

private val mentionDetector: MentionDetector? by lazy {
if (!isInEditMode) {
val detector = newMentionDetector()
cleaner.register(this, RustCleanerTask(detector))
detector
} else {
null
}
}

private lateinit var inlineCodeBgHelper: SpanBackgroundHelper
private lateinit var codeBlockBgHelper: SpanBackgroundHelper
Expand All @@ -43,13 +56,20 @@ open class EditorStyledTextView : AppCompatTextView {

private val spannableFactory = ReuseSourceSpannableFactory()

private var mentionDisplayHandler: MentionDisplayHandler? = null
var mentionDisplayHandler: MentionDisplayHandler? = null
private var htmlConverter: HtmlConverter? = null

var onLinkClickedListener: ((String) -> Unit)? = null

// This gesture detector will be used to detect clicks on spans
private val gestureDetector = GestureDetectorCompat(context, object : GestureDetector.SimpleOnGestureListener() {

override fun onDown(e: MotionEvent): Boolean {
// Find any spans in the coordinates
val spans = findSpansForTouchEvent(e)
return spans.any { it is LinkSpan || it is PillSpan || it is CustomMentionSpan }
}

override fun onSingleTapUp(e: MotionEvent): Boolean {
// Find any spans in the coordinates
val spans = findSpansForTouchEvent(e)
Expand All @@ -59,17 +79,20 @@ open class EditorStyledTextView : AppCompatTextView {
when (span) {
is LinkSpan -> {
onLinkClickedListener?.invoke(span.url)
return true
}
is PillSpan -> {
span.url?.let { onLinkClickedListener?.invoke(it) }
return true
}
is CustomMentionSpan -> {
span.url?.let { onLinkClickedListener?.invoke(it) }
return true
}
else -> Unit
}
}
return true
return false
}
})

Expand Down Expand Up @@ -138,18 +161,9 @@ open class EditorStyledTextView : AppCompatTextView {
override fun onAttachedToWindow() {
super.onAttachedToWindow()

mentionDetector = if (isInEditMode) null else newMentionDetector()

updateStyle(styleConfig, mentionDisplayHandler)
}

override fun onDetachedFromWindow() {
super.onDetachedFromWindow()

mentionDetector?.destroy()
mentionDetector = null
}

private fun createHtmlConverter(styleConfig: StyleConfig, mentionDisplayHandler: MentionDisplayHandler?): HtmlConverter {
return HtmlConverter.Factory.create(
context = context,
Expand All @@ -165,11 +179,11 @@ open class EditorStyledTextView : AppCompatTextView {

override fun onTouchEvent(event: MotionEvent?): Boolean {
// We pass the event to the gesture detector
event?.let { gestureDetector.onTouchEvent(it) }
val handled = event?.let { gestureDetector.onTouchEvent(it) }
// This will handle the default actions for any touch event in the TextView
super.onTouchEvent(event)
// We need to return true to be able to detect any events
return true
// We return if we handled the event and want to intercept it or not
return handled ?: false
}

private fun findSpansForTouchEvent(event: MotionEvent): Array<out Any> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import androidx.annotation.ColorRes
import androidx.annotation.Dimension
import androidx.core.content.res.ResourcesCompat

/**
* This class provides access to resources needed to convert HTML to spans.
*/
internal interface ResourcesHelper {
fun getDisplayMetrics(): DisplayMetrics

Expand All @@ -14,6 +17,9 @@ internal interface ResourcesHelper {
fun getColor(@ColorRes colorId: Int): Int
}

/**
* This class provides access to Android resources needed to convert HTML to spans.
*/
internal class AndroidResourcesHelper(
private val application: Application,
) : ResourcesHelper {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.element.android.wysiwyg.utils

import timber.log.Timber
import uniffi.wysiwyg_composer.Disposable

internal class RustCleanerTask(
private val disposable: Disposable,
) : Runnable {
override fun run() {
Timber.d("Cleaning up disposable: $disposable")
disposable.destroy()
}
}

0 comments on commit 65b72e2

Please sign in to comment.