react-native-enriched 0.2.0 → 0.2.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 (107) hide show
  1. package/README.md +1 -5
  2. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +3 -0
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +1 -0
  4. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +10 -0
  5. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +7 -0
  6. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +92 -0
  7. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +6 -0
  8. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +1 -1
  9. package/android/src/main/java/com/swmansion/enriched/events/OnRequestHtmlResultEvent.kt +33 -0
  10. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +6 -0
  11. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +6 -0
  12. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +6 -0
  13. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +6 -0
  14. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +6 -0
  15. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +6 -0
  16. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +5 -0
  17. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +6 -0
  18. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +5 -0
  19. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +6 -0
  20. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +6 -0
  21. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +6 -0
  22. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +9 -3
  23. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +5 -0
  24. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +5 -0
  25. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +6 -0
  26. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +4 -0
  27. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +78 -0
  28. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +80 -4
  29. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +8 -0
  30. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +6 -6
  31. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +6 -6
  32. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +19 -19
  33. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +40 -51
  34. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +13 -15
  35. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +23 -21
  36. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +35 -36
  37. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +4 -4
  38. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +13 -14
  39. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +12 -13
  40. package/ios/EnrichedTextInputView.h +25 -13
  41. package/ios/EnrichedTextInputView.mm +872 -581
  42. package/ios/attachments/ImageAttachment.h +10 -0
  43. package/ios/attachments/ImageAttachment.mm +34 -0
  44. package/ios/attachments/MediaAttachment.h +23 -0
  45. package/ios/attachments/MediaAttachment.mm +31 -0
  46. package/ios/config/InputConfig.h +6 -6
  47. package/ios/config/InputConfig.mm +39 -33
  48. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +10 -0
  49. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +7 -0
  50. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +21 -0
  51. package/ios/inputParser/InputParser.h +5 -5
  52. package/ios/inputParser/InputParser.mm +789 -378
  53. package/ios/inputTextView/InputTextView.h +1 -1
  54. package/ios/inputTextView/InputTextView.mm +100 -59
  55. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +11 -9
  56. package/ios/internals/EnrichedTextInputViewShadowNode.h +28 -25
  57. package/ios/internals/EnrichedTextInputViewShadowNode.mm +45 -40
  58. package/ios/internals/EnrichedTextInputViewState.h +3 -1
  59. package/ios/styles/BlockQuoteStyle.mm +189 -118
  60. package/ios/styles/BoldStyle.mm +95 -63
  61. package/ios/styles/CodeBlockStyle.mm +204 -128
  62. package/ios/styles/H1Style.mm +10 -4
  63. package/ios/styles/H2Style.mm +10 -4
  64. package/ios/styles/H3Style.mm +10 -4
  65. package/ios/styles/HeadingStyleBase.mm +129 -84
  66. package/ios/styles/ImageStyle.mm +75 -73
  67. package/ios/styles/InlineCodeStyle.mm +148 -85
  68. package/ios/styles/ItalicStyle.mm +76 -52
  69. package/ios/styles/LinkStyle.mm +348 -227
  70. package/ios/styles/MentionStyle.mm +363 -246
  71. package/ios/styles/OrderedListStyle.mm +171 -106
  72. package/ios/styles/StrikethroughStyle.mm +52 -35
  73. package/ios/styles/UnderlineStyle.mm +68 -46
  74. package/ios/styles/UnorderedListStyle.mm +169 -106
  75. package/ios/utils/BaseStyleProtocol.h +2 -2
  76. package/ios/utils/ColorExtension.mm +7 -5
  77. package/ios/utils/FontExtension.mm +42 -27
  78. package/ios/utils/LayoutManagerExtension.h +1 -1
  79. package/ios/utils/LayoutManagerExtension.mm +280 -170
  80. package/ios/utils/MentionParams.h +0 -1
  81. package/ios/utils/MentionStyleProps.h +1 -1
  82. package/ios/utils/MentionStyleProps.mm +27 -20
  83. package/ios/utils/OccurenceUtils.h +42 -42
  84. package/ios/utils/OccurenceUtils.mm +142 -119
  85. package/ios/utils/ParagraphAttributesUtils.h +6 -2
  86. package/ios/utils/ParagraphAttributesUtils.mm +115 -71
  87. package/ios/utils/ParagraphsUtils.h +2 -1
  88. package/ios/utils/ParagraphsUtils.mm +40 -26
  89. package/ios/utils/StringExtension.h +1 -1
  90. package/ios/utils/StringExtension.mm +19 -16
  91. package/ios/utils/StyleHeaders.h +27 -15
  92. package/ios/utils/TextInsertionUtils.h +13 -2
  93. package/ios/utils/TextInsertionUtils.mm +38 -20
  94. package/ios/utils/WordsUtils.h +2 -1
  95. package/ios/utils/WordsUtils.mm +32 -22
  96. package/ios/utils/ZeroWidthSpaceUtils.h +3 -1
  97. package/ios/utils/ZeroWidthSpaceUtils.mm +145 -79
  98. package/lib/module/EnrichedTextInput.js +39 -1
  99. package/lib/module/EnrichedTextInput.js.map +1 -1
  100. package/lib/module/EnrichedTextInputNativeComponent.ts +11 -0
  101. package/lib/typescript/src/EnrichedTextInput.d.ts +1 -0
  102. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  103. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +6 -0
  104. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  105. package/package.json +8 -1
  106. package/src/EnrichedTextInput.tsx +45 -0
  107. package/src/EnrichedTextInputNativeComponent.ts +11 -0
package/README.md CHANGED
@@ -40,7 +40,7 @@ We can help you build your next dream product –
40
40
  ## Prerequisites
41
41
 
42
42
  - `react-native-enriched` currently supports only Android and iOS platforms
43
- - It works only with [the React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page) and supports following React Native releases: `0.79`, `0.80`, `0.81` and `0.82`
43
+ - It works only with [the React Native New Architecture (Fabric)](https://reactnative.dev/architecture/landing-page) and supports following React Native releases: `0.79`, `0.80`, `0.81`, `0.82` and `0.83`
44
44
 
45
45
  ## Installation
46
46
 
@@ -238,14 +238,10 @@ See the [API Reference](docs/API_REFERENCE.md) for a detailed overview of all th
238
238
  ## Known limitations
239
239
 
240
240
  - Only one level of lists is supported. We currently do not support nested lists.
241
- - Inline images are supported only on Android.
242
- - Codeblocks are supported only on Android.
243
241
  - iOS headings can't have the same `fontSize` in their config as input's `fontSize`. Doing so results in incorrect headings behavior.
244
242
 
245
243
  ## Future Plans
246
244
 
247
- - Adding Codeblocks and Inline Images to iOS input.
248
- - Making some optimizations around `onChangeHtml` event, maybe some imperative API to get the HTML output.
249
245
  - Creating `EnrichedText` text component that supports our HTML output format with all additional interactions like pressing links or mentions.
250
246
  - Adding API for custom link detection regex.
251
247
  - Web library implementation via `react-native-web`.
@@ -144,6 +144,9 @@ public class EnrichedTextInputViewManagerDelegate<T extends View, U extends Base
144
144
  case "addMention":
145
145
  mViewManager.addMention(view, args.getString(0), args.getString(1), args.getString(2));
146
146
  break;
147
+ case "requestHTML":
148
+ mViewManager.requestHTML(view, args.getInt(0));
149
+ break;
147
150
  }
148
151
  }
149
152
  }
@@ -53,4 +53,5 @@ public interface EnrichedTextInputViewManagerInterface<T extends View> extends V
53
53
  void addImage(T view, String uri, float width, float height);
54
54
  void startMention(T view, String indicator);
55
55
  void addMention(T view, String indicator, String text, String payload);
56
+ void requestHTML(T view, int requestId);
56
57
  }
@@ -115,4 +115,14 @@ payload.setProperty(runtime, "text", event.text);
115
115
  });
116
116
  }
117
117
 
118
+
119
+ void EnrichedTextInputViewEventEmitter::onRequestHtmlResult(OnRequestHtmlResult event) const {
120
+ dispatchEvent("requestHtmlResult", [event=std::move(event)](jsi::Runtime &runtime) {
121
+ auto payload = jsi::Object(runtime);
122
+ payload.setProperty(runtime, "requestId", event.requestId);
123
+ payload.setProperty(runtime, "html", jsi::valueFromDynamic(runtime, event.html));
124
+ return payload;
125
+ });
126
+ }
127
+
118
128
  } // namespace facebook::react
@@ -74,6 +74,11 @@ class EnrichedTextInputViewEventEmitter : public ViewEventEmitter {
74
74
  int end;
75
75
  std::string text;
76
76
  };
77
+
78
+ struct OnRequestHtmlResult {
79
+ int requestId;
80
+ folly::dynamic html;
81
+ };
77
82
  void onInputFocus(OnInputFocus value) const;
78
83
 
79
84
  void onInputBlur(OnInputBlur value) const;
@@ -91,5 +96,7 @@ class EnrichedTextInputViewEventEmitter : public ViewEventEmitter {
91
96
  void onMention(OnMention value) const;
92
97
 
93
98
  void onChangeSelection(OnChangeSelection value) const;
99
+
100
+ void onRequestHtmlResult(OnRequestHtmlResult value) const;
94
101
  };
95
102
  } // namespace facebook::react
@@ -29,8 +29,13 @@ import com.facebook.react.views.text.ReactTypefaceUtils.parseFontWeight
29
29
  import com.swmansion.enriched.events.MentionHandler
30
30
  import com.swmansion.enriched.events.OnInputBlurEvent
31
31
  import com.swmansion.enriched.events.OnInputFocusEvent
32
+ import com.swmansion.enriched.events.OnRequestHtmlResultEvent
33
+ import com.swmansion.enriched.spans.EnrichedH1Span
34
+ import com.swmansion.enriched.spans.EnrichedH2Span
35
+ import com.swmansion.enriched.spans.EnrichedH3Span
32
36
  import com.swmansion.enriched.spans.EnrichedImageSpan
33
37
  import com.swmansion.enriched.spans.EnrichedSpans
38
+ import com.swmansion.enriched.spans.interfaces.EnrichedSpan
34
39
  import com.swmansion.enriched.styles.InlineStyles
35
40
  import com.swmansion.enriched.styles.ListStyles
36
41
  import com.swmansion.enriched.styles.ParagraphStyles
@@ -59,6 +64,13 @@ class EnrichedTextInputView : AppCompatEditText {
59
64
 
60
65
  val mentionHandler: MentionHandler? = MentionHandler(this)
61
66
  var htmlStyle: HtmlStyle = HtmlStyle(this, null)
67
+ set(value) {
68
+ if (field != value) {
69
+ val prev = field
70
+ field = value
71
+ reApplyHtmlStyleForSpans(prev, value)
72
+ }
73
+ }
62
74
  var spanWatcher: EnrichedSpanWatcher? = null
63
75
  var layoutManager: EnrichedTextInputViewLayoutManager = EnrichedTextInputViewLayoutManager(this)
64
76
 
@@ -335,6 +347,7 @@ class EnrichedTextInputView : AppCompatEditText {
335
347
  // This ensured that newly created spans will take the new font size into account
336
348
  htmlStyle.invalidateStyles()
337
349
  layoutManager.invalidateLayout()
350
+ forceScrollToSelection()
338
351
  }
339
352
 
340
353
  fun setFontFamily(family: String?) {
@@ -566,6 +579,19 @@ class EnrichedTextInputView : AppCompatEditText {
566
579
  parametrizedStyles?.setMentionSpan(text, indicator, attributes)
567
580
  }
568
581
 
582
+ fun requestHTML(requestId: Int) {
583
+ val html = try {
584
+ EnrichedParser.toHtmlWithDefault(text)
585
+ } catch (e: Exception) {
586
+ null
587
+ }
588
+
589
+ val reactContext = context as ReactContext
590
+ val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
591
+ val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
592
+ dispatcher?.dispatchEvent(OnRequestHtmlResultEvent(surfaceId, id, requestId, html, experimentalSynchronousEvents))
593
+ }
594
+
569
595
  // Sometimes setting up style triggers many changes in sequence
570
596
  // Eg. removing conflicting styles -> changing text -> applying spans
571
597
  // In such scenario we want to prevent from handling side effects (eg. onTextChanged)
@@ -578,6 +604,72 @@ class EnrichedTextInputView : AppCompatEditText {
578
604
  }
579
605
  }
580
606
 
607
+ private fun forceScrollToSelection() {
608
+ val textLayout = layout ?: return
609
+ val cursorOffset = selectionStart
610
+ if (cursorOffset <= 0) return
611
+
612
+ val selectedLineIndex = textLayout.getLineForOffset(cursorOffset)
613
+ val selectedLineTop = textLayout.getLineTop(selectedLineIndex)
614
+ val selectedLineBottom = textLayout.getLineBottom(selectedLineIndex)
615
+ val visibleTextHeight = height - paddingTop - paddingBottom
616
+
617
+ if (visibleTextHeight <= 0) return
618
+
619
+ val visibleTop = scrollY
620
+ val visibleBottom = scrollY + visibleTextHeight
621
+ var targetScrollY = scrollY
622
+
623
+ if (selectedLineTop < visibleTop) {
624
+ targetScrollY = selectedLineTop
625
+ } else if (selectedLineBottom > visibleBottom) {
626
+ targetScrollY = selectedLineBottom - visibleTextHeight
627
+ }
628
+
629
+ val maxScrollY = (textLayout.height - visibleTextHeight).coerceAtLeast(0)
630
+ targetScrollY = targetScrollY.coerceIn(0, maxScrollY)
631
+ scrollTo(scrollX, targetScrollY)
632
+ }
633
+
634
+ private fun reApplyHtmlStyleForSpans(previousHtmlStyle: HtmlStyle, nextHtmlStyle: HtmlStyle) {
635
+ val shouldRemoveBoldSpanFromH1Span = !previousHtmlStyle.h1Bold && nextHtmlStyle.h1Bold
636
+ val shouldRemoveBoldSpanFromH2Span = !previousHtmlStyle.h2Bold && nextHtmlStyle.h2Bold
637
+ val shouldRemoveBoldSpanFromH3Span = !previousHtmlStyle.h3Bold && nextHtmlStyle.h3Bold
638
+
639
+ val spannable = text as? Spannable ?: return
640
+ if (spannable.isEmpty()) return
641
+
642
+ var shouldEmitStateChange = false
643
+
644
+ runAsATransaction {
645
+ val spans = spannable.getSpans(0, spannable.length, EnrichedSpan::class.java)
646
+ for (span in spans) {
647
+ if (!span.dependsOnHtmlStyle) continue
648
+
649
+ val start = spannable.getSpanStart(span)
650
+ val end = spannable.getSpanEnd(span)
651
+ val flags = spannable.getSpanFlags(span)
652
+
653
+ if (start == -1 || end == -1) continue
654
+
655
+ if ((span is EnrichedH1Span && shouldRemoveBoldSpanFromH1Span) || (span is EnrichedH2Span && shouldRemoveBoldSpanFromH2Span) || (span is EnrichedH3Span && shouldRemoveBoldSpanFromH3Span)) {
656
+ val isRemoved = removeStyle(EnrichedSpans.BOLD, start, end)
657
+ if (isRemoved) shouldEmitStateChange = true
658
+ }
659
+
660
+ spannable.removeSpan(span)
661
+ val newSpan = span.rebuildWithStyle(htmlStyle)
662
+ spannable.setSpan(newSpan, start, end, flags)
663
+ }
664
+
665
+ if (shouldEmitStateChange) {
666
+ selection?.validateStyles()
667
+ }
668
+ }
669
+ layoutManager.invalidateLayout()
670
+ forceScrollToSelection()
671
+ }
672
+
581
673
  override fun onAttachedToWindow() {
582
674
  super.onAttachedToWindow()
583
675
 
@@ -24,6 +24,7 @@ import com.swmansion.enriched.events.OnInputFocusEvent
24
24
  import com.swmansion.enriched.events.OnLinkDetectedEvent
25
25
  import com.swmansion.enriched.events.OnMentionDetectedEvent
26
26
  import com.swmansion.enriched.events.OnMentionEvent
27
+ import com.swmansion.enriched.events.OnRequestHtmlResultEvent
27
28
  import com.swmansion.enriched.spans.EnrichedSpans
28
29
  import com.swmansion.enriched.styles.HtmlStyle
29
30
  import com.swmansion.enriched.utils.jsonStringToStringMap
@@ -71,6 +72,7 @@ class EnrichedTextInputViewManager : SimpleViewManager<EnrichedTextInputView>(),
71
72
  map.put(OnMentionDetectedEvent.EVENT_NAME, mapOf("registrationName" to OnMentionDetectedEvent.EVENT_NAME))
72
73
  map.put(OnMentionEvent.EVENT_NAME, mapOf("registrationName" to OnMentionEvent.EVENT_NAME))
73
74
  map.put(OnChangeSelectionEvent.EVENT_NAME, mapOf("registrationName" to OnChangeSelectionEvent.EVENT_NAME))
75
+ map.put(OnRequestHtmlResultEvent.EVENT_NAME, mapOf("registrationName" to OnRequestHtmlResultEvent.EVENT_NAME))
74
76
 
75
77
  return map
76
78
  }
@@ -268,6 +270,10 @@ class EnrichedTextInputViewManager : SimpleViewManager<EnrichedTextInputView>(),
268
270
  view?.addMention(text, indicator, attributes)
269
271
  }
270
272
 
273
+ override fun requestHTML(view: EnrichedTextInputView?, requestId: Int) {
274
+ view?.requestHTML(requestId)
275
+ }
276
+
271
277
  override fun measure(
272
278
  context: Context,
273
279
  localData: ReadableMap?,
@@ -9,8 +9,8 @@ class MentionHandler(private val view: EnrichedTextInputView) {
9
9
  private var previousIndicator: String? = null
10
10
 
11
11
  fun reset() {
12
+ endMention()
12
13
  previousText = null
13
- previousIndicator = null
14
14
  }
15
15
 
16
16
  fun endMention() {
@@ -0,0 +1,33 @@
1
+ package com.swmansion.enriched.events
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.WritableMap
5
+ import com.facebook.react.uimanager.events.Event
6
+
7
+ class OnRequestHtmlResultEvent(
8
+ surfaceId: Int,
9
+ viewId: Int,
10
+ private val requestId: Int,
11
+ private val html: String?,
12
+ private val experimentalSynchronousEvents: Boolean
13
+ ) : Event<OnRequestHtmlResultEvent>(surfaceId, viewId) {
14
+
15
+ override fun getEventName(): String = EVENT_NAME
16
+
17
+ override fun getEventData(): WritableMap {
18
+ val eventData: WritableMap = Arguments.createMap()
19
+ eventData.putInt("requestId", requestId)
20
+ if (html != null) {
21
+ eventData.putString("html", html)
22
+ } else {
23
+ eventData.putNull("html")
24
+ }
25
+ return eventData
26
+ }
27
+
28
+ override fun experimental_isSynchronous(): Boolean = experimentalSynchronousEvents
29
+
30
+ companion object {
31
+ const val EVENT_NAME: String = "onRequestHtmlResult"
32
+ }
33
+ }
@@ -11,6 +11,8 @@ import com.swmansion.enriched.styles.HtmlStyle
11
11
 
12
12
  // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/QuoteSpan.java
13
13
  class EnrichedBlockQuoteSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedBlockSpan {
14
+ override val dependsOnHtmlStyle: Boolean = true
15
+
14
16
  override fun updateMeasureState(p0: TextPaint) {
15
17
  // Do nothing, but inform layout that this span affects text metrics
16
18
  }
@@ -35,4 +37,8 @@ class EnrichedBlockQuoteSpan(private val htmlStyle: HtmlStyle) : MetricAffecting
35
37
  textPaint?.color = color
36
38
  }
37
39
  }
40
+
41
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedBlockQuoteSpan {
42
+ return EnrichedBlockQuoteSpan(htmlStyle)
43
+ }
38
44
  }
@@ -2,9 +2,15 @@ package com.swmansion.enriched.spans
2
2
 
3
3
  import android.graphics.Typeface
4
4
  import android.text.style.StyleSpan
5
+ import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan
5
6
  import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan
6
7
  import com.swmansion.enriched.styles.HtmlStyle
7
8
 
8
9
  @Suppress("UNUSED_PARAMETER")
9
10
  class EnrichedBoldSpan(htmlStyle: HtmlStyle) : StyleSpan(Typeface.BOLD), EnrichedInlineSpan {
11
+ override val dependsOnHtmlStyle: Boolean = false
12
+
13
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedBoldSpan {
14
+ return EnrichedBoldSpan(htmlStyle)
15
+ }
10
16
  }
@@ -13,6 +13,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedBlockSpan
13
13
  import com.swmansion.enriched.styles.HtmlStyle
14
14
 
15
15
  class EnrichedCodeBlockSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LineBackgroundSpan, EnrichedBlockSpan {
16
+ override val dependsOnHtmlStyle: Boolean = true
17
+
16
18
  override fun updateDrawState(paint: TextPaint) {
17
19
  paint.typeface = Typeface.MONOSPACE
18
20
  paint.color = htmlStyle.codeBlockColor
@@ -74,4 +76,8 @@ class EnrichedCodeBlockSpan(private val htmlStyle: HtmlStyle) : MetricAffectingS
74
76
  canvas.drawPath(path, p)
75
77
  p.color = previousColor
76
78
  }
79
+
80
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedCodeBlockSpan {
81
+ return EnrichedCodeBlockSpan(htmlStyle)
82
+ }
77
83
  }
@@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7
7
  import com.swmansion.enriched.styles.HtmlStyle
8
8
 
9
9
  class EnrichedH1Span(private val style: HtmlStyle) : AbsoluteSizeSpan(style.h1FontSize), EnrichedHeadingSpan {
10
+ override val dependsOnHtmlStyle: Boolean = true
11
+
10
12
  override fun updateDrawState(tp: TextPaint) {
11
13
  super.updateDrawState(tp)
12
14
  val bold = style.h1Bold
@@ -14,4 +16,8 @@ class EnrichedH1Span(private val style: HtmlStyle) : AbsoluteSizeSpan(style.h1Fo
14
16
  tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15
17
  }
16
18
  }
19
+
20
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH1Span {
21
+ return EnrichedH1Span(htmlStyle)
22
+ }
17
23
  }
@@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7
7
  import com.swmansion.enriched.styles.HtmlStyle
8
8
 
9
9
  class EnrichedH2Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h2FontSize), EnrichedHeadingSpan {
10
+ override val dependsOnHtmlStyle: Boolean = true
11
+
10
12
  override fun updateDrawState(tp: TextPaint) {
11
13
  super.updateDrawState(tp)
12
14
  val bold = htmlStyle.h2Bold
@@ -14,4 +16,8 @@ class EnrichedH2Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlSt
14
16
  tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15
17
  }
16
18
  }
19
+
20
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH2Span {
21
+ return EnrichedH2Span(htmlStyle)
22
+ }
17
23
  }
@@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedHeadingSpan
7
7
  import com.swmansion.enriched.styles.HtmlStyle
8
8
 
9
9
  class EnrichedH3Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlStyle.h3FontSize), EnrichedHeadingSpan {
10
+ override val dependsOnHtmlStyle: Boolean = true
11
+
10
12
  override fun updateDrawState(tp: TextPaint) {
11
13
  super.updateDrawState(tp)
12
14
  val bold = htmlStyle.h3Bold
@@ -14,4 +16,8 @@ class EnrichedH3Span(private val htmlStyle: HtmlStyle) : AbsoluteSizeSpan(htmlSt
14
16
  tp.typeface = Typeface.create(tp.typeface, Typeface.BOLD)
15
17
  }
16
18
  }
19
+
20
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedH3Span {
21
+ return EnrichedH3Span(htmlStyle)
22
+ }
17
23
  }
@@ -18,9 +18,12 @@ import com.swmansion.enriched.utils.AsyncDrawable
18
18
  import androidx.core.graphics.drawable.toDrawable
19
19
  import com.swmansion.enriched.R
20
20
  import com.swmansion.enriched.spans.utils.ForceRedrawSpan
21
+ import com.swmansion.enriched.styles.HtmlStyle
21
22
  import com.swmansion.enriched.utils.ResourceManager
22
23
 
23
24
  class EnrichedImageSpan : ImageSpan, EnrichedInlineSpan {
25
+ override val dependsOnHtmlStyle: Boolean = false
26
+
24
27
  private var width: Int = 0
25
28
  private var height: Int = 0
26
29
 
@@ -119,6 +122,8 @@ class EnrichedImageSpan : ImageSpan, EnrichedInlineSpan {
119
122
  return height
120
123
  }
121
124
 
125
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedImageSpan = this
126
+
122
127
  companion object {
123
128
  fun createEnrichedImageSpan(src: String, width: Int, height: Int): EnrichedImageSpan {
124
129
  var imgDrawable = prepareDrawableForImage(src)
@@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan
7
7
  import com.swmansion.enriched.styles.HtmlStyle
8
8
 
9
9
  class EnrichedInlineCodeSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), EnrichedInlineSpan {
10
+ override val dependsOnHtmlStyle: Boolean = true
11
+
10
12
  override fun updateDrawState(textPaint: TextPaint) {
11
13
  val typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL)
12
14
  textPaint.typeface = typeface
@@ -18,4 +20,8 @@ class EnrichedInlineCodeSpan(private val htmlStyle: HtmlStyle) : MetricAffecting
18
20
  val typeface = Typeface.create(Typeface.MONOSPACE, Typeface.NORMAL)
19
21
  textPaint.typeface = typeface
20
22
  }
23
+
24
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedInlineCodeSpan {
25
+ return EnrichedInlineCodeSpan(htmlStyle)
26
+ }
21
27
  }
@@ -7,4 +7,9 @@ import com.swmansion.enriched.styles.HtmlStyle
7
7
 
8
8
  @Suppress("UNUSED_PARAMETER")
9
9
  class EnrichedItalicSpan(private val htmlStyle: HtmlStyle) : StyleSpan(Typeface.ITALIC), EnrichedInlineSpan {
10
+ override val dependsOnHtmlStyle: Boolean = false
11
+
12
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedItalicSpan {
13
+ return EnrichedItalicSpan(htmlStyle)
14
+ }
10
15
  }
@@ -7,6 +7,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedInlineSpan
7
7
  import com.swmansion.enriched.styles.HtmlStyle
8
8
 
9
9
  class EnrichedLinkSpan(private val url: String, private val htmlStyle: HtmlStyle) : ClickableSpan(), EnrichedInlineSpan {
10
+ override val dependsOnHtmlStyle: Boolean = true
11
+
10
12
  override fun onClick(view: View) {
11
13
  // Do nothing, links inside the input are not clickable.
12
14
  // We are using `ClickableSpan` to allow the text to be styled as a link.
@@ -21,4 +23,8 @@ class EnrichedLinkSpan(private val url: String, private val htmlStyle: HtmlStyle
21
23
  fun getUrl(): String {
22
24
  return url
23
25
  }
26
+
27
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedLinkSpan {
28
+ return EnrichedLinkSpan(url, htmlStyle)
29
+ }
24
30
  }
@@ -8,6 +8,8 @@ import com.swmansion.enriched.styles.HtmlStyle
8
8
 
9
9
  class EnrichedMentionSpan(private val text: String, private val indicator: String, private val attributes: Map<String, String>, private val htmlStyle: HtmlStyle) :
10
10
  ClickableSpan(), EnrichedInlineSpan {
11
+ override val dependsOnHtmlStyle: Boolean = true
12
+
11
13
  override fun onClick(view: View) {
12
14
  // Do nothing. Mentions inside the input are not clickable.
13
15
  // We are using `ClickableSpan` to allow the text to be styled as a clickable element.
@@ -33,4 +35,8 @@ class EnrichedMentionSpan(private val text: String, private val indicator: Strin
33
35
  fun getIndicator(): String {
34
36
  return indicator
35
37
  }
38
+
39
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedMentionSpan {
40
+ return EnrichedMentionSpan(text, indicator, attributes, htmlStyle)
41
+ }
36
42
  }
@@ -11,6 +11,8 @@ import com.swmansion.enriched.spans.interfaces.EnrichedParagraphSpan
11
11
  import com.swmansion.enriched.styles.HtmlStyle
12
12
 
13
13
  class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan {
14
+ override val dependsOnHtmlStyle: Boolean = true
15
+
14
16
  override fun updateMeasureState(p0: TextPaint) {
15
17
  // Do nothing, but inform layout that this span affects text metrics
16
18
  }
@@ -78,4 +80,8 @@ class EnrichedOrderedListSpan(private var index: Int, private val htmlStyle: Htm
78
80
  fun setIndex(i: Int) {
79
81
  index = i
80
82
  }
83
+
84
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedOrderedListSpan {
85
+ return EnrichedOrderedListSpan(index, htmlStyle)
86
+ }
81
87
  }
@@ -2,9 +2,13 @@ package com.swmansion.enriched.spans
2
2
 
3
3
  import com.swmansion.enriched.styles.HtmlStyle
4
4
 
5
- data class BaseSpanConfig(val clazz: Class<*>)
6
- data class ParagraphSpanConfig(val clazz: Class<*>, val isContinuous: Boolean)
7
- data class ListSpanConfig(val clazz: Class<*>, val shortcut: String)
5
+ interface ISpanConfig {
6
+ val clazz: Class<*>
7
+ }
8
+
9
+ data class BaseSpanConfig(override val clazz: Class<*>): ISpanConfig
10
+ data class ParagraphSpanConfig(override val clazz: Class<*>, val isContinuous: Boolean): ISpanConfig
11
+ data class ListSpanConfig(override val clazz: Class<*>, val shortcut: String) : ISpanConfig
8
12
 
9
13
  data class StylesMergingConfig(
10
14
  // styles that should be removed when we apply specific style
@@ -64,6 +68,8 @@ object EnrichedSpans {
64
68
  MENTION to BaseSpanConfig(EnrichedMentionSpan::class.java),
65
69
  )
66
70
 
71
+ val allSpans: Map<String, ISpanConfig> = inlineSpans + paragraphSpans + listSpans + parametrizedStyles
72
+
67
73
  fun getMergingConfigForStyle(style: String, htmlStyle: HtmlStyle): StylesMergingConfig? {
68
74
  return when (style) {
69
75
  BOLD -> {
@@ -6,4 +6,9 @@ import com.swmansion.enriched.styles.HtmlStyle
6
6
 
7
7
  @Suppress("UNUSED_PARAMETER")
8
8
  class EnrichedStrikeThroughSpan(private val htmlStyle: HtmlStyle) : StrikethroughSpan(), EnrichedInlineSpan {
9
+ override val dependsOnHtmlStyle: Boolean = false
10
+
11
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedStrikeThroughSpan {
12
+ return EnrichedStrikeThroughSpan(htmlStyle)
13
+ }
9
14
  }
@@ -6,4 +6,9 @@ import com.swmansion.enriched.styles.HtmlStyle
6
6
 
7
7
  @Suppress("UNUSED_PARAMETER")
8
8
  class EnrichedUnderlineSpan(private val htmlStyle: HtmlStyle) : UnderlineSpan(), EnrichedInlineSpan {
9
+ override val dependsOnHtmlStyle: Boolean = false
10
+
11
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedUnderlineSpan {
12
+ return EnrichedUnderlineSpan(htmlStyle)
13
+ }
9
14
  }
@@ -12,6 +12,8 @@ import com.swmansion.enriched.styles.HtmlStyle
12
12
 
13
13
  // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/main/core/java/android/text/style/BulletSpan.java
14
14
  class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : MetricAffectingSpan(), LeadingMarginSpan, EnrichedParagraphSpan {
15
+ override val dependsOnHtmlStyle: Boolean = true
16
+
15
17
  override fun updateMeasureState(p0: TextPaint) {
16
18
  // Do nothing, but inform layout that this span affects text metrics
17
19
  }
@@ -56,4 +58,8 @@ class EnrichedUnorderedListSpan(private val htmlStyle: HtmlStyle) : MetricAffect
56
58
  paint.style = style
57
59
  }
58
60
  }
61
+
62
+ override fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedUnorderedListSpan {
63
+ return EnrichedUnorderedListSpan(htmlStyle)
64
+ }
59
65
  }
@@ -1,4 +1,8 @@
1
1
  package com.swmansion.enriched.spans.interfaces
2
2
 
3
+ import com.swmansion.enriched.styles.HtmlStyle
4
+
3
5
  interface EnrichedSpan {
6
+ val dependsOnHtmlStyle: Boolean
7
+ fun rebuildWithStyle(htmlStyle: HtmlStyle): EnrichedSpan
4
8
  }
@@ -212,6 +212,84 @@ class HtmlStyle {
212
212
  return parseFontWeight(fontWeight)
213
213
  }
214
214
 
215
+ override fun equals(other: Any?): Boolean {
216
+ if (this === other) return true
217
+ if (other !is HtmlStyle) return false
218
+
219
+ return h1FontSize == other.h1FontSize &&
220
+ h1Bold == other.h1Bold &&
221
+ h2FontSize == other.h2FontSize &&
222
+ h2Bold == other.h2Bold &&
223
+ h3FontSize == other.h3FontSize &&
224
+ h3Bold == other.h3Bold &&
225
+
226
+ blockquoteColor == other.blockquoteColor &&
227
+ blockquoteBorderColor == other.blockquoteBorderColor &&
228
+ blockquoteStripeWidth == other.blockquoteStripeWidth &&
229
+ blockquoteGapWidth == other.blockquoteGapWidth &&
230
+
231
+ olGapWidth == other.olGapWidth &&
232
+ olMarginLeft == other.olMarginLeft &&
233
+ olMarkerFontWeight == other.olMarkerFontWeight &&
234
+ olMarkerColor == other.olMarkerColor &&
235
+
236
+ ulGapWidth == other.ulGapWidth &&
237
+ ulMarginLeft == other.ulMarginLeft &&
238
+ ulBulletSize == other.ulBulletSize &&
239
+ ulBulletColor == other.ulBulletColor &&
240
+
241
+ aColor == other.aColor &&
242
+ aUnderline == other.aUnderline &&
243
+
244
+ codeBlockColor == other.codeBlockColor &&
245
+ codeBlockBackgroundColor == other.codeBlockBackgroundColor &&
246
+ codeBlockRadius == other.codeBlockRadius &&
247
+
248
+ inlineCodeColor == other.inlineCodeColor &&
249
+ inlineCodeBackgroundColor == other.inlineCodeBackgroundColor &&
250
+
251
+ mentionsStyle == other.mentionsStyle
252
+ }
253
+
254
+
255
+ override fun hashCode(): Int {
256
+ var result = h1FontSize.hashCode()
257
+ result = 31 * result + h1Bold.hashCode()
258
+ result = 31 * result + h2FontSize.hashCode()
259
+ result = 31 * result + h2Bold.hashCode()
260
+ result = 31 * result + h3FontSize.hashCode()
261
+ result = 31 * result + h3Bold.hashCode()
262
+
263
+ result = 31 * result + (blockquoteColor ?: 0)
264
+ result = 31 * result + blockquoteBorderColor.hashCode()
265
+ result = 31 * result + blockquoteStripeWidth.hashCode()
266
+ result = 31 * result + blockquoteGapWidth.hashCode()
267
+
268
+ result = 31 * result + olGapWidth.hashCode()
269
+ result = 31 * result + olMarginLeft.hashCode()
270
+ result = 31 * result + (olMarkerFontWeight?.hashCode() ?: 0)
271
+ result = 31 * result + (olMarkerColor ?: 0)
272
+
273
+ result = 31 * result + ulGapWidth.hashCode()
274
+ result = 31 * result + ulMarginLeft.hashCode()
275
+ result = 31 * result + ulBulletSize.hashCode()
276
+ result = 31 * result + ulBulletColor.hashCode()
277
+
278
+ result = 31 * result + aColor.hashCode()
279
+ result = 31 * result + aUnderline.hashCode()
280
+
281
+ result = 31 * result + codeBlockColor.hashCode()
282
+ result = 31 * result + codeBlockBackgroundColor.hashCode()
283
+ result = 31 * result + codeBlockRadius.hashCode()
284
+
285
+ result = 31 * result + inlineCodeColor.hashCode()
286
+ result = 31 * result + inlineCodeBackgroundColor.hashCode()
287
+
288
+ result = 31 * result + mentionsStyle.hashCode()
289
+
290
+ return result
291
+ }
292
+
215
293
  companion object {
216
294
  data class MentionStyle(
217
295
  val color: Int,