react-native-enriched-markdown-scaffold 1.0.2-scaffold.8 → 1.0.2-scaffold.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +10 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +38 -4
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +12 -0
- package/package.json +1 -1
package/android/build.gradle
CHANGED
|
@@ -270,6 +270,8 @@ dependencies {
|
|
|
270
270
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
271
271
|
// Baseline Profile installer for AOT compilation hints
|
|
272
272
|
implementation "androidx.profileinstaller:profileinstaller:1.3.1"
|
|
273
|
+
// AppCompatTextView draws emoji through EmojiCompat; measurement must apply it too
|
|
274
|
+
implementation "androidx.emoji2:emoji2:1.4.0"
|
|
273
275
|
// LaTeX math rendering (optional — set enrichedMarkdown.enableMath=false in gradle.properties to exclude)
|
|
274
276
|
if (enableMath) {
|
|
275
277
|
implementation("io.github.erweixin:ratex-android:0.1.10")
|
package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt
CHANGED
|
@@ -21,6 +21,7 @@ import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxT
|
|
|
21
21
|
import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap
|
|
22
22
|
import com.swmansion.enriched.markdown.utils.text.view.clearTextSelection
|
|
23
23
|
import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback
|
|
24
|
+
import com.swmansion.enriched.markdown.utils.text.view.isRomSelectionPopupShower
|
|
24
25
|
import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
|
|
25
26
|
import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView
|
|
26
27
|
import com.swmansion.enriched.markdown.views.BlockSegmentView
|
|
@@ -140,6 +141,15 @@ class EnrichedMarkdownInternalText
|
|
|
140
141
|
return super.startActionMode(callback, type)
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
// See isRomSelectionPopupShower: MIUI shows its own selection popup this way.
|
|
145
|
+
override fun postDelayed(
|
|
146
|
+
action: Runnable?,
|
|
147
|
+
delayMillis: Long,
|
|
148
|
+
): Boolean {
|
|
149
|
+
if (contextMenuHidden && isRomSelectionPopupShower(action)) return false
|
|
150
|
+
return super.postDelayed(action, delayMillis)
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/** Clears the selection and ends its ActionMode. */
|
|
144
154
|
fun clearSelection() {
|
|
145
155
|
clearTextSelection()
|
|
@@ -44,6 +44,7 @@ import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionMode
|
|
|
44
44
|
import com.swmansion.enriched.markdown.utils.text.view.currentTextSelection
|
|
45
45
|
import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent
|
|
46
46
|
import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent
|
|
47
|
+
import com.swmansion.enriched.markdown.utils.text.view.isRomSelectionPopupShower
|
|
47
48
|
import com.swmansion.enriched.markdown.utils.text.view.reallowParentInterceptIfLinkReleased
|
|
48
49
|
import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView
|
|
49
50
|
import java.util.concurrent.Executors
|
|
@@ -348,6 +349,15 @@ class EnrichedMarkdownText
|
|
|
348
349
|
return super.startActionMode(callback, type)
|
|
349
350
|
}
|
|
350
351
|
|
|
352
|
+
// See isRomSelectionPopupShower: MIUI shows its own selection popup this way.
|
|
353
|
+
override fun postDelayed(
|
|
354
|
+
action: Runnable?,
|
|
355
|
+
delayMillis: Long,
|
|
356
|
+
): Boolean {
|
|
357
|
+
if (contextMenuHidden && isRomSelectionPopupShower(action)) return false
|
|
358
|
+
return super.postDelayed(action, delayMillis)
|
|
359
|
+
}
|
|
360
|
+
|
|
351
361
|
/** Clears the selection (ending its ActionMode) and reports `active: false`. */
|
|
352
362
|
fun clearSelection() {
|
|
353
363
|
clearTextSelection()
|
|
@@ -7,6 +7,7 @@ import android.text.SpannableString
|
|
|
7
7
|
import android.text.StaticLayout
|
|
8
8
|
import android.text.TextPaint
|
|
9
9
|
import android.util.Log
|
|
10
|
+
import androidx.emoji2.text.EmojiCompat
|
|
10
11
|
import com.facebook.react.bridge.ReadableMap
|
|
11
12
|
import com.facebook.react.uimanager.PixelUtil
|
|
12
13
|
import com.facebook.yoga.YogaMeasureMode
|
|
@@ -92,6 +93,38 @@ object MeasurementStore {
|
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
private val measurePaint = TextPaint()
|
|
96
|
+
|
|
97
|
+
@Volatile
|
|
98
|
+
private var emojiInitCallbackRegistered = false
|
|
99
|
+
|
|
100
|
+
// A view measured before EmojiCompat loaded keeps a size without its emoji glyphs.
|
|
101
|
+
private val emojiInitCallback =
|
|
102
|
+
object : EmojiCompat.InitCallback() {
|
|
103
|
+
override fun onInitialized() {
|
|
104
|
+
data.clear()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// AppCompatTextView draws emoji the system font lacks through EmojiCompat, which can be
|
|
109
|
+
// wider than the fallback glyph and wrap a line earlier; measure the same glyphs.
|
|
110
|
+
private fun withEmojiSpans(text: CharSequence): CharSequence {
|
|
111
|
+
if (text.isEmpty() || !EmojiCompat.isConfigured()) return text
|
|
112
|
+
val emojiCompat = EmojiCompat.get()
|
|
113
|
+
if (emojiCompat.loadState != EmojiCompat.LOAD_STATE_SUCCEEDED) {
|
|
114
|
+
if (!emojiInitCallbackRegistered) {
|
|
115
|
+
emojiInitCallbackRegistered = true
|
|
116
|
+
emojiCompat.registerInitCallback(emojiInitCallback)
|
|
117
|
+
}
|
|
118
|
+
return text
|
|
119
|
+
}
|
|
120
|
+
return try {
|
|
121
|
+
emojiCompat.process(SpannableString(text)) ?: text
|
|
122
|
+
} catch (e: IllegalStateException) {
|
|
123
|
+
Log.w(TAG, "EmojiCompat processing failed, measuring raw text", e)
|
|
124
|
+
text
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
95
128
|
private val measureRenderer = Renderer()
|
|
96
129
|
|
|
97
130
|
@Volatile
|
|
@@ -550,9 +583,10 @@ object MeasurementStore {
|
|
|
550
583
|
viewId: Int?,
|
|
551
584
|
): StaticLayout {
|
|
552
585
|
measurePaint.textSize = fontSize
|
|
553
|
-
|
|
586
|
+
val content = withEmojiSpans(text)
|
|
587
|
+
prepareImageSpansForMeasurement(content, widthPx)
|
|
554
588
|
return StaticLayout.Builder
|
|
555
|
-
.obtain(
|
|
589
|
+
.obtain(content, 0, content.length, measurePaint, widthPx)
|
|
556
590
|
.setIncludePad(false)
|
|
557
591
|
.setLineSpacing(0f, 1f)
|
|
558
592
|
.apply {
|
|
@@ -632,7 +666,7 @@ object MeasurementStore {
|
|
|
632
666
|
paint: TextPaint,
|
|
633
667
|
viewId: Int?,
|
|
634
668
|
): Long {
|
|
635
|
-
val content = text ?: ""
|
|
669
|
+
val content = withEmojiSpans(text ?: "")
|
|
636
670
|
val safeWidth = ceil(maxWidth).toInt().coerceAtLeast(1)
|
|
637
671
|
prepareImageSpansForMeasurement(content, safeWidth)
|
|
638
672
|
|
|
@@ -672,7 +706,7 @@ object MeasurementStore {
|
|
|
672
706
|
paint: TextPaint,
|
|
673
707
|
viewId: Int?,
|
|
674
708
|
): Pair<Long, StaticLayout> {
|
|
675
|
-
val content = text ?: ""
|
|
709
|
+
val content = withEmojiSpans(text ?: "")
|
|
676
710
|
val widthPx = ceil(maxWidth).toInt().coerceAtLeast(1)
|
|
677
711
|
prepareImageSpansForMeasurement(content, widthPx)
|
|
678
712
|
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt
CHANGED
|
@@ -74,3 +74,15 @@ internal class HeadlessActionMode private constructor(
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* MIUI 12 (Android 10) replaces android.widget.Editor: text selection never reaches
|
|
80
|
+
* startActionMode or the custom ActionMode callback, so HeadlessActionMode is never used.
|
|
81
|
+
* Its own popup is shown by Editor$HandleView.showActionPopupWindow, which posts a runnable
|
|
82
|
+
* on the TextView (Editor$HandleView$2). Dropping that runnable is the only hook left while
|
|
83
|
+
* `contextMenuHidden` is on.
|
|
84
|
+
*/
|
|
85
|
+
private const val ROM_SELECTION_POPUP_SHOWER_PREFIX = "android.widget.Editor\$HandleView"
|
|
86
|
+
|
|
87
|
+
internal fun isRomSelectionPopupShower(action: Runnable?): Boolean =
|
|
88
|
+
action != null && action.javaClass.name.startsWith(ROM_SELECTION_POPUP_SHOWER_PREFIX)
|
package/package.json
CHANGED