react-native-enriched 0.0.0 → 0.1.1

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 (169) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +875 -0
  3. package/ReactNativeEnriched.podspec +27 -0
  4. package/android/build.gradle +101 -0
  5. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +146 -0
  6. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +55 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  9. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  10. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  11. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  12. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +577 -0
  13. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  14. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  15. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  16. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/States.h +20 -0
  17. package/android/gradle.properties +5 -0
  18. package/android/src/main/AndroidManifest.xml +3 -0
  19. package/android/src/main/AndroidManifestNew.xml +2 -0
  20. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +535 -0
  21. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +64 -0
  22. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +292 -0
  23. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +19 -0
  24. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +40 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +28 -0
  26. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +29 -0
  27. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +24 -0
  28. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +30 -0
  29. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +27 -0
  30. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +27 -0
  31. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +30 -0
  32. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +29 -0
  33. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +33 -0
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +34 -0
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +10 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +38 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +17 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +17 -0
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +17 -0
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +41 -0
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +16 -0
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +10 -0
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +24 -0
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +36 -0
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +71 -0
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +111 -0
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +9 -0
  48. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +9 -0
  49. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +49 -0
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +4 -0
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +4 -0
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +4 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +4 -0
  54. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  55. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +5 -0
  56. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +227 -0
  57. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +146 -0
  58. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +173 -0
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +186 -0
  60. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +223 -0
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +857 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +285 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +204 -0
  64. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +91 -0
  65. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +73 -0
  66. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +51 -0
  67. package/android/src/main/new_arch/CMakeLists.txt +56 -0
  68. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +22 -0
  69. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +26 -0
  70. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +35 -0
  71. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +51 -0
  72. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +26 -0
  73. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +34 -0
  74. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +54 -0
  75. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +9 -0
  76. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +25 -0
  77. package/ios/EnrichedTextInputView.h +33 -0
  78. package/ios/EnrichedTextInputView.mm +1190 -0
  79. package/ios/EnrichedTextInputViewManager.mm +13 -0
  80. package/ios/config/InputConfig.h +67 -0
  81. package/ios/config/InputConfig.mm +382 -0
  82. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.cpp +22 -0
  83. package/ios/generated/RNEnrichedTextInputViewSpec/ComponentDescriptors.h +24 -0
  84. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +118 -0
  85. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +95 -0
  86. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +128 -0
  87. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +577 -0
  88. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +384 -0
  89. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.cpp +17 -0
  90. package/ios/generated/RNEnrichedTextInputViewSpec/ShadowNodes.h +23 -0
  91. package/ios/generated/RNEnrichedTextInputViewSpec/States.cpp +16 -0
  92. package/ios/generated/RNEnrichedTextInputViewSpec/States.h +20 -0
  93. package/ios/inputParser/InputParser.h +11 -0
  94. package/ios/inputParser/InputParser.mm +659 -0
  95. package/ios/inputTextView/InputTextView.h +6 -0
  96. package/ios/inputTextView/InputTextView.mm +115 -0
  97. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +17 -0
  98. package/ios/internals/EnrichedTextInputViewShadowNode.h +40 -0
  99. package/ios/internals/EnrichedTextInputViewShadowNode.mm +83 -0
  100. package/ios/internals/EnrichedTextInputViewState.cpp +10 -0
  101. package/ios/internals/EnrichedTextInputViewState.h +20 -0
  102. package/ios/styles/BlockQuoteStyle.mm +248 -0
  103. package/ios/styles/BoldStyle.mm +122 -0
  104. package/ios/styles/H1Style.mm +10 -0
  105. package/ios/styles/H2Style.mm +10 -0
  106. package/ios/styles/H3Style.mm +10 -0
  107. package/ios/styles/HeadingStyleBase.mm +144 -0
  108. package/ios/styles/InlineCodeStyle.mm +163 -0
  109. package/ios/styles/ItalicStyle.mm +110 -0
  110. package/ios/styles/LinkStyle.mm +463 -0
  111. package/ios/styles/MentionStyle.mm +476 -0
  112. package/ios/styles/OrderedListStyle.mm +225 -0
  113. package/ios/styles/StrikethroughStyle.mm +80 -0
  114. package/ios/styles/UnderlineStyle.mm +112 -0
  115. package/ios/styles/UnorderedListStyle.mm +225 -0
  116. package/ios/utils/BaseStyleProtocol.h +16 -0
  117. package/ios/utils/ColorExtension.h +6 -0
  118. package/ios/utils/ColorExtension.mm +27 -0
  119. package/ios/utils/FontExtension.h +13 -0
  120. package/ios/utils/FontExtension.mm +91 -0
  121. package/ios/utils/LayoutManagerExtension.h +6 -0
  122. package/ios/utils/LayoutManagerExtension.mm +171 -0
  123. package/ios/utils/LinkData.h +9 -0
  124. package/ios/utils/LinkData.mm +4 -0
  125. package/ios/utils/MentionParams.h +9 -0
  126. package/ios/utils/MentionParams.mm +4 -0
  127. package/ios/utils/MentionStyleProps.h +13 -0
  128. package/ios/utils/MentionStyleProps.mm +56 -0
  129. package/ios/utils/OccurenceUtils.h +37 -0
  130. package/ios/utils/OccurenceUtils.mm +124 -0
  131. package/ios/utils/ParagraphsUtils.h +7 -0
  132. package/ios/utils/ParagraphsUtils.mm +54 -0
  133. package/ios/utils/StringExtension.h +15 -0
  134. package/ios/utils/StringExtension.mm +57 -0
  135. package/ios/utils/StyleHeaders.h +74 -0
  136. package/ios/utils/StylePair.h +9 -0
  137. package/ios/utils/StylePair.mm +4 -0
  138. package/ios/utils/StyleTypeEnum.h +22 -0
  139. package/ios/utils/TextDecorationLineEnum.h +6 -0
  140. package/ios/utils/TextDecorationLineEnum.mm +4 -0
  141. package/ios/utils/TextInsertionUtils.h +6 -0
  142. package/ios/utils/TextInsertionUtils.mm +48 -0
  143. package/ios/utils/WordsUtils.h +6 -0
  144. package/ios/utils/WordsUtils.mm +88 -0
  145. package/ios/utils/ZeroWidthSpaceUtils.h +7 -0
  146. package/ios/utils/ZeroWidthSpaceUtils.mm +164 -0
  147. package/lib/module/EnrichedTextInput.js +191 -0
  148. package/lib/module/EnrichedTextInput.js.map +1 -0
  149. package/lib/module/EnrichedTextInputNativeComponent.ts +235 -0
  150. package/lib/module/index.js +4 -0
  151. package/lib/module/index.js.map +1 -0
  152. package/lib/module/normalizeHtmlStyle.js +141 -0
  153. package/lib/module/normalizeHtmlStyle.js.map +1 -0
  154. package/lib/module/package.json +1 -0
  155. package/lib/typescript/package.json +1 -0
  156. package/lib/typescript/src/EnrichedTextInput.d.ts +113 -0
  157. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -0
  158. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +160 -0
  159. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -0
  160. package/lib/typescript/src/index.d.ts +3 -0
  161. package/lib/typescript/src/index.d.ts.map +1 -0
  162. package/lib/typescript/src/normalizeHtmlStyle.d.ts +4 -0
  163. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +1 -0
  164. package/package.json +172 -1
  165. package/react-native.config.js +13 -0
  166. package/src/EnrichedTextInput.tsx +358 -0
  167. package/src/EnrichedTextInputNativeComponent.ts +235 -0
  168. package/src/index.tsx +9 -0
  169. package/src/normalizeHtmlStyle.ts +188 -0
@@ -0,0 +1,227 @@
1
+ package com.swmansion.enriched.styles
2
+
3
+ import android.graphics.Color
4
+ import com.facebook.react.bridge.ColorPropConverter
5
+ import com.facebook.react.bridge.ReactContext
6
+ import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.uimanager.PixelUtil
8
+ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
9
+ import com.swmansion.enriched.EnrichedTextInputView
10
+ import kotlin.Float
11
+ import kotlin.Int
12
+ import kotlin.String
13
+ import kotlin.math.ceil
14
+
15
+ class HtmlStyle {
16
+ private var style: ReadableMap? = null
17
+ private var view: EnrichedTextInputView? = null
18
+
19
+ // Default values are ignored as they are specified on the JS side.
20
+ // They are specified only because they are required by the constructor.
21
+ // JS passes them as a prop - so they are initialized after the constructor is called.
22
+ var h1FontSize: Int = 72
23
+ var h1Bold: Boolean = false
24
+
25
+ var h2FontSize: Int = 64
26
+ var h2Bold: Boolean = false
27
+
28
+ var h3FontSize: Int = 56
29
+ var h3Bold: Boolean = false
30
+
31
+ var blockquoteColor: Int? = null
32
+ var blockquoteBorderColor: Int = Color.BLACK
33
+ var blockquoteStripeWidth: Int = 2
34
+ var blockquoteGapWidth: Int = 16
35
+
36
+ var olGapWidth: Int = 16
37
+ var olMarginLeft: Int = 24
38
+ var olMarkerFontWeight: Int? = null
39
+ var olMarkerColor: Int? = null
40
+
41
+ var ulGapWidth: Int = 16
42
+ var ulMarginLeft: Int = 24
43
+ var ulBulletSize: Int = 8
44
+ var ulBulletColor: Int = Color.BLACK
45
+
46
+ var imgWidth: Int = 200
47
+ var imgHeight: Int = 200
48
+
49
+ var aColor: Int = Color.BLACK
50
+ var aUnderline: Boolean = true
51
+
52
+ var codeBlockColor: Int = Color.BLACK
53
+ var codeBlockBackgroundColor: Int = Color.BLACK
54
+ var codeBlockRadius: Float = 4f
55
+
56
+ var inlineCodeColor: Int = Color.BLACK
57
+ var inlineCodeBackgroundColor: Int = Color.BLACK
58
+
59
+ var mentionsStyle: MutableMap<String, MentionStyle> = mutableMapOf()
60
+
61
+ constructor(view: EnrichedTextInputView?, style: ReadableMap?) {
62
+ this.view = view
63
+ this.style = style
64
+
65
+ invalidateStyles()
66
+ }
67
+
68
+ fun invalidateStyles() {
69
+ val style = this.style ?: return
70
+
71
+ val h1Style = style.getMap("h1")
72
+ h1FontSize = parseFloat(h1Style, "fontSize").toInt()
73
+ h1Bold = h1Style?.getBoolean("bold") == true
74
+
75
+ val h2Style = style.getMap("h2")
76
+ h2FontSize = parseFloat(h2Style, "fontSize").toInt()
77
+ h2Bold = h2Style?.getBoolean("bold") == true
78
+
79
+ val h3Style = style.getMap("h3")
80
+ h3FontSize = parseFloat(h3Style, "fontSize").toInt()
81
+ h3Bold = h3Style?.getBoolean("bold") == true
82
+
83
+ val blockquoteStyle = style.getMap("blockquote")
84
+ blockquoteColor = parseOptionalColor(blockquoteStyle, "color")
85
+ blockquoteBorderColor = parseColor(blockquoteStyle, "borderColor")
86
+ blockquoteGapWidth = parseFloat(blockquoteStyle, "gapWidth").toInt()
87
+ blockquoteStripeWidth = parseFloat(blockquoteStyle, "borderWidth").toInt()
88
+
89
+ val olStyle = style.getMap("ol")
90
+ val userDefinedMarginLeft = parseFloat(olStyle, "marginLeft").toInt()
91
+ val calculatedMarginLeft = calculateOlMarginLeft(view, userDefinedMarginLeft)
92
+ olMarginLeft = calculatedMarginLeft
93
+ olGapWidth = parseFloat(olStyle, "gapWidth").toInt()
94
+ olMarkerColor = parseOptionalColor(olStyle, "markerColor")
95
+ olMarkerFontWeight = parseOptionalFontWeight(olStyle, "markerFontWeight")
96
+
97
+ val ulStyle = style.getMap("ul")
98
+ ulBulletColor = parseColor(ulStyle, "bulletColor")
99
+ ulGapWidth = parseFloat(ulStyle, "gapWidth").toInt()
100
+ ulMarginLeft = parseFloat(ulStyle, "marginLeft").toInt()
101
+ ulBulletSize = parseFloat(ulStyle, "bulletSize").toInt()
102
+
103
+ val imgStyle = style.getMap("img")
104
+ imgWidth = parseFloat(imgStyle, "width").toInt()
105
+ imgHeight = parseFloat(imgStyle, "height").toInt()
106
+
107
+ val aStyle = style.getMap("a")
108
+ aColor = parseColor(aStyle, "color")
109
+ aUnderline = parseIsUnderline(aStyle)
110
+
111
+ val codeBlockStyle = style.getMap("codeblock")
112
+ codeBlockRadius = parseFloat(codeBlockStyle, "borderRadius")
113
+ codeBlockColor = parseColor(codeBlockStyle, "color")
114
+ codeBlockBackgroundColor = parseColorWithOpacity(codeBlockStyle, "backgroundColor", 80)
115
+
116
+ val inlineCodeStyle = style.getMap("code")
117
+ inlineCodeColor = parseColor(inlineCodeStyle, "color")
118
+ inlineCodeBackgroundColor = parseColorWithOpacity(inlineCodeStyle, "backgroundColor", 80)
119
+
120
+ val mentionStyle = style.getMap("mention")
121
+ mentionsStyle = parseMentionsStyle(mentionStyle)
122
+ }
123
+
124
+ private fun parseFloat(map: ReadableMap?, key: String): Float {
125
+ val safeMap = ensureValueIsSet(map, key)
126
+
127
+ val fontSize = safeMap.getDouble(key)
128
+ return ceil(PixelUtil.toPixelFromSP(fontSize))
129
+ }
130
+
131
+ private fun parseColorWithOpacity(map: ReadableMap?, key: String, opacity: Int): Int {
132
+ val color = parseColor(map, key)
133
+ return withOpacity(color, opacity)
134
+ }
135
+
136
+ private fun parseOptionalColor(map: ReadableMap?, key: String): Int? {
137
+ if (map == null) return null
138
+ if (!map.hasKey(key)) return null
139
+ if (map.isNull(key)) return null
140
+
141
+ return parseColor(map, key)
142
+ }
143
+
144
+ private fun parseColor(map: ReadableMap?, key: String): Int {
145
+ val safeMap = ensureValueIsSet(map, key)
146
+
147
+ val color = safeMap.getDouble(key)
148
+ val parsedColor = ColorPropConverter.getColor(color, view?.context as ReactContext)
149
+ if (parsedColor == null) {
150
+ throw Error("Specified color value is not supported: $color")
151
+ }
152
+
153
+ return parsedColor
154
+ }
155
+
156
+ private fun withOpacity(color: Int, alpha: Int): Int {
157
+ val a = alpha.coerceIn(0, 255)
158
+ return (color and 0x00FFFFFF) or (a shl 24)
159
+ }
160
+
161
+ private fun parseIsUnderline(map: ReadableMap?): Boolean {
162
+ val underline = map?.getString("textDecorationLine")
163
+ val isEnabled = underline == "underline"
164
+ val isDisabled = underline == "none"
165
+
166
+ if (isEnabled) return true
167
+ if (isDisabled) return false
168
+
169
+ throw Error("Specified textDecorationLine value is not supported: $underline. Supported values are 'underline' and 'none'.")
170
+ }
171
+
172
+ private fun calculateOlMarginLeft(view: EnrichedTextInputView?, userMargin: Int): Int {
173
+ val fontSize = view?.fontSize?.toInt() ?: 0
174
+ val leadMargin = fontSize / 2
175
+
176
+ return leadMargin + userMargin
177
+ }
178
+
179
+ private fun ensureValueIsSet(map: ReadableMap?, key: String): ReadableMap {
180
+ if (map == null) throw Error("Style map cannot be null")
181
+
182
+ if (!map.hasKey(key)) throw Error("Style map must contain key: $key")
183
+
184
+ if (map.isNull(key)) throw Error("Style map cannot contain null value for key: $key")
185
+
186
+ return map
187
+ }
188
+
189
+ private fun parseMentionsStyle(mentionsStyle: ReadableMap?): MutableMap<String, MentionStyle> {
190
+ if (mentionsStyle == null) throw Error("Mentions style cannot be null")
191
+
192
+ val parsedMentionsStyle: MutableMap<String, MentionStyle> = mutableMapOf()
193
+
194
+ val iterator = mentionsStyle.keySetIterator()
195
+ while (iterator.hasNextKey()) {
196
+ val key = iterator.nextKey()
197
+ val value = mentionsStyle.getMap(key)
198
+
199
+ if (value == null) throw Error("Mention style for key '$key' cannot be null")
200
+
201
+ val color = parseColor(value, "color")
202
+ val backgroundColor = parseColorWithOpacity(value, "backgroundColor", 80)
203
+ val isUnderline = parseIsUnderline(value)
204
+ val parsedStyle = MentionStyle(color, backgroundColor, isUnderline)
205
+ parsedMentionsStyle.put(key, parsedStyle)
206
+ }
207
+
208
+ return parsedMentionsStyle
209
+ }
210
+
211
+ private fun parseOptionalFontWeight(map: ReadableMap?, key: String): Int? {
212
+ if (map == null) return null
213
+ if (!map.hasKey(key)) return null
214
+ if (map.isNull(key)) return null
215
+
216
+ val fontWeight = map.getString(key) ?: return null
217
+ return parseFontWeight(fontWeight)
218
+ }
219
+
220
+ companion object {
221
+ data class MentionStyle(
222
+ val color: Int,
223
+ val backgroundColor: Int,
224
+ val underline: Boolean
225
+ )
226
+ }
227
+ }
@@ -0,0 +1,146 @@
1
+ package com.swmansion.enriched.styles
2
+
3
+ import android.text.Editable
4
+ import android.text.Spannable
5
+ import com.swmansion.enriched.EnrichedTextInputView
6
+ import com.swmansion.enriched.spans.EnrichedSpans
7
+ import com.swmansion.enriched.utils.getSafeSpanBoundaries
8
+
9
+ class InlineStyles(private val view: EnrichedTextInputView) {
10
+ private fun <T>setSpan(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
11
+ val previousSpanStart = (start - 1).coerceAtLeast(0)
12
+ val previousSpanEnd = previousSpanStart + 1
13
+ val nextSpanStart = (end + 1).coerceAtMost(spannable.length)
14
+ val nextSpanEnd = (nextSpanStart + 1).coerceAtMost(spannable.length)
15
+ val previousSpans = spannable.getSpans(previousSpanStart, previousSpanEnd, type)
16
+ val nextSpans = spannable.getSpans(nextSpanStart, nextSpanEnd, type)
17
+ var minimum = start
18
+ var maximum = end
19
+
20
+ for (span in previousSpans) {
21
+ val spanStart = spannable.getSpanStart(span)
22
+ minimum = spanStart.coerceAtMost(minimum)
23
+ }
24
+
25
+ for (span in nextSpans) {
26
+ val spanEnd = spannable.getSpanEnd(span)
27
+ maximum = spanEnd.coerceAtLeast(maximum)
28
+ }
29
+
30
+ val spans = spannable.getSpans(minimum, maximum, type)
31
+ for (span in spans) {
32
+ spannable.removeSpan(span)
33
+ }
34
+
35
+ val span = type.getDeclaredConstructor(HtmlStyle::class.java).newInstance(view.htmlStyle)
36
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(minimum, maximum)
37
+ spannable.setSpan(span, safeStart, safeEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
38
+ }
39
+
40
+ private fun <T>setAndMergeSpans(spannable: Spannable, type: Class<T>, start: Int, end: Int) {
41
+ val spans = spannable.getSpans(start, end, type)
42
+
43
+ // No spans setup for current selection, means we just need to assign new span
44
+ if (spans.isEmpty()) {
45
+ setSpan(spannable, type, start, end)
46
+ return
47
+ }
48
+
49
+ var setSpanOnFinish = false
50
+
51
+ // Some spans are present, we have to remove spans and (optionally) apply new spans
52
+ for (span in spans) {
53
+ val spanStart = spannable.getSpanStart(span)
54
+ val spanEnd = spannable.getSpanEnd(span)
55
+ var finalStart: Int? = null
56
+ var finalEnd: Int? = null
57
+
58
+ spannable.removeSpan(span)
59
+
60
+ if (start == spanStart && end == spanEnd) {
61
+ setSpanOnFinish = false
62
+ } else if (start > spanStart && end < spanEnd) {
63
+ setSpan(spannable, type, spanStart, start)
64
+ setSpan(spannable, type, end, spanEnd)
65
+ } else if (start == spanStart && end < spanEnd) {
66
+ finalStart = end
67
+ finalEnd = spanEnd
68
+ } else if (start > spanStart && end == spanEnd) {
69
+ finalStart = spanStart
70
+ finalEnd = start
71
+ } else if (start > spanStart) {
72
+ finalStart = spanStart
73
+ finalEnd = end
74
+ } else if (start < spanStart && end < spanEnd) {
75
+ finalStart = start
76
+ finalEnd = spanEnd
77
+ } else {
78
+ setSpanOnFinish = true
79
+ }
80
+
81
+ if (!setSpanOnFinish && finalStart != null && finalEnd != null) {
82
+ setSpan(spannable, type, finalStart, finalEnd)
83
+ }
84
+ }
85
+
86
+ if (setSpanOnFinish) {
87
+ setSpan(spannable, type, start, end)
88
+ }
89
+ }
90
+
91
+ fun afterTextChanged(s: Editable, endCursorPosition: Int) {
92
+ for ((style, config) in EnrichedSpans.inlineSpans) {
93
+ val start = view.spanState?.getStart(style) ?: continue
94
+ var end = endCursorPosition
95
+ val spans = s.getSpans(start, end, config.clazz)
96
+
97
+ for (span in spans) {
98
+ end = s.getSpanEnd(span).coerceAtLeast(end)
99
+ s.removeSpan(span)
100
+ }
101
+
102
+ setSpan(s, config.clazz, start, end)
103
+ }
104
+ }
105
+
106
+ fun toggleStyle(name: String) {
107
+ if (view.selection == null) return
108
+ val (start, end) = view.selection.getInlineSelection()
109
+ val config = EnrichedSpans.inlineSpans[name] ?: return
110
+ val type = config.clazz
111
+
112
+ // We either start or end current span
113
+ if (start == end) {
114
+ val styleStart = view.spanState?.getStart(name)
115
+
116
+ if (styleStart != null) {
117
+ view.spanState.setStart(name, null)
118
+ } else {
119
+ view.spanState?.setStart(name, start)
120
+ }
121
+
122
+ return
123
+ }
124
+
125
+ val spannable = view.text as Spannable
126
+ setAndMergeSpans(spannable, type, start, end)
127
+ view.selection.validateStyles()
128
+ }
129
+
130
+ fun removeStyle(name: String, start: Int, end: Int): Boolean {
131
+ val config = EnrichedSpans.inlineSpans[name] ?: return false
132
+ val spannable = view.text as Spannable
133
+ val spans = spannable.getSpans(start, end, config.clazz)
134
+ if (spans.isEmpty()) return false
135
+
136
+ for (span in spans) {
137
+ spannable.removeSpan(span)
138
+ }
139
+
140
+ return true
141
+ }
142
+
143
+ fun getStyleRange(): Pair<Int, Int> {
144
+ return view.selection?.getInlineSelection() ?: Pair(0, 0)
145
+ }
146
+ }
@@ -0,0 +1,173 @@
1
+ package com.swmansion.enriched.styles
2
+
3
+ import android.text.Editable
4
+ import android.text.Spannable
5
+ import android.text.SpannableStringBuilder
6
+ import android.text.Spanned
7
+ import com.swmansion.enriched.EnrichedTextInputView
8
+ import com.swmansion.enriched.spans.EnrichedOrderedListSpan
9
+ import com.swmansion.enriched.spans.EnrichedSpans
10
+ import com.swmansion.enriched.spans.EnrichedUnorderedListSpan
11
+ import com.swmansion.enriched.utils.getParagraphBounds
12
+ 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? {
16
+ if (s <= 0) return null
17
+
18
+ val (previousParagraphStart, previousParagraphEnd) = spannable.getParagraphBounds(s - 1)
19
+ val spans = spannable.getSpans(previousParagraphStart, previousParagraphEnd, type)
20
+
21
+ if (spans.isNotEmpty()) {
22
+ return spans.last()
23
+ }
24
+
25
+ return null
26
+ }
27
+
28
+ private fun <T>isPreviousParagraphList(spannable: Spannable, s: Int, type: Class<T>): Boolean {
29
+ val previousSpan = getPreviousParagraphSpan(spannable, s, type)
30
+
31
+ return previousSpan != null
32
+ }
33
+
34
+ private fun getOrderedListIndex(spannable: Spannable, s: Int): Int {
35
+ val span = getPreviousParagraphSpan(spannable, s, EnrichedOrderedListSpan::class.java)
36
+ val index = span?.getIndex() ?: 0
37
+ return index + 1
38
+ }
39
+
40
+ private fun setSpan(spannable: Spannable, name: String, start: Int, end: Int) {
41
+ val (safeStart, safeEnd) = spannable.getSafeSpanBoundaries(start, end)
42
+
43
+ if (name == EnrichedSpans.UNORDERED_LIST) {
44
+ val span = EnrichedUnorderedListSpan(view.htmlStyle)
45
+ spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
46
+ return
47
+ }
48
+
49
+ if (name == EnrichedSpans.ORDERED_LIST) {
50
+ val index = getOrderedListIndex(spannable, safeStart)
51
+ val span = EnrichedOrderedListSpan(index, view.htmlStyle)
52
+ spannable.setSpan(span, safeStart, safeEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
53
+ }
54
+ }
55
+
56
+ private fun <T>removeSpansForRange(spannable: Spannable, start: Int, end: Int, clazz: Class<T>): Boolean {
57
+ val ssb = spannable as SpannableStringBuilder
58
+ val spans = ssb.getSpans(start, end, clazz)
59
+ if (spans.isEmpty()) return false
60
+
61
+ ssb.replace(start, end, ssb.substring(start, end).replace("\u200B", ""))
62
+
63
+ for (span in spans) {
64
+ ssb.removeSpan(span)
65
+ }
66
+
67
+ return true
68
+ }
69
+
70
+ fun updateOrderedListIndexes(text: Spannable, position: Int) {
71
+ val spans = text.getSpans(position + 1, text.length, EnrichedOrderedListSpan::class.java)
72
+ for (span in spans) {
73
+ val spanStart = text.getSpanStart(span)
74
+ val index = getOrderedListIndex(text, spanStart)
75
+ span.setIndex(index)
76
+ }
77
+ }
78
+
79
+ fun toggleStyle(name: String) {
80
+ if (view.selection == null) return
81
+ val config = EnrichedSpans.listSpans[name] ?: return
82
+ val spannable = view.text as SpannableStringBuilder
83
+ val (start, end) = view.selection.getParagraphSelection()
84
+ val styleStart = view.spanState?.getStart(name)
85
+
86
+ if (styleStart != null) {
87
+ view.spanState.setStart(name, null)
88
+ removeSpansForRange(spannable, start, end, config.clazz)
89
+ view.selection.validateStyles()
90
+
91
+ return
92
+ }
93
+
94
+ if (start == end) {
95
+ spannable.insert(start, "\u200B")
96
+ view.spanState?.setStart(name, start + 1)
97
+ removeSpansForRange(spannable, start, end, config.clazz)
98
+ setSpan(spannable, name, start, end + 1)
99
+
100
+ return
101
+ }
102
+
103
+ var currentStart = start
104
+ val paragraphs = spannable.substring(start, end).split("\n")
105
+ removeSpansForRange(spannable, start, end, config.clazz)
106
+
107
+ for (paragraph in paragraphs) {
108
+ spannable.insert(currentStart, "\u200B")
109
+ val currentEnd = currentStart + paragraph.length + 1
110
+ setSpan(spannable, name, currentStart, currentEnd)
111
+
112
+ currentStart = currentEnd + 1
113
+ }
114
+
115
+ view.spanState?.setStart(name, currentStart)
116
+ }
117
+
118
+ private fun handleAfterTextChanged(s: Editable, name: String, endCursorPosition: Int, previousTextLength: Int) {
119
+ val config = EnrichedSpans.listSpans[name] ?: return
120
+ val cursorPosition = endCursorPosition.coerceAtMost(s.length)
121
+ val (start, end) = s.getParagraphBounds(cursorPosition)
122
+
123
+ val isBackspace = previousTextLength > s.length
124
+ val isNewLine = cursorPosition > 0 && s[cursorPosition - 1] == '\n'
125
+ val isShortcut = s.substring(start, end).startsWith(config.shortcut)
126
+ val spans = s.getSpans(start, end, config.clazz)
127
+
128
+ // Remove spans if cursor is at the start of the paragraph and spans exist
129
+ if (isBackspace && start == cursorPosition && spans.isNotEmpty()) {
130
+ removeSpansForRange(s, start, end, config.clazz)
131
+ return
132
+ }
133
+
134
+ if (!isBackspace && isShortcut) {
135
+ s.replace(start, cursorPosition, "\u200B")
136
+ setSpan(s, name, start, start + 1)
137
+ // Inform that new span has been added
138
+ view.selection?.validateStyles()
139
+ return
140
+ }
141
+
142
+ if (!isBackspace && isNewLine && isPreviousParagraphList(s, start, config.clazz)) {
143
+ s.insert(cursorPosition, "\u200B")
144
+ setSpan(s, name, start, end + 1)
145
+ // Inform that new span has been added
146
+ view.selection?.validateStyles()
147
+ return
148
+ }
149
+
150
+ if (spans.isNotEmpty()) {
151
+ for (span in spans) {
152
+ s.removeSpan(span)
153
+ }
154
+
155
+ setSpan(s, name, start, end)
156
+ }
157
+ }
158
+
159
+ fun afterTextChanged(s: Editable, endCursorPosition: Int, previousTextLength: Int) {
160
+ handleAfterTextChanged(s, EnrichedSpans.ORDERED_LIST, endCursorPosition, previousTextLength)
161
+ handleAfterTextChanged(s, EnrichedSpans.UNORDERED_LIST, endCursorPosition, previousTextLength)
162
+ }
163
+
164
+ fun getStyleRange(): Pair<Int, Int> {
165
+ return view.selection?.getParagraphSelection() ?: Pair(0, 0)
166
+ }
167
+
168
+ fun removeStyle(name: String, start: Int, end: Int): Boolean {
169
+ val config = EnrichedSpans.listSpans[name] ?: return false
170
+ val spannable = view.text as Spannable
171
+ return removeSpansForRange(spannable, start, end, config.clazz)
172
+ }
173
+ }