react-native-enriched-markdown-scaffold 1.0.2-scaffold.1 → 1.0.2-scaffold.10
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/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java +4 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java +18 -0
- package/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java +4 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp +30 -0
- package/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h +210 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +176 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +45 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +35 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +112 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +34 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/MeasurementStore.kt +46 -8
- package/android/src/main/java/com/swmansion/enriched/markdown/events/CopyRequestEvent.kt +28 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/LinkLayoutEvent.kt +36 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/events/SelectionChangeEvent.kt +43 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt +25 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/input/editing/InputConnectionWrapper.kt +7 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +37 -7
- package/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +5 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/CodeBackgroundSpan.kt +13 -99
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/InlineChipPainter.kt +140 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkChipSpans.kt +177 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/spans/LinkSpan.kt +1 -6
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/InlineMathStyle.kt +16 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkStyle.kt +36 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/LinkVariantEntry.kt +7 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/styles/MathStyle.kt +3 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +51 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/input/PasteTextUtils.kt +34 -2
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt +2 -1
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +6 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt +88 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLayoutCollector.kt +100 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +53 -14
- package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionChangeReporter.kt +232 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/BlockMenuOptions.kt +64 -0
- package/android/src/main/java/com/swmansion/enriched/markdown/views/CodeBlockContainerView.kt +29 -3
- package/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +30 -7
- package/android/src/math/java/com/swmansion/enriched/markdown/renderer/MathInlineRenderer.kt +1 -0
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathInlineSpan.kt +13 -6
- package/android/src/math/java/com/swmansion/enriched/markdown/spans/MathMeasureHelper.kt +2 -1
- package/android/src/math/java/com/swmansion/enriched/markdown/views/MathContainerView.kt +25 -9
- package/docs/API_REFERENCE.md +183 -4
- package/docs/COPY_OPTIONS.md +40 -0
- package/docs/INPUT.md +3 -1
- package/docs/MENTIONS.md +8 -1
- package/docs/STYLES.md +27 -0
- package/docs/TEXT.md +2 -0
- package/docs/WEB.md +1 -0
- package/ios/EnrichedMarkdown.mm +321 -9
- package/ios/EnrichedMarkdownText.mm +203 -6
- package/ios/attachments/ENRMMathInlineAttachment+macOS.m +2 -1
- package/ios/attachments/ENRMMathInlineAttachment.h +3 -0
- package/ios/attachments/ENRMMathInlineAttachment.m +2 -1
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp +148 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h +120 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp +30 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h +210 -0
- package/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h +50 -2
- package/ios/internals/MeasurementCache.h +8 -2
- package/ios/math/ENRMRaTeXBridge.swift +23 -5
- package/ios/renderer/ENRMMathInlineRenderer.m +1 -0
- package/ios/renderer/LinkRenderer.m +47 -15
- package/ios/styles/StyleConfig.h +20 -0
- package/ios/styles/StyleConfig.mm +84 -0
- package/ios/utils/CodeBackground.m +3 -65
- package/ios/utils/ENRMBlockMenu.h +66 -0
- package/ios/utils/ENRMChipDrawing.h +24 -0
- package/ios/utils/ENRMChipDrawing.m +83 -0
- package/ios/utils/ENRMLinkChip.h +70 -0
- package/ios/utils/ENRMLinkChip.m +213 -0
- package/ios/utils/ENRMLinkLayout.h +50 -0
- package/ios/utils/ENRMLinkLayout.m +45 -0
- package/ios/utils/ENRMSelectionChange.h +120 -0
- package/ios/utils/ENRMSelectionChange.m +282 -0
- package/ios/utils/EditMenuUtils+macOS.m +4 -1
- package/ios/utils/EditMenuUtils.h +35 -6
- package/ios/utils/EditMenuUtils.m +171 -15
- package/ios/utils/HTMLGenerator.m +2 -1
- package/ios/utils/MarkdownAccessibilityElementBuilder.m +3 -1
- package/ios/utils/MarkdownExtractor.m +2 -1
- package/ios/utils/RTFExportUtils.m +2 -0
- package/ios/utils/StylePropsUtils.h +47 -1
- package/ios/utils/TextViewLayoutManager.mm +3 -0
- package/ios/views/ENRMCodeBlockContainerView.h +16 -2
- package/ios/views/ENRMCodeBlockContainerView.m +22 -19
- package/ios/views/ENRMMathContainerView.h +11 -0
- package/ios/views/ENRMMathContainerView.m +17 -20
- package/ios/views/TableContainerView.h +14 -0
- package/ios/views/TableContainerView.m +34 -18
- package/lib/module/EnrichedMarkdownNativeComponent.ts +138 -1
- package/lib/module/EnrichedMarkdownTextNativeComponent.ts +138 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/jest/index.js +14 -0
- package/lib/module/jest/index.js.map +1 -1
- package/lib/module/native/EnrichedMarkdownText.js +169 -11
- package/lib/module/native/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.js +17 -4
- package/lib/module/normalizeMarkdownStyle.js.map +1 -1
- package/lib/module/normalizeMarkdownStyle.web.js +17 -4
- package/lib/module/normalizeMarkdownStyle.web.js.map +1 -1
- package/lib/module/web/EnrichedMarkdownText.js +6 -3
- package/lib/module/web/EnrichedMarkdownText.js.map +1 -1
- package/lib/module/web/renderers/InlineRenderers.js +96 -3
- package/lib/module/web/renderers/InlineRenderers.js.map +1 -1
- package/lib/module/web/styles.js +18 -0
- package/lib/module/web/styles.js.map +1 -1
- package/lib/module/web/utils.js +13 -0
- package/lib/module/web/utils.js.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts +123 -2
- package/lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts +123 -2
- package/lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -2
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -1
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/lib/typescript/src/jest/index.d.ts +1 -1
- package/lib/typescript/src/jest/index.d.ts.map +1 -1
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts +5 -5
- package/lib/typescript/src/native/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyle.d.ts +50 -5
- package/lib/typescript/src/types/MarkdownStyle.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts +12 -0
- package/lib/typescript/src/types/MarkdownStyleInternal.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.d.ts +144 -11
- package/lib/typescript/src/types/MarkdownTextProps.d.ts.map +1 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts +41 -1
- package/lib/typescript/src/types/MarkdownTextProps.web.d.ts.map +1 -1
- package/lib/typescript/src/types/events.d.ts +66 -0
- package/lib/typescript/src/types/events.d.ts.map +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts +1 -1
- package/lib/typescript/src/web/EnrichedMarkdownText.d.ts.map +1 -1
- package/lib/typescript/src/web/renderers/InlineRenderers.d.ts.map +1 -1
- package/lib/typescript/src/web/styles.d.ts.map +1 -1
- package/lib/typescript/src/web/types.d.ts +4 -0
- package/lib/typescript/src/web/types.d.ts.map +1 -1
- package/lib/typescript/src/web/utils.d.ts +4 -0
- package/lib/typescript/src/web/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/EnrichedMarkdownNativeComponent.ts +138 -1
- package/src/EnrichedMarkdownTextNativeComponent.ts +138 -1
- package/src/index.tsx +6 -0
- package/src/index.web.tsx +5 -1
- package/src/jest/index.tsx +22 -1
- package/src/native/EnrichedMarkdownText.tsx +220 -9
- package/src/normalizeMarkdownStyle.ts +15 -0
- package/src/normalizeMarkdownStyle.web.ts +13 -0
- package/src/types/MarkdownStyle.ts +51 -5
- package/src/types/MarkdownStyleInternal.ts +12 -0
- package/src/types/MarkdownTextProps.ts +151 -11
- package/src/types/MarkdownTextProps.web.ts +43 -1
- package/src/types/events.ts +69 -0
- package/src/web/EnrichedMarkdownText.tsx +10 -3
- package/src/web/renderers/InlineRenderers.tsx +111 -3
- package/src/web/styles.ts +25 -0
- package/src/web/types.ts +4 -0
- package/src/web/utils.ts +8 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.spans
|
|
2
|
+
|
|
3
|
+
import android.graphics.Canvas
|
|
4
|
+
import android.graphics.Paint
|
|
5
|
+
import android.graphics.Path
|
|
6
|
+
import android.graphics.RectF
|
|
7
|
+
import android.text.SpannableStringBuilder
|
|
8
|
+
import android.text.Spanned
|
|
9
|
+
import android.text.TextPaint
|
|
10
|
+
import android.text.style.LineBackgroundSpan
|
|
11
|
+
import android.text.style.ReplacementSpan
|
|
12
|
+
import com.swmansion.enriched.markdown.styles.LinkChipStyle
|
|
13
|
+
import kotlin.math.max
|
|
14
|
+
import kotlin.math.min
|
|
15
|
+
import kotlin.math.roundToInt
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Reserves a link's `paddingHorizontal` at one outer edge. It covers one [LinkSpacers.CHAR]
|
|
19
|
+
* inserted by `LinkRenderer`: U+00A0 is line-break class GL, so a break is allowed before it
|
|
20
|
+
* after a space but never after it, and never before it after a non-space — the spacer always
|
|
21
|
+
* travels with the link's first (or last) glyph, never orphaned at a wrap.
|
|
22
|
+
*
|
|
23
|
+
* Layout-only: every text exit point strips spacers by this span ([LinkSpacers.strip]).
|
|
24
|
+
*/
|
|
25
|
+
class LinkSpacerSpan(
|
|
26
|
+
private val width: Float,
|
|
27
|
+
) : ReplacementSpan() {
|
|
28
|
+
override fun getSize(
|
|
29
|
+
paint: Paint,
|
|
30
|
+
text: CharSequence?,
|
|
31
|
+
start: Int,
|
|
32
|
+
end: Int,
|
|
33
|
+
fm: Paint.FontMetricsInt?,
|
|
34
|
+
): Int {
|
|
35
|
+
// Keep the line metrics of the surrounding text.
|
|
36
|
+
if (fm != null) paint.getFontMetricsInt(fm)
|
|
37
|
+
return width.roundToInt()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun draw(
|
|
41
|
+
canvas: Canvas,
|
|
42
|
+
text: CharSequence?,
|
|
43
|
+
start: Int,
|
|
44
|
+
end: Int,
|
|
45
|
+
x: Float,
|
|
46
|
+
top: Int,
|
|
47
|
+
y: Int,
|
|
48
|
+
bottom: Int,
|
|
49
|
+
paint: Paint,
|
|
50
|
+
) = Unit
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
object LinkSpacers {
|
|
54
|
+
const val CHAR = '\u00A0'
|
|
55
|
+
|
|
56
|
+
/** [text] without link spacers (returns [text] itself when it has none). */
|
|
57
|
+
fun strip(text: CharSequence): CharSequence {
|
|
58
|
+
if (text !is Spanned) return text
|
|
59
|
+
val spacers = text.getSpans(0, text.length, LinkSpacerSpan::class.java)
|
|
60
|
+
if (spacers.isEmpty()) return text
|
|
61
|
+
val builder = SpannableStringBuilder(text)
|
|
62
|
+
spacers
|
|
63
|
+
.map { builder.getSpanStart(it) to builder.getSpanEnd(it) }
|
|
64
|
+
.filter { (start, end) -> start in 0 until end }
|
|
65
|
+
.sortedByDescending { it.first }
|
|
66
|
+
.forEach { (start, end) -> builder.delete(start, end) }
|
|
67
|
+
return builder
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** True when [start, end) lies inside a spacer (a segment exporters must skip). */
|
|
71
|
+
fun isSpacer(
|
|
72
|
+
text: Spanned,
|
|
73
|
+
start: Int,
|
|
74
|
+
end: Int,
|
|
75
|
+
): Boolean = end > start && text.getSpans(start, end, LinkSpacerSpan::class.java).isNotEmpty()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Paints a link chip (background + border) behind the link glyphs. One instance per link,
|
|
80
|
+
* spanning the link including its spacers, so the chip already covers `paddingHorizontal`.
|
|
81
|
+
* Vertically the chip spans the link font's ascent..descent ± `paddingVertical`; the vertical
|
|
82
|
+
* padding only bleeds into the surrounding line spacing and never changes the line box.
|
|
83
|
+
*/
|
|
84
|
+
class LinkChipBackgroundSpan(
|
|
85
|
+
val chip: LinkChipStyle,
|
|
86
|
+
) : LineBackgroundSpan {
|
|
87
|
+
private val rect = RectF()
|
|
88
|
+
private val path = Path()
|
|
89
|
+
private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
90
|
+
private val borderPaint =
|
|
91
|
+
Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
92
|
+
style = Paint.Style.STROKE
|
|
93
|
+
strokeJoin = Paint.Join.ROUND
|
|
94
|
+
strokeCap = Paint.Cap.ROUND
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override fun drawBackground(
|
|
98
|
+
canvas: Canvas,
|
|
99
|
+
p: Paint,
|
|
100
|
+
left: Int,
|
|
101
|
+
right: Int,
|
|
102
|
+
top: Int,
|
|
103
|
+
baseline: Int,
|
|
104
|
+
bottom: Int,
|
|
105
|
+
text: CharSequence,
|
|
106
|
+
start: Int,
|
|
107
|
+
end: Int,
|
|
108
|
+
lineNum: Int,
|
|
109
|
+
) {
|
|
110
|
+
if (!chip.isVisible || text !is Spanned) return
|
|
111
|
+
|
|
112
|
+
val spanStart = text.getSpanStart(this)
|
|
113
|
+
val spanEnd = text.getSpanEnd(this)
|
|
114
|
+
if (spanStart !in 0 until spanEnd || spanEnd <= start || spanStart >= end) return
|
|
115
|
+
|
|
116
|
+
val isFirst = spanStart >= start
|
|
117
|
+
val isLast = spanEnd <= end
|
|
118
|
+
|
|
119
|
+
val leadingMargin = InlineChipPainter.leadingMarginAt(text, start)
|
|
120
|
+
val fragmentStart = max(spanStart, start)
|
|
121
|
+
val fragmentEnd = trimTrailingWhitespace(text, fragmentStart, min(spanEnd, end))
|
|
122
|
+
val startX = InlineChipPainter.horizontalOffset(text, start, end, fragmentStart, p, leadingMargin) + left
|
|
123
|
+
val endX = InlineChipPainter.horizontalOffset(text, start, end, fragmentEnd, p, leadingMargin) + left
|
|
124
|
+
|
|
125
|
+
val (ascent, descent) = linkFontMetrics(text, spanStart, spanEnd, p)
|
|
126
|
+
rect.set(
|
|
127
|
+
min(startX, endX),
|
|
128
|
+
baseline + ascent - chip.paddingVertical,
|
|
129
|
+
max(startX, endX),
|
|
130
|
+
baseline + descent + chip.paddingVertical,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
backgroundPaint.color = chip.backgroundColor
|
|
134
|
+
borderPaint.color = chip.borderColor
|
|
135
|
+
borderPaint.strokeWidth = chip.borderWidth
|
|
136
|
+
InlineChipPainter.draw(
|
|
137
|
+
canvas,
|
|
138
|
+
rect,
|
|
139
|
+
path,
|
|
140
|
+
chip.borderRadius,
|
|
141
|
+
backgroundPaint.takeIf { android.graphics.Color.alpha(chip.backgroundColor) > 0 },
|
|
142
|
+
borderPaint.takeIf { android.graphics.Color.alpha(chip.borderColor) > 0 && chip.borderWidth > 0f },
|
|
143
|
+
isFirst,
|
|
144
|
+
isLast,
|
|
145
|
+
strokeInset = true,
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
companion object {
|
|
150
|
+
/** A wrapped fragment ends before the whitespace the line break hangs. */
|
|
151
|
+
fun trimTrailingWhitespace(
|
|
152
|
+
text: CharSequence,
|
|
153
|
+
start: Int,
|
|
154
|
+
end: Int,
|
|
155
|
+
): Int {
|
|
156
|
+
var index = end
|
|
157
|
+
while (index > start && text[index - 1] != LinkSpacers.CHAR && text[index - 1].isWhitespace()) index--
|
|
158
|
+
return index
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Ascent/descent of the link's own font: [LinkSpan] sets its size and typeface at draw
|
|
163
|
+
* time only, so the base paint [p] alone would report the paragraph's metrics.
|
|
164
|
+
*/
|
|
165
|
+
fun linkFontMetrics(
|
|
166
|
+
text: Spanned,
|
|
167
|
+
spanStart: Int,
|
|
168
|
+
spanEnd: Int,
|
|
169
|
+
p: Paint,
|
|
170
|
+
): Pair<Float, Float> {
|
|
171
|
+
val paint = TextPaint(p)
|
|
172
|
+
text.getSpans(spanStart, spanEnd, LinkSpan::class.java).firstOrNull()?.updateDrawState(paint)
|
|
173
|
+
val metrics = paint.fontMetrics
|
|
174
|
+
return metrics.ascent to metrics.descent
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.swmansion.enriched.markdown.spans
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.graphics.Color
|
|
5
4
|
import android.text.TextPaint
|
|
6
5
|
import android.text.style.ClickableSpan
|
|
7
6
|
import android.view.View
|
|
@@ -57,10 +56,6 @@ class LinkSpan(
|
|
|
57
56
|
|
|
58
57
|
textPaint.color = variant?.color ?: styleCache.linkColor
|
|
59
58
|
textPaint.isUnderlineText = variant?.underline ?: styleCache.linkUnderline
|
|
60
|
-
|
|
61
|
-
val backgroundColor = variant?.backgroundColor ?: styleCache.linkBackgroundColor
|
|
62
|
-
if (Color.alpha(backgroundColor) > 0) {
|
|
63
|
-
textPaint.bgColor = backgroundColor
|
|
64
|
-
}
|
|
59
|
+
// The background is painted as a chip by LinkChipBackgroundSpan.
|
|
65
60
|
}
|
|
66
61
|
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
package com.swmansion.enriched.markdown.styles
|
|
2
2
|
|
|
3
3
|
import com.facebook.react.bridge.ReadableMap
|
|
4
|
+
import kotlin.math.ceil
|
|
4
5
|
|
|
5
6
|
data class InlineMathStyle(
|
|
6
7
|
val color: Int,
|
|
7
8
|
val paddingVertical: Float,
|
|
8
9
|
val fontWeight: String,
|
|
10
|
+
val glyphOverflow: Float = 0f,
|
|
9
11
|
) {
|
|
12
|
+
/** Line-box padding the span reports: author padding plus the ink headroom. */
|
|
13
|
+
val boxPaddingVertical: Float get() = paddingVertical + glyphOverflow
|
|
14
|
+
|
|
10
15
|
companion object {
|
|
11
16
|
fun fromReadableMap(
|
|
12
17
|
map: ReadableMap,
|
|
@@ -15,7 +20,17 @@ data class InlineMathStyle(
|
|
|
15
20
|
val color = parser.parseColor(map, "color")
|
|
16
21
|
val paddingVertical = parser.toPixelFromDIP(map.getDouble("paddingVertical").toFloat())
|
|
17
22
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
18
|
-
|
|
23
|
+
val glyphOverflow = glyphOverflowPx(parser.parseOptionalDouble(map, "glyphOverflow").toFloat(), parser)
|
|
24
|
+
return InlineMathStyle(color, paddingVertical, fontWeight, glyphOverflow)
|
|
19
25
|
}
|
|
20
26
|
}
|
|
21
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `glyphOverflow` in whole pixels. Rounded up so a measured box and the bitmap or
|
|
31
|
+
* canvas it holds always grow by the same integer amount on each side.
|
|
32
|
+
*/
|
|
33
|
+
internal fun glyphOverflowPx(
|
|
34
|
+
dip: Float,
|
|
35
|
+
parser: StyleParser,
|
|
36
|
+
): Float = if (dip <= 0f) 0f else ceil(parser.toPixelFromDIP(dip))
|
|
@@ -7,6 +7,7 @@ data class LinkStyle(
|
|
|
7
7
|
val color: Int,
|
|
8
8
|
val underline: Boolean,
|
|
9
9
|
val backgroundColor: Int,
|
|
10
|
+
val chip: LinkChipStyle,
|
|
10
11
|
) {
|
|
11
12
|
companion object {
|
|
12
13
|
fun fromReadableMap(
|
|
@@ -17,7 +18,41 @@ data class LinkStyle(
|
|
|
17
18
|
val color = parser.parseColor(map, "color")
|
|
18
19
|
val underline = map.getBoolean("underline")
|
|
19
20
|
val backgroundColor = parser.parseColor(map, "backgroundColor")
|
|
20
|
-
return LinkStyle(fontFamily, color, underline, backgroundColor)
|
|
21
|
+
return LinkStyle(fontFamily, color, underline, backgroundColor, LinkChipStyle.fromReadableMap(map, parser, backgroundColor))
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Chip ("box") properties of a link, in pixels. Never inherited from the base link
|
|
28
|
+
* style by variants — the JS normalizer fills missing values with 0 / transparent.
|
|
29
|
+
*/
|
|
30
|
+
data class LinkChipStyle(
|
|
31
|
+
val backgroundColor: Int,
|
|
32
|
+
val borderColor: Int,
|
|
33
|
+
val borderRadius: Float,
|
|
34
|
+
val borderWidth: Float,
|
|
35
|
+
val paddingHorizontal: Float,
|
|
36
|
+
val paddingVertical: Float,
|
|
37
|
+
) {
|
|
38
|
+
val isVisible: Boolean
|
|
39
|
+
get() =
|
|
40
|
+
android.graphics.Color.alpha(backgroundColor) > 0 ||
|
|
41
|
+
(android.graphics.Color.alpha(borderColor) > 0 && borderWidth > 0f)
|
|
42
|
+
|
|
43
|
+
companion object {
|
|
44
|
+
fun fromReadableMap(
|
|
45
|
+
map: ReadableMap,
|
|
46
|
+
parser: StyleParser,
|
|
47
|
+
backgroundColor: Int,
|
|
48
|
+
): LinkChipStyle =
|
|
49
|
+
LinkChipStyle(
|
|
50
|
+
backgroundColor = backgroundColor,
|
|
51
|
+
borderColor = parser.parseOptionalColor(map, "borderColor") ?: 0,
|
|
52
|
+
borderRadius = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderRadius").toFloat()),
|
|
53
|
+
borderWidth = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderWidth").toFloat()),
|
|
54
|
+
paddingHorizontal = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingHorizontal").toFloat()),
|
|
55
|
+
paddingVertical = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingVertical").toFloat()),
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -13,17 +13,21 @@ data class LinkVariantEntry(
|
|
|
13
13
|
val color: Int,
|
|
14
14
|
val underline: Boolean,
|
|
15
15
|
val backgroundColor: Int,
|
|
16
|
+
val chip: LinkChipStyle,
|
|
16
17
|
) {
|
|
17
18
|
companion object {
|
|
18
19
|
fun fromReadableMap(
|
|
19
20
|
map: ReadableMap,
|
|
20
21
|
parser: StyleParser,
|
|
21
|
-
): LinkVariantEntry
|
|
22
|
-
|
|
22
|
+
): LinkVariantEntry {
|
|
23
|
+
val backgroundColor = parser.parseColor(map, "backgroundColor")
|
|
24
|
+
return LinkVariantEntry(
|
|
23
25
|
pattern = map.getString("pattern") ?: "",
|
|
24
26
|
color = parser.parseColor(map, "color"),
|
|
25
27
|
underline = parser.parseBoolean(map, "underline"),
|
|
26
|
-
backgroundColor =
|
|
28
|
+
backgroundColor = backgroundColor,
|
|
29
|
+
chip = LinkChipStyle.fromReadableMap(map, parser, backgroundColor),
|
|
27
30
|
)
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
}
|
|
@@ -11,6 +11,7 @@ data class MathStyle(
|
|
|
11
11
|
val marginBottom: Float,
|
|
12
12
|
val textAlign: String,
|
|
13
13
|
val fontWeight: String,
|
|
14
|
+
val glyphOverflow: Float = 0f,
|
|
14
15
|
) {
|
|
15
16
|
companion object {
|
|
16
17
|
fun fromReadableMap(
|
|
@@ -25,6 +26,7 @@ data class MathStyle(
|
|
|
25
26
|
val marginBottom = parser.toPixelFromDIP(map.getDouble("marginBottom").toFloat())
|
|
26
27
|
val textAlign = parser.parseString(map, "textAlign", "center")
|
|
27
28
|
val fontWeight = parser.parseString(map, "fontWeight", "normal")
|
|
29
|
+
val glyphOverflow = glyphOverflowPx(parser.parseOptionalDouble(map, "glyphOverflow").toFloat(), parser)
|
|
28
30
|
return MathStyle(
|
|
29
31
|
fontSize,
|
|
30
32
|
color,
|
|
@@ -34,6 +36,7 @@ data class MathStyle(
|
|
|
34
36
|
marginBottom,
|
|
35
37
|
textAlign,
|
|
36
38
|
fontWeight,
|
|
39
|
+
glyphOverflow,
|
|
37
40
|
)
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -7,11 +7,16 @@ import com.facebook.react.uimanager.UIManagerHelper
|
|
|
7
7
|
import com.swmansion.enriched.markdown.accessibility.AccessibilityLabels
|
|
8
8
|
import com.swmansion.enriched.markdown.events.ContextMenuItemPressEvent
|
|
9
9
|
import com.swmansion.enriched.markdown.events.CopyPressEvent
|
|
10
|
+
import com.swmansion.enriched.markdown.events.CopyRequestEvent
|
|
11
|
+
import com.swmansion.enriched.markdown.events.LinkLayoutEvent
|
|
10
12
|
import com.swmansion.enriched.markdown.events.LinkLongPressEvent
|
|
11
13
|
import com.swmansion.enriched.markdown.events.LinkPressEvent
|
|
14
|
+
import com.swmansion.enriched.markdown.events.SelectionChangeEvent
|
|
12
15
|
import com.swmansion.enriched.markdown.events.TaskListItemPressEvent
|
|
13
16
|
import com.swmansion.enriched.markdown.parser.Md4cFlags
|
|
17
|
+
import com.swmansion.enriched.markdown.utils.text.view.LinkLayoutFragment
|
|
14
18
|
import com.swmansion.enriched.markdown.utils.text.view.SelectionMenuConfig
|
|
19
|
+
import com.swmansion.enriched.markdown.utils.text.view.SelectionSnapshot
|
|
15
20
|
|
|
16
21
|
fun markdownEventTypeConstants(): MutableMap<String, Any> {
|
|
17
22
|
val map = mutableMapOf<String, Any>()
|
|
@@ -22,11 +27,37 @@ fun markdownEventTypeConstants(): MutableMap<String, Any> {
|
|
|
22
27
|
mapOf("registrationName" to TaskListItemPressEvent.EVENT_NAME)
|
|
23
28
|
map[CopyPressEvent.EVENT_NAME] =
|
|
24
29
|
mapOf("registrationName" to CopyPressEvent.EVENT_NAME)
|
|
30
|
+
map[CopyRequestEvent.EVENT_NAME] =
|
|
31
|
+
mapOf("registrationName" to CopyRequestEvent.EVENT_NAME)
|
|
25
32
|
map[ContextMenuItemPressEvent.EVENT_NAME] =
|
|
26
33
|
mapOf("registrationName" to ContextMenuItemPressEvent.EVENT_NAME)
|
|
34
|
+
map[LinkLayoutEvent.EVENT_NAME] =
|
|
35
|
+
mapOf("registrationName" to LinkLayoutEvent.EVENT_NAME)
|
|
36
|
+
map[SelectionChangeEvent.EVENT_NAME] =
|
|
37
|
+
mapOf("registrationName" to SelectionChangeEvent.EVENT_NAME)
|
|
27
38
|
return map
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
fun emitLinkLayout(
|
|
42
|
+
view: View,
|
|
43
|
+
fragments: List<LinkLayoutFragment>,
|
|
44
|
+
) {
|
|
45
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
46
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
47
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
48
|
+
eventDispatcher?.dispatchEvent(LinkLayoutEvent(surfaceId, view.id, fragments))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun emitSelectionChange(
|
|
52
|
+
view: View,
|
|
53
|
+
snapshot: SelectionSnapshot,
|
|
54
|
+
) {
|
|
55
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
56
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
57
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
58
|
+
eventDispatcher?.dispatchEvent(SelectionChangeEvent(surfaceId, view.id, snapshot))
|
|
59
|
+
}
|
|
60
|
+
|
|
30
61
|
fun emitLinkPress(
|
|
31
62
|
view: View,
|
|
32
63
|
url: String,
|
|
@@ -74,6 +105,21 @@ fun emitCopyPress(
|
|
|
74
105
|
)
|
|
75
106
|
}
|
|
76
107
|
|
|
108
|
+
fun emitCopyRequest(
|
|
109
|
+
view: View,
|
|
110
|
+
source: String,
|
|
111
|
+
markdown: String,
|
|
112
|
+
text: String,
|
|
113
|
+
whole: Boolean,
|
|
114
|
+
) {
|
|
115
|
+
val context = view.context as com.facebook.react.bridge.ReactContext
|
|
116
|
+
val surfaceId = UIManagerHelper.getSurfaceId(context)
|
|
117
|
+
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id)
|
|
118
|
+
eventDispatcher?.dispatchEvent(
|
|
119
|
+
CopyRequestEvent(surfaceId, view.id, source, markdown, text, whole),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
77
123
|
fun emitContextMenuItemPress(
|
|
78
124
|
view: View,
|
|
79
125
|
itemText: String,
|
|
@@ -128,6 +174,11 @@ fun parseSelectionMenuConfig(value: ReadableMap?): SelectionMenuConfig {
|
|
|
128
174
|
copyImageUrlLabel = value.getString("copyImageUrlLabel") ?: "",
|
|
129
175
|
copyImageUrlsLabel = value.getString("copyImageUrlsLabel") ?: "",
|
|
130
176
|
copyImageUrlPluralTemplates = parseStringList(value.getArray("copyImageUrlPluralTemplates")),
|
|
177
|
+
copyEnabled = value.getBooleanOrDefault("copyEnabled", true),
|
|
178
|
+
copyFormatMarkdown = value.getBooleanOrDefault("copyFormatMarkdown", false),
|
|
179
|
+
systemItemsNone = value.getBooleanOrDefault("systemItemsNone", false),
|
|
180
|
+
codeBlockCopyButton = value.getBooleanOrDefault("codeBlockCopyButton", true),
|
|
181
|
+
interceptCopy = value.getBooleanOrDefault("interceptCopy", false),
|
|
131
182
|
)
|
|
132
183
|
}
|
|
133
184
|
|
|
@@ -2,10 +2,17 @@ package com.swmansion.enriched.markdown.utils.input
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The pure half of the paste interception path. A `ClipData` cannot be built on
|
|
5
|
-
* the JVM, so the joining
|
|
6
|
-
*
|
|
5
|
+
* the JVM, so the joining and matching rules live here where a unit test can
|
|
6
|
+
* reach them; the call sites (context menu, `onReceiveContent` and the IME
|
|
7
|
+
* commit) only extract the items.
|
|
7
8
|
*/
|
|
8
9
|
object PasteTextUtils {
|
|
10
|
+
/**
|
|
11
|
+
* Shortest single-line IME commit that is compared against the clipboard.
|
|
12
|
+
* Longer than a word picked from the suggestion strip.
|
|
13
|
+
*/
|
|
14
|
+
const val IME_PASTE_MIN_LENGTH = 16
|
|
15
|
+
|
|
9
16
|
/**
|
|
10
17
|
* Flattens a clip's items into one string. A clip regularly carries more than
|
|
11
18
|
* one item — reading only `getItemAt(0)` silently drops items 2..n. Items
|
|
@@ -17,4 +24,29 @@ object PasteTextUtils {
|
|
|
17
24
|
.mapNotNull { it?.toString() }
|
|
18
25
|
.filter { it.isNotEmpty() }
|
|
19
26
|
.joinToString("\n")
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether an IME `commitText` is worth comparing against the clipboard.
|
|
30
|
+
*
|
|
31
|
+
* Reading the clip is not free: on Android 12+ a read can show the system
|
|
32
|
+
* "pasted from your clipboard" toast, so it must not happen while the user is
|
|
33
|
+
* just typing. Typing, autocorrect and suggestion picks commit a word at a
|
|
34
|
+
* time and stay below [IME_PASTE_MIN_LENGTH]. A shorter commit is still
|
|
35
|
+
* compared when it spans lines, since only a paste or a multi-line dictation
|
|
36
|
+
* produces one — but not a lone "\n", which some keyboards commit for Enter.
|
|
37
|
+
*/
|
|
38
|
+
fun mayBeImePaste(committed: String): Boolean =
|
|
39
|
+
committed.length >= IME_PASTE_MIN_LENGTH ||
|
|
40
|
+
(committed.length > 1 && committed.contains('\n'))
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether an IME commit is the keyboard pasting the clipboard. Only an exact
|
|
44
|
+
* match counts: anything the user typed or dictated differs from the clip.
|
|
45
|
+
* Text the keyboard changed on the way (trimmed, an older clipboard-history
|
|
46
|
+
* entry, one item of a multi-item clip) does not match and inserts inline.
|
|
47
|
+
*/
|
|
48
|
+
fun isImePaste(
|
|
49
|
+
committed: String,
|
|
50
|
+
clip: String,
|
|
51
|
+
): Boolean = clip.isNotEmpty() && clip == committed
|
|
20
52
|
}
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/HTMLGenerator.kt
CHANGED
|
@@ -12,6 +12,7 @@ import com.swmansion.enriched.markdown.spans.EmphasisSpan
|
|
|
12
12
|
import com.swmansion.enriched.markdown.spans.HeadingSpan
|
|
13
13
|
import com.swmansion.enriched.markdown.spans.HighlightSpan
|
|
14
14
|
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
15
|
+
import com.swmansion.enriched.markdown.spans.LinkSpacers
|
|
15
16
|
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
16
17
|
import com.swmansion.enriched.markdown.spans.OrderedListSpan
|
|
17
18
|
import com.swmansion.enriched.markdown.spans.StrikethroughSpan
|
|
@@ -676,7 +677,7 @@ object HTMLGenerator {
|
|
|
676
677
|
val segmentEnd = minOf(text.nextSpanTransition(i, end, Any::class.java), end)
|
|
677
678
|
val segmentText = text.subSequence(i, segmentEnd).toString()
|
|
678
679
|
|
|
679
|
-
if (segmentText.isNotEmpty() && segmentText != "\n") {
|
|
680
|
+
if (segmentText.isNotEmpty() && segmentText != "\n" && !LinkSpacers.isSpacer(text, i, segmentEnd)) {
|
|
680
681
|
appendStyledSegment(html, text, i, segmentEnd, segmentText, styles, isCodeBlock)
|
|
681
682
|
}
|
|
682
683
|
i = segmentEnd
|
|
@@ -12,6 +12,7 @@ import com.swmansion.enriched.markdown.spans.EmphasisSpan
|
|
|
12
12
|
import com.swmansion.enriched.markdown.spans.HeadingSpan
|
|
13
13
|
import com.swmansion.enriched.markdown.spans.HighlightSpan
|
|
14
14
|
import com.swmansion.enriched.markdown.spans.ImageSpan
|
|
15
|
+
import com.swmansion.enriched.markdown.spans.LinkSpacers
|
|
15
16
|
import com.swmansion.enriched.markdown.spans.LinkSpan
|
|
16
17
|
import com.swmansion.enriched.markdown.spans.OrderedListSpan
|
|
17
18
|
import com.swmansion.enriched.markdown.spans.StrikethroughSpan
|
|
@@ -55,6 +56,11 @@ object MarkdownExtractor {
|
|
|
55
56
|
var i = start
|
|
56
57
|
while (i < end) {
|
|
57
58
|
val nextTransition = spannable.nextSpanTransition(i, end, Any::class.java)
|
|
59
|
+
// Link spacers only reserve layout space for chip padding.
|
|
60
|
+
if (LinkSpacers.isSpacer(spannable, i, nextTransition)) {
|
|
61
|
+
i = nextTransition
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
58
64
|
val segmentText = spannable.subSequence(i, nextTransition).toString()
|
|
59
65
|
|
|
60
66
|
val handled =
|
package/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/HeadlessActionMode.kt
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package com.swmansion.enriched.markdown.utils.text.view
|
|
2
|
+
|
|
3
|
+
import android.view.ActionMode
|
|
4
|
+
import android.view.Menu
|
|
5
|
+
import android.view.MenuInflater
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.widget.PopupMenu
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stands in for the floating text-selection toolbar while `contextMenuHidden`
|
|
11
|
+
* is on. An empty menu only hides the AOSP FloatingToolbar; ROMs that draw
|
|
12
|
+
* their own selection popup (MIUI) still show it. Answering the Editor's
|
|
13
|
+
* startActionMode with this mode means no toolbar window is ever created.
|
|
14
|
+
*
|
|
15
|
+
* The callback is the Editor's TextActionModeCallback wrapping ours, so it
|
|
16
|
+
* still gets the full create/prepare/destroy lifecycle: Editor keeps the
|
|
17
|
+
* selection handles and its transient-state bookkeeping, and finish() clears
|
|
18
|
+
* the selection exactly as dismissing the real toolbar would.
|
|
19
|
+
*/
|
|
20
|
+
internal class HeadlessActionMode private constructor(
|
|
21
|
+
private val view: View,
|
|
22
|
+
private val callback: ActionMode.Callback,
|
|
23
|
+
) : ActionMode() {
|
|
24
|
+
// A real framework menu: Editor populates it with MenuItem calls
|
|
25
|
+
// (setAlphabeticShortcut, setShowAsAction, setIntent) a stub can't take.
|
|
26
|
+
private val menu: Menu = PopupMenu(view.context, view).menu
|
|
27
|
+
private var finished = false
|
|
28
|
+
|
|
29
|
+
override fun setTitle(title: CharSequence?) = Unit
|
|
30
|
+
|
|
31
|
+
override fun setTitle(resId: Int) = Unit
|
|
32
|
+
|
|
33
|
+
override fun setSubtitle(subtitle: CharSequence?) = Unit
|
|
34
|
+
|
|
35
|
+
override fun setSubtitle(resId: Int) = Unit
|
|
36
|
+
|
|
37
|
+
override fun setCustomView(view: View?) = Unit
|
|
38
|
+
|
|
39
|
+
override fun invalidate() {
|
|
40
|
+
if (!finished) callback.onPrepareActionMode(this, menu)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override fun invalidateContentRect() = Unit
|
|
44
|
+
|
|
45
|
+
override fun hide(duration: Long) = Unit
|
|
46
|
+
|
|
47
|
+
override fun finish() {
|
|
48
|
+
if (finished) return
|
|
49
|
+
finished = true
|
|
50
|
+
callback.onDestroyActionMode(this)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun getMenu(): Menu = menu
|
|
54
|
+
|
|
55
|
+
override fun getTitle(): CharSequence? = null
|
|
56
|
+
|
|
57
|
+
override fun getSubtitle(): CharSequence? = null
|
|
58
|
+
|
|
59
|
+
override fun getCustomView(): View? = null
|
|
60
|
+
|
|
61
|
+
override fun getMenuInflater(): MenuInflater = MenuInflater(view.context)
|
|
62
|
+
|
|
63
|
+
companion object {
|
|
64
|
+
/** Mirrors the framework: null when the callback declines to start. */
|
|
65
|
+
fun start(
|
|
66
|
+
view: View,
|
|
67
|
+
callback: ActionMode.Callback,
|
|
68
|
+
): ActionMode? {
|
|
69
|
+
val mode = HeadlessActionMode(view, callback)
|
|
70
|
+
mode.type = TYPE_FLOATING
|
|
71
|
+
if (!callback.onCreateActionMode(mode, mode.menu)) return null
|
|
72
|
+
callback.onPrepareActionMode(mode, mode.menu)
|
|
73
|
+
return mode
|
|
74
|
+
}
|
|
75
|
+
}
|
|
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)
|