react-native-enriched 0.2.1 → 0.3.0

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.
Files changed (156) hide show
  1. package/README.md +15 -12
  2. package/android/build.gradle +77 -72
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +18 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +6 -0
  5. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +146 -0
  6. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +140 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +10 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +194 -0
  9. package/android/lint.gradle +70 -0
  10. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputConnectionWrapper.kt +140 -0
  11. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +245 -116
  12. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +3 -1
  13. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +162 -53
  14. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +1 -3
  15. package/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt +70 -21
  16. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +20 -10
  17. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +8 -9
  18. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +10 -9
  19. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateDeprecatedEvent.kt +21 -0
  20. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +9 -12
  21. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +10 -10
  22. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +7 -9
  23. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +7 -9
  24. package/android/src/main/java/com/swmansion/enriched/events/OnInputKeyPressEvent.kt +27 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +13 -11
  26. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +10 -9
  27. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +9 -8
  28. package/android/src/main/java/com/swmansion/enriched/events/OnRequestHtmlResultEvent.kt +1 -2
  29. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +21 -8
  30. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +5 -4
  31. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +7 -5
  32. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +5 -4
  33. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +5 -4
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +5 -4
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt +24 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt +24 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt +24 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +29 -17
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +5 -4
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +5 -4
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +7 -7
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +11 -14
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +15 -14
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +167 -71
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +5 -4
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +5 -4
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +8 -8
  48. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +3 -2
  49. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +1 -2
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +1 -2
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +3 -2
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +1 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +1 -2
  54. package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +2 -1
  55. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +78 -21
  56. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +25 -8
  57. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +60 -20
  58. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +86 -26
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +128 -52
  60. package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +10 -7
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedConstants.kt +11 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedEditableFactory.kt +17 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +128 -87
  64. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +71 -42
  65. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +183 -48
  66. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpannable.kt +82 -0
  67. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpannableStringBuilder.kt +15 -0
  68. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +0 -70
  69. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +46 -14
  70. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +34 -11
  71. package/android/src/main/new_arch/CMakeLists.txt +6 -0
  72. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +21 -1
  73. package/ios/EnrichedTextInputView.h +1 -1
  74. package/ios/EnrichedTextInputView.mm +381 -49
  75. package/ios/config/InputConfig.h +18 -0
  76. package/ios/config/InputConfig.mm +118 -8
  77. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +146 -0
  78. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +140 -0
  79. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +10 -0
  80. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +194 -0
  81. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +74 -0
  82. package/ios/inputParser/InputParser.mm +83 -10
  83. package/ios/{attachments → interfaces}/ImageAttachment.mm +3 -1
  84. package/ios/interfaces/LinkRegexConfig.h +19 -0
  85. package/ios/interfaces/LinkRegexConfig.mm +37 -0
  86. package/ios/{utils → interfaces}/MentionStyleProps.mm +2 -2
  87. package/ios/{utils → interfaces}/StyleHeaders.h +10 -0
  88. package/ios/{utils → interfaces}/StyleTypeEnum.h +3 -0
  89. package/ios/styles/BlockQuoteStyle.mm +5 -5
  90. package/ios/styles/BoldStyle.mm +21 -6
  91. package/ios/styles/CodeBlockStyle.mm +5 -5
  92. package/ios/styles/H4Style.mm +17 -0
  93. package/ios/styles/H5Style.mm +17 -0
  94. package/ios/styles/H6Style.mm +17 -0
  95. package/ios/styles/HeadingStyleBase.mm +27 -10
  96. package/ios/styles/ImageStyle.mm +5 -5
  97. package/ios/styles/InlineCodeStyle.mm +30 -19
  98. package/ios/styles/ItalicStyle.mm +5 -5
  99. package/ios/styles/LinkStyle.mm +98 -40
  100. package/ios/styles/MentionStyle.mm +4 -4
  101. package/ios/styles/OrderedListStyle.mm +5 -5
  102. package/ios/styles/StrikethroughStyle.mm +5 -5
  103. package/ios/styles/UnderlineStyle.mm +5 -5
  104. package/ios/styles/UnorderedListStyle.mm +5 -5
  105. package/ios/utils/ParagraphAttributesUtils.h +4 -0
  106. package/ios/utils/ParagraphAttributesUtils.mm +67 -0
  107. package/ios/utils/ParagraphsUtils.mm +4 -4
  108. package/lib/module/EnrichedTextInput.js +22 -1
  109. package/lib/module/EnrichedTextInput.js.map +1 -1
  110. package/lib/module/EnrichedTextInputNativeComponent.ts +138 -12
  111. package/lib/module/{normalizeHtmlStyle.js → utils/normalizeHtmlStyle.js} +12 -0
  112. package/lib/module/utils/normalizeHtmlStyle.js.map +1 -0
  113. package/lib/module/utils/regexParser.js +46 -0
  114. package/lib/module/utils/regexParser.js.map +1 -0
  115. package/lib/typescript/src/EnrichedTextInput.d.ts +23 -14
  116. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  117. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +123 -12
  118. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  119. package/lib/typescript/src/index.d.ts +1 -1
  120. package/lib/typescript/src/index.d.ts.map +1 -1
  121. package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts +4 -0
  122. package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts.map +1 -0
  123. package/lib/typescript/src/utils/regexParser.d.ts +3 -0
  124. package/lib/typescript/src/utils/regexParser.d.ts.map +1 -0
  125. package/package.json +10 -6
  126. package/src/EnrichedTextInput.tsx +51 -13
  127. package/src/EnrichedTextInputNativeComponent.ts +138 -12
  128. package/src/index.tsx +2 -0
  129. package/src/{normalizeHtmlStyle.ts → utils/normalizeHtmlStyle.ts} +14 -2
  130. package/src/utils/regexParser.ts +56 -0
  131. package/lib/module/normalizeHtmlStyle.js.map +0 -1
  132. package/lib/typescript/src/normalizeHtmlStyle.d.ts +0 -4
  133. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +0 -1
  134. /package/ios/{utils → extensions}/ColorExtension.h +0 -0
  135. /package/ios/{utils → extensions}/ColorExtension.mm +0 -0
  136. /package/ios/{utils → extensions}/FontExtension.h +0 -0
  137. /package/ios/{utils → extensions}/FontExtension.mm +0 -0
  138. /package/ios/{utils → extensions}/LayoutManagerExtension.h +0 -0
  139. /package/ios/{utils → extensions}/LayoutManagerExtension.mm +0 -0
  140. /package/ios/{utils → extensions}/StringExtension.h +0 -0
  141. /package/ios/{utils → extensions}/StringExtension.mm +0 -0
  142. /package/ios/{utils → interfaces}/BaseStyleProtocol.h +0 -0
  143. /package/ios/{attachments → interfaces}/ImageAttachment.h +0 -0
  144. /package/ios/{utils → interfaces}/ImageData.h +0 -0
  145. /package/ios/{utils → interfaces}/ImageData.mm +0 -0
  146. /package/ios/{utils → interfaces}/LinkData.h +0 -0
  147. /package/ios/{utils → interfaces}/LinkData.mm +0 -0
  148. /package/ios/{attachments → interfaces}/MediaAttachment.h +0 -0
  149. /package/ios/{attachments → interfaces}/MediaAttachment.mm +0 -0
  150. /package/ios/{utils → interfaces}/MentionParams.h +0 -0
  151. /package/ios/{utils → interfaces}/MentionParams.mm +0 -0
  152. /package/ios/{utils → interfaces}/MentionStyleProps.h +0 -0
  153. /package/ios/{utils → interfaces}/StylePair.h +0 -0
  154. /package/ios/{utils → interfaces}/StylePair.mm +0 -0
  155. /package/ios/{utils → interfaces}/TextDecorationLineEnum.h +0 -0
  156. /package/ios/{utils → interfaces}/TextDecorationLineEnum.mm +0 -0
@@ -28,6 +28,15 @@ class HtmlStyle {
28
28
  var h3FontSize: Int = 56
29
29
  var h3Bold: Boolean = false
30
30
 
31
+ var h4FontSize: Int = 48
32
+ var h4Bold: Boolean = false
33
+
34
+ var h5FontSize: Int = 40
35
+ var h5Bold: Boolean = false
36
+
37
+ var h6FontSize: Int = 32
38
+ var h6Bold: Boolean = false
39
+
31
40
  var blockquoteColor: Int? = null
32
41
  var blockquoteBorderColor: Int = Color.BLACK
33
42
  var blockquoteStripeWidth: Int = 2
@@ -77,6 +86,18 @@ class HtmlStyle {
77
86
  h3FontSize = parseFloat(h3Style, "fontSize").toInt()
78
87
  h3Bold = h3Style?.getBoolean("bold") == true
79
88
 
89
+ val h4Style = style.getMap("h4")
90
+ h4FontSize = parseFloat(h4Style, "fontSize").toInt()
91
+ h4Bold = h4Style?.getBoolean("bold") == true
92
+
93
+ val h5Style = style.getMap("h5")
94
+ h5FontSize = parseFloat(h5Style, "fontSize").toInt()
95
+ h5Bold = h5Style?.getBoolean("bold") == true
96
+
97
+ val h6Style = style.getMap("h6")
98
+ h6FontSize = parseFloat(h6Style, "fontSize").toInt()
99
+ h6Bold = h6Style?.getBoolean("bold") == true
100
+
80
101
  val blockquoteStyle = style.getMap("blockquote")
81
102
  blockquoteColor = parseOptionalColor(blockquoteStyle, "color")
82
103
  blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor")
@@ -114,19 +135,29 @@ class HtmlStyle {
114
135
  mentionsStyle = parseMentionsStyle(mentionStyle)
115
136
  }
116
137
 
117
- private fun parseFloat(map: ReadableMap?, key: String): Float {
138
+ private fun parseFloat(
139
+ map: ReadableMap?,
140
+ key: String,
141
+ ): Float {
118
142
  val safeMap = ensureValueIsSet(map, key)
119
143
 
120
144
  val value = safeMap.getDouble(key)
121
145
  return ceil(PixelUtil.toPixelFromSP(value))
122
146
  }
123
147
 
124
- private fun parseColorWithOpacity(map: ReadableMap?, key: String, opacity: Int): Int {
148
+ private fun parseColorWithOpacity(
149
+ map: ReadableMap?,
150
+ key: String,
151
+ opacity: Int,
152
+ ): Int {
125
153
  val color = parseColor(map, key)
126
154
  return withOpacity(color, opacity)
127
155
  }
128
156
 
129
- private fun parseOptionalColor(map: ReadableMap?, key: String): Int? {
157
+ private fun parseOptionalColor(
158
+ map: ReadableMap?,
159
+ key: String,
160
+ ): Int? {
130
161
  if (map == null) return null
131
162
  if (!map.hasKey(key)) return null
132
163
  if (map.isNull(key)) return null
@@ -134,7 +165,10 @@ class HtmlStyle {
134
165
  return parseColor(map, key)
135
166
  }
136
167
 
137
- private fun parseColor(map: ReadableMap?, key: String): Int {
168
+ private fun parseColor(
169
+ map: ReadableMap?,
170
+ key: String,
171
+ ): Int {
138
172
  val safeMap = ensureValueIsSet(map, key)
139
173
 
140
174
  val color = safeMap.getDouble(key)
@@ -146,7 +180,10 @@ class HtmlStyle {
146
180
  return parsedColor
147
181
  }
148
182
 
149
- private fun withOpacity(color: Int, alpha: Int): Int {
183
+ private fun withOpacity(
184
+ color: Int,
185
+ alpha: Int,
186
+ ): Int {
150
187
  // Do not apply opacity to transparent color
151
188
  if (Color.alpha(color) == 0) return color
152
189
  val a = alpha.coerceIn(0, 255)
@@ -164,14 +201,20 @@ class HtmlStyle {
164
201
  throw Error("Specified textDecorationLine value is not supported: $underline. Supported values are 'underline' and 'none'.")
165
202
  }
166
203
 
167
- private fun calculateOlMarginLeft(view: EnrichedTextInputView?, userMargin: Int): Int {
204
+ private fun calculateOlMarginLeft(
205
+ view: EnrichedTextInputView?,
206
+ userMargin: Int,
207
+ ): Int {
168
208
  val fontSize = view?.fontSize?.toInt() ?: 0
169
209
  val leadMargin = fontSize / 2
170
210
 
171
211
  return leadMargin + userMargin
172
212
  }
173
213
 
174
- private fun ensureValueIsSet(map: ReadableMap?, key: String): ReadableMap {
214
+ private fun ensureValueIsSet(
215
+ map: ReadableMap?,
216
+ key: String,
217
+ ): ReadableMap {
175
218
  if (map == null) throw Error("Style map cannot be null")
176
219
 
177
220
  if (!map.hasKey(key)) throw Error("Style map must contain key: $key")
@@ -186,24 +229,27 @@ class HtmlStyle {
186
229
 
187
230
  val parsedMentionsStyle: MutableMap<String, MentionStyle> = mutableMapOf()
188
231
 
189
- val iterator = mentionsStyle.keySetIterator()
190
- while (iterator.hasNextKey()) {
191
- val key = iterator.nextKey()
192
- val value = mentionsStyle.getMap(key)
232
+ val iterator = mentionsStyle.keySetIterator()
233
+ while (iterator.hasNextKey()) {
234
+ val key = iterator.nextKey()
235
+ val value = mentionsStyle.getMap(key)
193
236
 
194
- if (value == null) throw Error("Mention style for key '$key' cannot be null")
237
+ if (value == null) throw Error("Mention style for key '$key' cannot be null")
195
238
 
196
- val color = parseColor(value, "color")
197
- val backgroundColor = parseColorWithOpacity(value, "backgroundColor", 80)
198
- val isUnderline = parseIsUnderline(value)
199
- val parsedStyle = MentionStyle(color, backgroundColor, isUnderline)
200
- parsedMentionsStyle.put(key, parsedStyle)
201
- }
239
+ val color = parseColor(value, "color")
240
+ val backgroundColor = parseColorWithOpacity(value, "backgroundColor", 80)
241
+ val isUnderline = parseIsUnderline(value)
242
+ val parsedStyle = MentionStyle(color, backgroundColor, isUnderline)
243
+ parsedMentionsStyle.put(key, parsedStyle)
244
+ }
202
245
 
203
246
  return parsedMentionsStyle
204
247
  }
205
248
 
206
- private fun parseOptionalFontWeight(map: ReadableMap?, key: String): Int? {
249
+ private fun parseOptionalFontWeight(
250
+ map: ReadableMap?,
251
+ key: String,
252
+ ): Int? {
207
253
  if (map == null) return null
208
254
  if (!map.hasKey(key)) return null
209
255
  if (map.isNull(key)) return null
@@ -222,6 +268,12 @@ class HtmlStyle {
222
268
  h2Bold == other.h2Bold &&
223
269
  h3FontSize == other.h3FontSize &&
224
270
  h3Bold == other.h3Bold &&
271
+ h4FontSize == other.h4FontSize &&
272
+ h4Bold == other.h4Bold &&
273
+ h5FontSize == other.h5FontSize &&
274
+ h5Bold == other.h5Bold &&
275
+ h6FontSize == other.h6FontSize &&
276
+ h6Bold == other.h6Bold &&
225
277
 
226
278
  blockquoteColor == other.blockquoteColor &&
227
279
  blockquoteBorderColor == other.blockquoteBorderColor &&
@@ -251,7 +303,6 @@ class HtmlStyle {
251
303
  mentionsStyle == other.mentionsStyle
252
304
  }
253
305
 
254
-
255
306
  override fun hashCode(): Int {
256
307
  var result = h1FontSize.hashCode()
257
308
  result = 31 * result + h1Bold.hashCode()
@@ -259,6 +310,12 @@ class HtmlStyle {
259
310
  result = 31 * result + h2Bold.hashCode()
260
311
  result = 31 * result + h3FontSize.hashCode()
261
312
  result = 31 * result + h3Bold.hashCode()
313
+ result = 31 * result + h4FontSize.hashCode()
314
+ result = 31 * result + h4Bold.hashCode()
315
+ result = 31 * result + h5FontSize.hashCode()
316
+ result = 31 * result + h5Bold.hashCode()
317
+ result = 31 * result + h6FontSize.hashCode()
318
+ result = 31 * result + h6Bold.hashCode()
262
319
 
263
320
  result = 31 * result + (blockquoteColor ?: 0)
264
321
  result = 31 * result + blockquoteBorderColor.hashCode()
@@ -294,7 +351,7 @@ class HtmlStyle {
294
351
  data class MentionStyle(
295
352
  val color: Int,
296
353
  val backgroundColor: Int,
297
- val underline: Boolean
354
+ val underline: Boolean,
298
355
  )
299
356
  }
300
357
  }
@@ -6,8 +6,15 @@ import com.swmansion.enriched.EnrichedTextInputView
6
6
  import com.swmansion.enriched.spans.EnrichedSpans
7
7
  import com.swmansion.enriched.utils.getSafeSpanBoundaries
8
8
 
9
- class InlineStyles(private val view: EnrichedTextInputView) {
10
- private fun <T>setSpan(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
9
+ class InlineStyles(
10
+ private val view: EnrichedTextInputView,
11
+ ) {
12
+ private fun <T> setSpan(
13
+ spannable: Spannable,
14
+ type: Class<T>,
15
+ start: Int,
16
+ end: Int,
17
+ ) {
11
18
  val previousSpanStart = (start - 1).coerceAtLeast(0)
12
19
  val previousSpanEnd = previousSpanStart + 1
13
20
  val nextSpanStart = (end + 1).coerceAtMost(spannable.length)
@@ -37,7 +44,12 @@ class InlineStyles(private val view: EnrichedTextInputView) {
37
44
  spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
38
45
  }
39
46
 
40
- private fun <T>setAndMergeSpans(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
47
+ private fun <T> setAndMergeSpans(
48
+ spannable: Spannable,
49
+ type: Class<T>,
50
+ start: Int,
51
+ end: Int,
52
+ ) {
41
53
  val spans = spannable.getSpans(start, end, type)
42
54
 
43
55
  // No spans setup for current selection, means we just need to assign new span
@@ -89,7 +101,10 @@ class InlineStyles(private val view: EnrichedTextInputView) {
89
101
  }
90
102
  }
91
103
 
92
- fun afterTextChanged(s: Editable, endCursorPosition: Int) {
104
+ fun afterTextChanged(
105
+ s: Editable,
106
+ endCursorPosition: Int,
107
+ ) {
93
108
  for ((style, config) in EnrichedSpans.inlineSpans) {
94
109
  val start = view.spanState?.getStart(style) ?: continue
95
110
  var end = endCursorPosition
@@ -128,7 +143,11 @@ class InlineStyles(private val view: EnrichedTextInputView) {
128
143
  view.selection.validateStyles()
129
144
  }
130
145
 
131
- fun removeStyle(name: String, start: Int, end: Int): Boolean {
146
+ fun removeStyle(
147
+ name: String,
148
+ start: Int,
149
+ end: Int,
150
+ ): Boolean {
132
151
  val config = EnrichedSpans.inlineSpans[name] ?: return false
133
152
  val spannable = view.text as Spannable
134
153
  val spans = spannable.getSpans(start, end, config.clazz)
@@ -141,7 +160,5 @@ class InlineStyles(private val view: EnrichedTextInputView) {
141
160
  return true
142
161
  }
143
162
 
144
- fun getStyleRange(): Pair<Int, Int> {
145
- return view.selection?.getInlineSelection() ?: Pair(0, 0)
146
- }
163
+ fun getStyleRange(): Pair<Int, Int> = view.selection?.getInlineSelection() ?: Pair(0, 0)
147
164
  }
@@ -8,11 +8,19 @@ import com.swmansion.enriched.EnrichedTextInputView
8
8
  import com.swmansion.enriched.spans.EnrichedOrderedListSpan
9
9
  import com.swmansion.enriched.spans.EnrichedSpans
10
10
  import com.swmansion.enriched.spans.EnrichedUnorderedListSpan
11
+ import com.swmansion.enriched.utils.EnrichedConstants
11
12
  import com.swmansion.enriched.utils.getParagraphBounds
12
13
  import com.swmansion.enriched.utils.getSafeSpanBoundaries
13
-
14
- class ListStyles(private val view: EnrichedTextInputView) {
15
- private fun <T>getPreviousParagraphSpan(spannable: Spannable, s: Int, type: Class<T>): T? {
14
+ import com.swmansion.enriched.utils.removeZWS
15
+
16
+ class ListStyles(
17
+ private val view: EnrichedTextInputView,
18
+ ) {
19
+ private fun <T> getPreviousParagraphSpan(
20
+ spannable: Spannable,
21
+ s: Int,
22
+ type: Class<T>,
23
+ ): T? {
16
24
  if (s <= 0) return null
17
25
 
18
26
  val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(s - 1)
@@ -25,19 +33,31 @@ class ListStyles(private val view: EnrichedTextInputView) {
25
33
  return null
26
34
  }
27
35
 
28
- private fun <T>isPreviousParagraphList(spannable: Spannable, s: Int, type: Class<T>): Boolean {
36
+ private fun <T> isPreviousParagraphList(
37
+ spannable: Spannable,
38
+ s: Int,
39
+ type: Class<T>,
40
+ ): Boolean {
29
41
  val previousSpan = getPreviousParagraphSpan(spannable, s, type)
30
42
 
31
43
  return previousSpan != null
32
44
  }
33
45
 
34
- private fun getOrderedListIndex(spannable: Spannable, s: Int): Int {
46
+ private fun getOrderedListIndex(
47
+ spannable: Spannable,
48
+ s: Int,
49
+ ): Int {
35
50
  val span = getPreviousParagraphSpan(spannable, s, EnrichedOrderedListSpan::class.java)
36
51
  val index = span?.getIndex() ?: 0
37
52
  return index + 1
38
53
  }
39
54
 
40
- private fun setSpan(spannable: Spannable, name: String, start: Int, end: Int) {
55
+ private fun setSpan(
56
+ spannable: Spannable,
57
+ name: String,
58
+ start: Int,
59
+ end: Int,
60
+ ) {
41
61
  val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
42
62
 
43
63
  if (name == EnrichedSpans.UNORDERED_LIST) {
@@ -53,7 +73,12 @@ class ListStyles(private val view: EnrichedTextInputView) {
53
73
  }
54
74
  }
55
75
 
56
- private fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>): Boolean {
76
+ private fun <T> removeSpansForRange(
77
+ spannable: Spannable,
78
+ start: Int,
79
+ end: Int,
80
+ clazz: Class<T>,
81
+ ): Boolean {
57
82
  val ssb = spannable as SpannableStringBuilder
58
83
  val spans = ssb.getSpans(start, end, clazz)
59
84
  if (spans.isEmpty()) return false
@@ -62,13 +87,17 @@ class ListStyles(private val view: EnrichedTextInputView) {
62
87
  ssb.removeSpan(span)
63
88
  }
64
89
 
65
- ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))
90
+ ssb.removeZWS(start, end)
66
91
  return true
67
92
  }
68
93
 
69
- fun updateOrderedListIndexes(text: Spannable, position: Int) {
94
+ fun updateOrderedListIndexes(
95
+ text: Spannable,
96
+ position: Int,
97
+ ) {
70
98
  val spans = text.getSpans(position + 1, text.length, EnrichedOrderedListSpan::class.java)
71
- for (span in spans) {
99
+ val sortedSpans = spans.sortedBy { text.getSpanStart(it) }
100
+ for (span in sortedSpans) {
72
101
  val spanStart = text.getSpanStart(span)
73
102
  val index = getOrderedListIndex(text, spanStart)
74
103
  span.setIndex(index)
@@ -91,7 +120,7 @@ class ListStyles(private val view: EnrichedTextInputView) {
91
120
  }
92
121
 
93
122
  if (start == end) {
94
- spannable.insert(start, "\u200B")
123
+ spannable.insert(start, EnrichedConstants.ZWS_STRING)
95
124
  view.spanState?.setStart(name, start + 1)
96
125
  removeSpansForRange(spannable, start, end, config.clazz)
97
126
  setSpan(spannable, name, start, end + 1)
@@ -104,7 +133,7 @@ class ListStyles(private val view: EnrichedTextInputView) {
104
133
  removeSpansForRange(spannable, start, end, config.clazz)
105
134
 
106
135
  for (paragraph in paragraphs) {
107
- spannable.insert(currentStart, "\u200B")
136
+ spannable.insert(currentStart, EnrichedConstants.ZWS_STRING)
108
137
  val currentEnd = currentStart + paragraph.length + 1
109
138
  setSpan(spannable, name, currentStart, currentEnd)
110
139
 
@@ -114,7 +143,12 @@ class ListStyles(private val view: EnrichedTextInputView) {
114
143
  view.spanState?.setStart(name, currentStart)
115
144
  }
116
145
 
117
- private fun handleAfterTextChanged(s: Editable, name: String, endCursorPosition: Int, previousTextLength: Int) {
146
+ private fun handleAfterTextChanged(
147
+ s: Editable,
148
+ name: String,
149
+ endCursorPosition: Int,
150
+ previousTextLength: Int,
151
+ ) {
118
152
  val config = EnrichedSpans.listSpans[name] ?: return
119
153
  val cursorPosition = endCursorPosition.coerceAtMost(s.length)
120
154
  val (start, end) = s.getParagraphBounds(cursorPosition)
@@ -131,7 +165,7 @@ class ListStyles(private val view: EnrichedTextInputView) {
131
165
  }
132
166
 
133
167
  if (!isBackspace && isShortcut) {
134
- s.replace(start, cursorPosition, "\u200B")
168
+ s.replace(start, cursorPosition, EnrichedConstants.ZWS_STRING)
135
169
  setSpan(s, name, start, start + 1)
136
170
  // Inform that new span has been added
137
171
  view.selection?.validateStyles()
@@ -139,7 +173,7 @@ class ListStyles(private val view: EnrichedTextInputView) {
139
173
  }
140
174
 
141
175
  if (!isBackspace && isNewLine && isPreviousParagraphList(s, start, config.clazz)) {
142
- s.insert(cursorPosition, "\u200B")
176
+ s.insert(cursorPosition, EnrichedConstants.ZWS_STRING)
143
177
  setSpan(s, name, start, end + 1)
144
178
  // Inform that new span has been added
145
179
  view.selection?.validateStyles()
@@ -155,16 +189,22 @@ class ListStyles(private val view: EnrichedTextInputView) {
155
189
  }
156
190
  }
157
191
 
158
- fun afterTextChanged(s: Editable, endCursorPosition: Int, previousTextLength: Int) {
192
+ fun afterTextChanged(
193
+ s: Editable,
194
+ endCursorPosition: Int,
195
+ previousTextLength: Int,
196
+ ) {
159
197
  handleAfterTextChanged(s, EnrichedSpans.ORDERED_LIST, endCursorPosition, previousTextLength)
160
198
  handleAfterTextChanged(s, EnrichedSpans.UNORDERED_LIST, endCursorPosition, previousTextLength)
161
199
  }
162
200
 
163
- fun getStyleRange(): Pair<Int, Int> {
164
- return view.selection?.getParagraphSelection() ?: Pair(0, 0)
165
- }
201
+ fun getStyleRange(): Pair<Int, Int> = view.selection?.getParagraphSelection() ?: Pair(0, 0)
166
202
 
167
- fun removeStyle(name: String, start: Int, end: Int): Boolean {
203
+ fun removeStyle(
204
+ name: String,
205
+ start: Int,
206
+ end: Int,
207
+ ): Boolean {
168
208
  val config = EnrichedSpans.listSpans[name] ?: return false
169
209
  val spannable = view.text as Spannable
170
210
  return removeSpansForRange(spannable, start, end, config.clazz)
@@ -7,11 +7,19 @@ import android.util.Log
7
7
  import com.swmansion.enriched.EnrichedTextInputView
8
8
  import com.swmansion.enriched.spans.EnrichedSpans
9
9
  import com.swmansion.enriched.spans.interfaces.EnrichedSpan
10
+ import com.swmansion.enriched.utils.EnrichedConstants
10
11
  import com.swmansion.enriched.utils.getParagraphBounds
11
12
  import com.swmansion.enriched.utils.getSafeSpanBoundaries
12
-
13
- class ParagraphStyles(private val view: EnrichedTextInputView) {
14
- private fun <T>getPreviousParagraphSpan(spannable: Spannable, paragraphStart: Int, type: Class<T>): T? {
13
+ import com.swmansion.enriched.utils.removeZWS
14
+
15
+ class ParagraphStyles(
16
+ private val view: EnrichedTextInputView,
17
+ ) {
18
+ private fun <T> getPreviousParagraphSpan(
19
+ spannable: Spannable,
20
+ paragraphStart: Int,
21
+ type: Class<T>,
22
+ ): T? {
15
23
  if (paragraphStart <= 0) return null
16
24
 
17
25
  val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(paragraphStart - 1)
@@ -30,7 +38,11 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
30
38
  return null
31
39
  }
32
40
 
33
- private fun <T>getNextParagraphSpan(spannable: Spannable, paragraphEnd: Int, type: Class<T>): T? {
41
+ private fun <T> getNextParagraphSpan(
42
+ spannable: Spannable,
43
+ paragraphEnd: Int,
44
+ type: Class<T>,
45
+ ): T? {
34
46
  if (paragraphEnd >= spannable.length - 1) return null
35
47
 
36
48
  val (nextParagraphStart, nextParagraphEnd) = spannable.getParagraphBounds(paragraphEnd + 1)
@@ -54,7 +66,12 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
54
66
  * Applies a continuous span to the specified range.
55
67
  * If the new range touches existing continuous spans, they are coalesced into a single span
56
68
  */
57
- private fun <T>setContinuousSpan(spannable: Spannable, start: Int, end: Int, type: Class<T>) {
69
+ private fun <T> setContinuousSpan(
70
+ spannable: Spannable,
71
+ start: Int,
72
+ end: Int,
73
+ type: Class<T>,
74
+ ) {
58
75
  val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
59
76
  val previousSpan = getPreviousParagraphSpan(spannable, start, type)
60
77
  val nextSpan = getNextParagraphSpan(spannable, end, type)
@@ -75,8 +92,12 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
75
92
  spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
76
93
  }
77
94
 
78
-
79
- private fun <T>setSpan(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
95
+ private fun <T> setSpan(
96
+ spannable: Spannable,
97
+ type: Class<T>,
98
+ start: Int,
99
+ end: Int,
100
+ ) {
80
101
  if (EnrichedSpans.isTypeContinuous(type)) {
81
102
  setContinuousSpan(spannable, start, end, type)
82
103
  return
@@ -87,7 +108,12 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
87
108
  spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
88
109
  }
89
110
 
90
- private fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>): Boolean {
111
+ private fun <T> removeSpansForRange(
112
+ spannable: Spannable,
113
+ start: Int,
114
+ end: Int,
115
+ clazz: Class<T>,
116
+ ): Boolean {
91
117
  val ssb = spannable as SpannableStringBuilder
92
118
  var finalStart = start
93
119
  var finalEnd = end
@@ -102,11 +128,17 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
102
128
  ssb.removeSpan(span)
103
129
  }
104
130
 
105
- ssb.replace(finalStart, finalEnd, ssb.substring(finalStart, finalEnd).replace("\u200B", ""))
131
+ ssb.removeZWS(finalStart, finalEnd)
132
+
106
133
  return true
107
134
  }
108
135
 
109
- private fun <T>setAndMergeSpans(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
136
+ private fun <T> setAndMergeSpans(
137
+ spannable: Spannable,
138
+ type: Class<T>,
139
+ start: Int,
140
+ end: Int,
141
+ ) {
110
142
  val spans = spannable.getSpans(start, end, type)
111
143
 
112
144
  // No spans setup for current selection, means we just need to assign new span
@@ -157,7 +189,11 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
157
189
  }
158
190
  }
159
191
 
160
- private fun <T>isSpanEnabledInNextLine(spannable: Spannable, index: Int, type: Class<T>): Boolean {
192
+ private fun <T> isSpanEnabledInNextLine(
193
+ spannable: Spannable,
194
+ index: Int,
195
+ type: Class<T>,
196
+ ): Boolean {
161
197
  val selection = view.selection ?: return false
162
198
  if (index + 1 >= spannable.length) return false
163
199
  val (start, end) = selection.getParagraphSelection()
@@ -166,7 +202,11 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
166
202
  return spans.isNotEmpty()
167
203
  }
168
204
 
169
- private fun <T>mergeAdjacentStyleSpans(s: Editable, endCursorPosition: Int, type: Class<T>) {
205
+ private fun <T> mergeAdjacentStyleSpans(
206
+ s: Editable,
207
+ endCursorPosition: Int,
208
+ type: Class<T>,
209
+ ) {
170
210
  val (start, end) = s.getParagraphBounds(endCursorPosition)
171
211
  val currParagraphSpans = s.getSpans(start, end, type)
172
212
 
@@ -193,7 +233,12 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
193
233
  s.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
194
234
  }
195
235
 
196
- private fun handleConflictsDuringNewlineDeletion(s: Editable, style: String, paragraphStart: Int, paragraphEnd: Int): Boolean {
236
+ private fun handleConflictsDuringNewlineDeletion(
237
+ s: Editable,
238
+ style: String,
239
+ paragraphStart: Int,
240
+ paragraphEnd: Int,
241
+ ): Boolean {
197
242
  val spanState = view.spanState ?: return false
198
243
  val mergingConfig = EnrichedSpans.getMergingConfigForStyle(style, view.htmlStyle) ?: return false
199
244
  var isConflicting = false
@@ -219,7 +264,7 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
219
264
  }
220
265
 
221
266
  if (isConflicting) {
222
- val styleStart = spanState.getStart(style) ?: return false
267
+ val styleStart = spanState.getStart(style) ?: return false
223
268
  spanState.setStart(style, null)
224
269
  removeStyle(style, styleStart, paragraphEnd)
225
270
  return true
@@ -228,8 +273,12 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
228
273
  return false
229
274
  }
230
275
 
231
-
232
- private fun deleteConflictingAndBlockingStyles(s: Editable, style: String, paragraphStart: Int, paragraphEnd: Int) {
276
+ private fun deleteConflictingAndBlockingStyles(
277
+ s: Editable,
278
+ style: String,
279
+ paragraphStart: Int,
280
+ paragraphEnd: Int,
281
+ ) {
233
282
  val mergingConfig = EnrichedSpans.getMergingConfigForStyle(style, view.htmlStyle) ?: return
234
283
  val stylesToCheck = mergingConfig.blockingStyles + mergingConfig.conflictingStyles
235
284
 
@@ -243,17 +292,26 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
243
292
  }
244
293
  }
245
294
 
246
- private fun <T>extendStyleOnWholeParagraph(s: Editable, span: EnrichedSpan, type: Class<T>, paragraphEnd: Int) {
295
+ private fun <T> extendStyleOnWholeParagraph(
296
+ s: Editable,
297
+ span: EnrichedSpan,
298
+ type: Class<T>,
299
+ paragraphEnd: Int,
300
+ ) {
247
301
  val currStyleStart = s.getSpanStart(span)
248
302
  s.removeSpan(span)
249
303
  val (safeStart, safeEnd) = s.getSafeSpanBoundaries(currStyleStart, paragraphEnd)
250
304
  setSpan(s, type, safeStart, safeEnd)
251
305
  }
252
306
 
253
- fun afterTextChanged(s: Editable, endPosition: Int, previousTextLength: Int) {
307
+ fun afterTextChanged(
308
+ s: Editable,
309
+ endPosition: Int,
310
+ previousTextLength: Int,
311
+ ) {
254
312
  var endCursorPosition = endPosition
255
313
  val isBackspace = s.length < previousTextLength
256
- val isNewLine = endCursorPosition == 0 || endCursorPosition > 0 && s[endCursorPosition - 1] == '\n'
314
+ val isNewLine = endCursorPosition == 0 || (endCursorPosition > 0 && s[endCursorPosition - 1] == '\n')
257
315
  val spanState = view.spanState ?: return
258
316
 
259
317
  for ((style, config) in EnrichedSpans.paragraphSpans) {
@@ -287,7 +345,7 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
287
345
  endCursorPosition -= 1
288
346
  spanState.setStart(style, null)
289
347
  } else {
290
- s.insert(endCursorPosition, "\u200B")
348
+ s.insert(endCursorPosition, EnrichedConstants.ZWS_STRING)
291
349
  endCursorPosition += 1
292
350
  }
293
351
  }
@@ -336,7 +394,7 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
336
394
  }
337
395
 
338
396
  if (start == end) {
339
- spannable.insert(start, "\u200B")
397
+ spannable.insert(start, EnrichedConstants.ZWS_STRING)
340
398
  setAndMergeSpans(spannable, type, start, end + 1)
341
399
  view.selection.validateStyles()
342
400
 
@@ -348,7 +406,7 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
348
406
  val paragraphs = spannable.substring(start, end).split("\n")
349
407
 
350
408
  for (paragraph in paragraphs) {
351
- spannable.insert(currentStart, "\u200B")
409
+ spannable.insert(currentStart, EnrichedConstants.ZWS_STRING)
352
410
  currentEnd = currentStart + paragraph.length + 1
353
411
  currentStart = currentEnd + 1
354
412
  }
@@ -357,11 +415,13 @@ class ParagraphStyles(private val view: EnrichedTextInputView) {
357
415
  view.selection.validateStyles()
358
416
  }
359
417
 
360
- fun getStyleRange(): Pair<Int, Int> {
361
- return view.selection?.getParagraphSelection() ?: Pair(0, 0)
362
- }
418
+ fun getStyleRange(): Pair<Int, Int> = view.selection?.getParagraphSelection() ?: Pair(0, 0)
363
419
 
364
- fun removeStyle(name: String, start: Int, end: Int): Boolean {
420
+ fun removeStyle(
421
+ name: String,
422
+ start: Int,
423
+ end: Int,
424
+ ): Boolean {
365
425
  val config = EnrichedSpans.paragraphSpans[name] ?: return false
366
426
  val spannable = view.text as Spannable
367
427
  return removeSpansForRange(spannable, start, end, config.clazz)