@expensify/react-native-live-markdown 0.1.44 → 0.1.46

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 (60) hide show
  1. package/RNLiveMarkdown.podspec +13 -0
  2. package/android/build.gradle +12 -0
  3. package/android/proguard-rules.pro +1 -0
  4. package/android/src/main/java/com/expensify/livemarkdown/CustomFabricUIManager.java +47 -0
  5. package/android/src/main/java/com/expensify/livemarkdown/CustomMountingManager.java +248 -0
  6. package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownModule.java +27 -0
  7. package/android/src/main/java/com/expensify/livemarkdown/LiveMarkdownPackage.java +30 -3
  8. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontFamilySpan.java +7 -1
  9. package/android/src/main/java/com/expensify/livemarkdown/MarkdownFontSizeSpan.java +3 -1
  10. package/android/src/main/java/com/expensify/livemarkdown/MarkdownUtils.java +2 -6
  11. package/android/src/main/new_arch/CMakeLists.txt +82 -0
  12. package/android/src/main/new_arch/MarkdownCommitHook.cpp +173 -0
  13. package/android/src/main/new_arch/MarkdownCommitHook.h +52 -0
  14. package/android/src/main/new_arch/NativeProxy.cpp +38 -0
  15. package/android/src/main/new_arch/NativeProxy.h +37 -0
  16. package/android/src/main/new_arch/OnLoad.cpp +8 -0
  17. package/android/src/main/new_arch/RNLiveMarkdownSpec.cpp +44 -0
  18. package/android/src/main/new_arch/RNLiveMarkdownSpec.h +37 -0
  19. package/android/src/newarch/NativeProxy.java +24 -0
  20. package/android/src/oldarch/NativeLiveMarkdownModuleSpec.java +37 -0
  21. package/android/src/oldarch/NativeProxy.java +9 -0
  22. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.cpp +58 -0
  23. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownShadowFamilyRegistry.h +35 -0
  24. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.cpp +38 -0
  25. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorShadowNode.h +48 -0
  26. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorState.h +34 -0
  27. package/cpp/react/renderer/components/RNLiveMarkdownSpec/MarkdownTextInputDecoratorViewComponentDescriptor.h +17 -0
  28. package/ios/MarkdownCommitHook.h +42 -0
  29. package/ios/MarkdownCommitHook.mm +195 -0
  30. package/ios/MarkdownTextInputDecoratorComponentView.mm +24 -1
  31. package/ios/MarkdownTextInputDecoratorView.mm +5 -1
  32. package/ios/RCTBackedTextFieldDelegateAdapter+Markdown.m +1 -1
  33. package/ios/RCTBaseTextInputView+Markdown.m +2 -2
  34. package/ios/RCTLiveMarkdownModule.h +12 -0
  35. package/ios/RCTLiveMarkdownModule.mm +48 -0
  36. package/ios/RCTMarkdownUtils.h +1 -3
  37. package/ios/RCTMarkdownUtils.mm +129 -136
  38. package/ios/RCTTextInputComponentView+Markdown.mm +18 -4
  39. package/ios/RCTUITextView+Markdown.mm +1 -1
  40. package/lib/commonjs/MarkdownTextInput.js +4 -0
  41. package/lib/commonjs/MarkdownTextInput.js.map +1 -1
  42. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js +3 -1
  43. package/lib/commonjs/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -1
  44. package/lib/commonjs/NativeLiveMarkdownModule.js +9 -0
  45. package/lib/commonjs/NativeLiveMarkdownModule.js.map +1 -0
  46. package/lib/module/MarkdownTextInput.js +4 -0
  47. package/lib/module/MarkdownTextInput.js.map +1 -1
  48. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js +3 -1
  49. package/lib/module/MarkdownTextInputDecoratorViewNativeComponent.js.map +1 -1
  50. package/lib/module/NativeLiveMarkdownModule.js +3 -0
  51. package/lib/module/NativeLiveMarkdownModule.js.map +1 -0
  52. package/lib/typescript/src/MarkdownTextInput.d.ts.map +1 -1
  53. package/lib/typescript/src/MarkdownTextInputDecoratorViewNativeComponent.d.ts.map +1 -1
  54. package/lib/typescript/src/NativeLiveMarkdownModule.d.ts +7 -0
  55. package/lib/typescript/src/NativeLiveMarkdownModule.d.ts.map +1 -0
  56. package/package.json +3 -2
  57. package/react-native.config.js +12 -0
  58. package/src/MarkdownTextInput.tsx +5 -0
  59. package/src/MarkdownTextInputDecoratorViewNativeComponent.ts +3 -1
  60. package/src/NativeLiveMarkdownModule.ts +8 -0
@@ -19,4 +19,17 @@ Pod::Spec.new do |s|
19
19
  s.resources = "parser/react-native-live-markdown-parser.js"
20
20
 
21
21
  install_modules_dependencies(s)
22
+
23
+ if ENV['USE_FRAMEWORKS'] && ENV['RCT_NEW_ARCH_ENABLED']
24
+ add_dependency(s, "React-Fabric", :additional_framework_paths => [
25
+ "react/renderer/textlayoutmanager/platform/ios",
26
+ "react/renderer/components/textinput/iostextinput",
27
+ ])
28
+ end
29
+
30
+ s.subspec "common" do |ss|
31
+ ss.source_files = "cpp/**/*.{cpp,h}"
32
+ ss.header_dir = "RNLiveMarkdown"
33
+ ss.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/cpp\"" }
34
+ end
22
35
  end
@@ -53,6 +53,9 @@ android {
53
53
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
54
54
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
55
55
  buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
56
+
57
+ consumerProguardFiles "proguard-rules.pro"
58
+
56
59
  externalNativeBuild {
57
60
  cmake {
58
61
  arguments "-DANDROID_STL=c++_shared", "-DANDROID_TOOLCHAIN=clang"
@@ -112,6 +115,15 @@ android {
112
115
  "**/libreactnativejni.so",
113
116
  ]
114
117
  }
118
+
119
+ packagingOptions {
120
+ // For some reason gradle only complains about the duplicated version of librrc_root and libreact_render libraries
121
+ // while there are more libraries copied in intermediates folder of the lib build directory, we exclude
122
+ // only the ones that make the build fail (ideally we should only include libreanimated but we
123
+ // are only allowed to specify exlude patterns)
124
+ exclude "**/libreact_render*.so"
125
+ exclude "**/librrc_root.so"
126
+ }
115
127
  }
116
128
 
117
129
  repositories {
@@ -0,0 +1 @@
1
+ -keep class com.expensify.livemarkdown.** { *; }
@@ -0,0 +1,47 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import androidx.annotation.NonNull;
4
+
5
+ import com.facebook.react.bridge.ReactApplicationContext;
6
+ import com.facebook.react.bridge.ReadableMap;
7
+ import com.facebook.react.fabric.FabricUIManager;
8
+ import com.facebook.react.fabric.mounting.MountingManager;
9
+ import com.facebook.react.uimanager.ViewManagerRegistry;
10
+ import com.facebook.react.uimanager.events.BatchEventDispatchedListener;
11
+
12
+ import java.lang.reflect.Field;
13
+
14
+ public class CustomFabricUIManager {
15
+
16
+ public static FabricUIManager create(FabricUIManager source, ReadableMap markdownProps) {
17
+ Class<? extends FabricUIManager> uiManagerClass = source.getClass();
18
+
19
+ try {
20
+ Field mountingManagerField = uiManagerClass.getDeclaredField("mMountingManager");
21
+ mountingManagerField.setAccessible(true);
22
+
23
+ ReactApplicationContext reactContext = readPrivateField(source, "mReactApplicationContext");
24
+ ViewManagerRegistry viewManagerRegistry = readPrivateField(source, "mViewManagerRegistry");
25
+ BatchEventDispatchedListener batchEventDispatchedListener = readPrivateField(source, "mBatchEventDispatchedListener");
26
+ MountingManager.MountItemExecutor mountItemExecutor = readPrivateField(source, "mMountItemExecutor");
27
+
28
+ FabricUIManager customFabricUIManager = new FabricUIManager(reactContext, viewManagerRegistry, batchEventDispatchedListener);
29
+
30
+ mountingManagerField.set(customFabricUIManager, new CustomMountingManager(viewManagerRegistry, mountItemExecutor, reactContext, markdownProps));
31
+
32
+ return customFabricUIManager;
33
+ } catch (NoSuchFieldException | IllegalAccessException e) {
34
+ throw new RuntimeException("[LiveMarkdown] Cannot read data from FabricUIManager");
35
+ }
36
+ }
37
+
38
+ private static <T> T readPrivateField(Object obj, String name) throws NoSuchFieldException, IllegalAccessException {
39
+ Class<?> clazz = obj.getClass();
40
+
41
+ Field field = clazz.getDeclaredField(name);
42
+ field.setAccessible(true);
43
+ T value = (T) field.get(obj);
44
+
45
+ return value;
46
+ }
47
+ }
@@ -0,0 +1,248 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import static com.facebook.react.views.text.TextAttributeProps.UNSET;
4
+
5
+ import android.content.Context;
6
+ import android.content.res.AssetManager;
7
+ import android.text.BoringLayout;
8
+ import android.text.Layout;
9
+ import android.text.Spannable;
10
+ import android.text.SpannableStringBuilder;
11
+ import android.text.TextPaint;
12
+
13
+ import androidx.annotation.NonNull;
14
+ import androidx.annotation.Nullable;
15
+
16
+ import com.facebook.common.logging.FLog;
17
+ import com.facebook.react.bridge.ReactContext;
18
+ import com.facebook.react.bridge.ReadableMap;
19
+ import com.facebook.react.common.mapbuffer.MapBuffer;
20
+ import com.facebook.react.fabric.mounting.MountingManager;
21
+ import com.facebook.react.uimanager.PixelUtil;
22
+ import com.facebook.react.uimanager.ViewManagerRegistry;
23
+ import com.facebook.react.views.text.TextAttributeProps;
24
+ import com.facebook.react.views.text.TextInlineViewPlaceholderSpan;
25
+ import com.facebook.react.views.text.TextLayoutManagerMapBuffer;
26
+ import com.facebook.yoga.YogaMeasureMode;
27
+ import com.facebook.yoga.YogaMeasureOutput;
28
+
29
+ import java.lang.reflect.InvocationTargetException;
30
+ import java.lang.reflect.Method;
31
+
32
+ public class CustomMountingManager extends MountingManager {
33
+ private static final boolean DEFAULT_INCLUDE_FONT_PADDING = true;
34
+ private static final TextPaint sTextPaintInstance = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
35
+
36
+ private MarkdownUtils markdownUtils;
37
+
38
+ public CustomMountingManager(
39
+ @NonNull ViewManagerRegistry viewManagerRegistry,
40
+ @NonNull MountItemExecutor mountItemExecutor,
41
+ @NonNull Context context,
42
+ @NonNull ReadableMap decoratorProps) {
43
+ super(viewManagerRegistry, mountItemExecutor);
44
+
45
+ AssetManager assetManager = context.getAssets();
46
+ MarkdownUtils.maybeInitializeRuntime(assetManager);
47
+
48
+ this.markdownUtils = new MarkdownUtils(assetManager);
49
+ this.markdownUtils.setMarkdownStyle(new MarkdownStyle(decoratorProps, context));
50
+ }
51
+
52
+ @Override
53
+ public long measureMapBuffer(
54
+ @NonNull ReactContext context,
55
+ @NonNull String componentName,
56
+ @NonNull MapBuffer attributedString,
57
+ @NonNull MapBuffer paragraphAttributes,
58
+ @Nullable MapBuffer state,
59
+ float width,
60
+ @NonNull YogaMeasureMode widthYogaMeasureMode,
61
+ float height,
62
+ @NonNull YogaMeasureMode heightYogaMeasureMode,
63
+ @Nullable float[] attachmentsPositions) {
64
+
65
+ Spannable text =
66
+ TextLayoutManagerMapBuffer.getOrCreateSpannableForText(context, attributedString, null);
67
+
68
+ if (text == null) {
69
+ return 0;
70
+ }
71
+
72
+ int textBreakStrategy =
73
+ TextAttributeProps.getTextBreakStrategy(
74
+ paragraphAttributes.getString(TextLayoutManagerMapBuffer.PA_KEY_TEXT_BREAK_STRATEGY));
75
+ boolean includeFontPadding =
76
+ paragraphAttributes.contains(TextLayoutManagerMapBuffer.PA_KEY_INCLUDE_FONT_PADDING)
77
+ ? paragraphAttributes.getBoolean(TextLayoutManagerMapBuffer.PA_KEY_INCLUDE_FONT_PADDING)
78
+ : DEFAULT_INCLUDE_FONT_PADDING;
79
+ int hyphenationFrequency =
80
+ TextAttributeProps.getHyphenationFrequency(
81
+ paragraphAttributes.getString(TextLayoutManagerMapBuffer.PA_KEY_HYPHENATION_FREQUENCY));
82
+
83
+ // StaticLayout returns wrong metrics for the last line if it's empty, add something to the
84
+ // last line so it's measured correctly
85
+ if (text.toString().endsWith("\n")) {
86
+ SpannableStringBuilder sb = new SpannableStringBuilder(text);
87
+ sb.append("I");
88
+
89
+ text = sb;
90
+ }
91
+
92
+ markdownUtils.applyMarkdownFormatting((SpannableStringBuilder)text);
93
+
94
+ BoringLayout.Metrics boring = BoringLayout.isBoring(text, sTextPaintInstance);
95
+
96
+ Class<TextLayoutManagerMapBuffer> mapBufferClass = TextLayoutManagerMapBuffer.class;
97
+ try {
98
+ Method createLayoutMethod = mapBufferClass.getDeclaredMethod("createLayout", Spannable.class, BoringLayout.Metrics.class, float.class, YogaMeasureMode.class, boolean.class, int.class, int.class);
99
+ createLayoutMethod.setAccessible(true);
100
+
101
+ Layout layout = (Layout)createLayoutMethod.invoke(
102
+ null,
103
+ text,
104
+ boring,
105
+ width,
106
+ widthYogaMeasureMode,
107
+ includeFontPadding,
108
+ textBreakStrategy,
109
+ hyphenationFrequency);
110
+
111
+ int maximumNumberOfLines =
112
+ paragraphAttributes.contains(TextLayoutManagerMapBuffer.PA_KEY_MAX_NUMBER_OF_LINES)
113
+ ? paragraphAttributes.getInt(TextLayoutManagerMapBuffer.PA_KEY_MAX_NUMBER_OF_LINES)
114
+ : UNSET;
115
+
116
+ int calculatedLineCount =
117
+ maximumNumberOfLines == UNSET || maximumNumberOfLines == 0
118
+ ? layout.getLineCount()
119
+ : Math.min(maximumNumberOfLines, layout.getLineCount());
120
+
121
+ // Instead of using `layout.getWidth()` (which may yield a significantly larger width for
122
+ // text that is wrapping), compute width using the longest line.
123
+ float calculatedWidth = 0;
124
+ if (widthYogaMeasureMode == YogaMeasureMode.EXACTLY) {
125
+ calculatedWidth = width;
126
+ } else {
127
+ for (int lineIndex = 0; lineIndex < calculatedLineCount; lineIndex++) {
128
+ boolean endsWithNewLine =
129
+ text.length() > 0 && text.charAt(layout.getLineEnd(lineIndex) - 1) == '\n';
130
+ float lineWidth =
131
+ endsWithNewLine ? layout.getLineMax(lineIndex) : layout.getLineWidth(lineIndex);
132
+ if (lineWidth > calculatedWidth) {
133
+ calculatedWidth = lineWidth;
134
+ }
135
+ }
136
+ if (widthYogaMeasureMode == YogaMeasureMode.AT_MOST && calculatedWidth > width) {
137
+ calculatedWidth = width;
138
+ }
139
+ }
140
+
141
+ // Android 11+ introduces changes in text width calculation which leads to cases
142
+ // where the container is measured smaller than text. Math.ceil prevents it
143
+ // See T136756103 for investigation
144
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.Q) {
145
+ calculatedWidth = (float) Math.ceil(calculatedWidth);
146
+ }
147
+
148
+ float calculatedHeight = height;
149
+ if (heightYogaMeasureMode != YogaMeasureMode.EXACTLY) {
150
+ calculatedHeight = layout.getLineBottom(calculatedLineCount - 1);
151
+ if (heightYogaMeasureMode == YogaMeasureMode.AT_MOST && calculatedHeight > height) {
152
+ calculatedHeight = height;
153
+ }
154
+ }
155
+
156
+ // Calculate the positions of the attachments (views) that will be rendered inside the
157
+ // Spanned Text. The following logic is only executed when a text contains views inside.
158
+ // This follows a similar logic than used in pre-fabric (see ReactTextView.onLayout method).
159
+ int attachmentIndex = 0;
160
+ int lastAttachmentFoundInSpan;
161
+ for (int i = 0; i < text.length(); i = lastAttachmentFoundInSpan) {
162
+ lastAttachmentFoundInSpan =
163
+ text.nextSpanTransition(i, text.length(), TextInlineViewPlaceholderSpan.class);
164
+ TextInlineViewPlaceholderSpan[] placeholders =
165
+ text.getSpans(i, lastAttachmentFoundInSpan, TextInlineViewPlaceholderSpan.class);
166
+ for (TextInlineViewPlaceholderSpan placeholder : placeholders) {
167
+ int start = text.getSpanStart(placeholder);
168
+ int line = layout.getLineForOffset(start);
169
+ boolean isLineTruncated = layout.getEllipsisCount(line) > 0;
170
+ // This truncation check works well on recent versions of Android (tested on 5.1.1 and
171
+ // 6.0.1) but not on Android 4.4.4. The reason is that getEllipsisCount is buggy on
172
+ // Android 4.4.4. Specifically, it incorrectly returns 0 if an inline view is the
173
+ // first thing to be truncated.
174
+ if (!(isLineTruncated && start >= layout.getLineStart(line) + layout.getEllipsisStart(line))
175
+ || start >= layout.getLineEnd(line)) {
176
+ float placeholderWidth = placeholder.getWidth();
177
+ float placeholderHeight = placeholder.getHeight();
178
+ // Calculate if the direction of the placeholder character is Right-To-Left.
179
+ boolean isRtlChar = layout.isRtlCharAt(start);
180
+ boolean isRtlParagraph = layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
181
+ float placeholderLeftPosition;
182
+ // There's a bug on Samsung devices where calling getPrimaryHorizontal on
183
+ // the last offset in the layout will result in an endless loop. Work around
184
+ // this bug by avoiding getPrimaryHorizontal in that case.
185
+ if (start == text.length() - 1) {
186
+ boolean endsWithNewLine =
187
+ text.length() > 0 && text.charAt(layout.getLineEnd(line) - 1) == '\n';
188
+ float lineWidth = endsWithNewLine ? layout.getLineMax(line) : layout.getLineWidth(line);
189
+ placeholderLeftPosition =
190
+ isRtlParagraph
191
+ // Equivalent to `layout.getLineLeft(line)` but `getLineLeft` returns
192
+ // incorrect
193
+ // values when the paragraph is RTL and `setSingleLine(true)`.
194
+ ? calculatedWidth - lineWidth
195
+ : layout.getLineRight(line) - placeholderWidth;
196
+ } else {
197
+ // The direction of the paragraph may not be exactly the direction the string is
198
+ // heading
199
+ // in at the
200
+ // position of the placeholder. So, if the direction of the character is the same
201
+ // as the
202
+ // paragraph
203
+ // use primary, secondary otherwise.
204
+ boolean characterAndParagraphDirectionMatch = isRtlParagraph == isRtlChar;
205
+ placeholderLeftPosition =
206
+ characterAndParagraphDirectionMatch
207
+ ? layout.getPrimaryHorizontal(start)
208
+ : layout.getSecondaryHorizontal(start);
209
+ if (isRtlParagraph) {
210
+ // Adjust `placeholderLeftPosition` to work around an Android bug.
211
+ // The bug is when the paragraph is RTL and `setSingleLine(true)`, some layout
212
+ // methods such as `getPrimaryHorizontal`, `getSecondaryHorizontal`, and
213
+ // `getLineRight` return incorrect values. Their return values seem to be off
214
+ // by the same number of pixels so subtracting these values cancels out the
215
+ // error.
216
+ //
217
+ // The result is equivalent to bugless versions of
218
+ // `getPrimaryHorizontal`/`getSecondaryHorizontal`.
219
+ placeholderLeftPosition =
220
+ calculatedWidth - (layout.getLineRight(line) - placeholderLeftPosition);
221
+ }
222
+ if (isRtlChar) {
223
+ placeholderLeftPosition -= placeholderWidth;
224
+ }
225
+ }
226
+ // Vertically align the inline view to the baseline of the line of text.
227
+ float placeholderTopPosition = layout.getLineBaseline(line) - placeholderHeight;
228
+ int attachmentPosition = attachmentIndex * 2;
229
+
230
+ // The attachment array returns the positions of each of the attachments as
231
+ attachmentsPositions[attachmentPosition] =
232
+ PixelUtil.toDIPFromPixel(placeholderTopPosition);
233
+ attachmentsPositions[attachmentPosition + 1] =
234
+ PixelUtil.toDIPFromPixel(placeholderLeftPosition);
235
+ attachmentIndex++;
236
+ }
237
+ }
238
+ }
239
+
240
+ float widthInSP = PixelUtil.toDIPFromPixel(calculatedWidth);
241
+ float heightInSP = PixelUtil.toDIPFromPixel(calculatedHeight);
242
+
243
+ return YogaMeasureOutput.make(widthInSP, heightInSP);
244
+ } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
245
+ throw new RuntimeException(e);
246
+ }
247
+ }
248
+ }
@@ -0,0 +1,27 @@
1
+ package com.expensify.livemarkdown;
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext;
4
+ import com.facebook.react.bridge.UIManager;
5
+ import com.facebook.react.fabric.FabricUIManager;
6
+ import com.facebook.react.uimanager.UIManagerHelper;
7
+ import com.facebook.react.uimanager.common.UIManagerType;
8
+
9
+ public class LiveMarkdownModule extends NativeLiveMarkdownModuleSpec {
10
+ private NativeProxy mNativeProxy;
11
+ public LiveMarkdownModule(ReactApplicationContext reactContext) {
12
+ super(reactContext);
13
+
14
+ this.mNativeProxy = new NativeProxy();
15
+ }
16
+
17
+ @Override
18
+ public boolean install() {
19
+ if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
20
+ FabricUIManager uiManager =
21
+ (FabricUIManager) UIManagerHelper.getUIManager(getReactApplicationContext(), UIManagerType.FABRIC);
22
+ mNativeProxy.createCommitHook(uiManager);
23
+ }
24
+
25
+ return true;
26
+ }
27
+ }
@@ -1,15 +1,22 @@
1
1
  package com.expensify.livemarkdown;
2
2
 
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
3
6
  import com.facebook.react.ReactPackage;
7
+ import com.facebook.react.TurboReactPackage;
4
8
  import com.facebook.react.bridge.NativeModule;
5
9
  import com.facebook.react.bridge.ReactApplicationContext;
10
+ import com.facebook.react.module.model.ReactModuleInfo;
11
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
6
12
  import com.facebook.react.uimanager.ViewManager;
7
13
 
8
14
  import java.util.ArrayList;
9
15
  import java.util.Collections;
10
16
  import java.util.List;
17
+ import java.util.Map;
11
18
 
12
- public class LiveMarkdownPackage implements ReactPackage {
19
+ public class LiveMarkdownPackage extends TurboReactPackage {
13
20
  @Override
14
21
  public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
15
22
  List<ViewManager> viewManagers = new ArrayList<>();
@@ -18,7 +25,27 @@ public class LiveMarkdownPackage implements ReactPackage {
18
25
  }
19
26
 
20
27
  @Override
21
- public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
22
- return Collections.emptyList();
28
+ public ReactModuleInfoProvider getReactModuleInfoProvider() {
29
+ return new ReactModuleInfoProvider() {
30
+ @Override
31
+ public Map<String, ReactModuleInfo> getReactModuleInfos() {
32
+ return Map.of(LiveMarkdownModule.NAME, new ReactModuleInfo(
33
+ LiveMarkdownModule.NAME,
34
+ LiveMarkdownModule.class.getName(),
35
+ false, // canOverrideExistingModule
36
+ false, // needsEagerInit
37
+ false, // isCxxModule
38
+ true // isTurboModule
39
+ ));
40
+ }
41
+ };
42
+ }
43
+ @Nullable
44
+ @Override
45
+ public NativeModule getModule(@NonNull String s, @NonNull ReactApplicationContext reactApplicationContext) {
46
+ if (s.equals(LiveMarkdownModule.NAME)) {
47
+ return new LiveMarkdownModule(reactApplicationContext);
48
+ }
49
+ return null;
23
50
  }
24
51
  }
@@ -8,6 +8,7 @@ import android.text.style.MetricAffectingSpan;
8
8
 
9
9
  import androidx.annotation.NonNull;
10
10
 
11
+ import com.facebook.react.common.assets.ReactFontManager.TypefaceStyle;
11
12
  import com.facebook.react.views.text.ReactFontManager;
12
13
 
13
14
  public class MarkdownFontFamilySpan extends MetricAffectingSpan implements MarkdownSpan {
@@ -31,7 +32,12 @@ public class MarkdownFontFamilySpan extends MetricAffectingSpan implements Markd
31
32
  }
32
33
 
33
34
  private void apply(@NonNull TextPaint textPaint) {
34
- int style = textPaint.getTypeface().getStyle();
35
+ int style = TypefaceStyle.NORMAL;
36
+ if (textPaint.getTypeface() != null) {
37
+ style = textPaint.getTypeface().getStyle();
38
+ } else {
39
+ style = TypefaceStyle.NORMAL;
40
+ }
35
41
  Typeface typeface = ReactFontManager.getInstance().getTypeface(mFontFamily, style, mAssetManager);
36
42
  textPaint.setTypeface(typeface);
37
43
  textPaint.setFlags(textPaint.getFlags() | Paint.SUBPIXEL_TEXT_FLAG);
@@ -2,8 +2,10 @@ package com.expensify.livemarkdown;
2
2
 
3
3
  import android.text.style.AbsoluteSizeSpan;
4
4
 
5
+ import com.facebook.react.uimanager.PixelUtil;
6
+
5
7
  public class MarkdownFontSizeSpan extends AbsoluteSizeSpan implements MarkdownSpan {
6
8
  public MarkdownFontSizeSpan(float fontSize) {
7
- super((int) fontSize, true);
9
+ super((int) PixelUtil.toPixelFromDIP(fontSize), false);
8
10
  }
9
11
  }
@@ -29,9 +29,7 @@ public class MarkdownUtils {
29
29
 
30
30
  private static boolean IS_RUNTIME_INITIALIZED = false;
31
31
 
32
- @ThreadConfined(UI)
33
- public static void maybeInitializeRuntime(AssetManager assetManager) {
34
- UiThreadUtil.assertOnUiThread();
32
+ public static synchronized void maybeInitializeRuntime(AssetManager assetManager) {
35
33
  if (IS_RUNTIME_INITIALIZED) {
36
34
  return;
37
35
  }
@@ -50,9 +48,7 @@ public class MarkdownUtils {
50
48
 
51
49
  private static native void nativeInitializeRuntime(String code);
52
50
 
53
- @ThreadConfined(UI)
54
- private static String parseMarkdown(String input) {
55
- UiThreadUtil.assertOnUiThread();
51
+ private synchronized static String parseMarkdown(String input) {
56
52
  return nativeParseMarkdown(input);
57
53
  }
58
54
 
@@ -0,0 +1,82 @@
1
+ cmake_minimum_required(VERSION 3.13)
2
+ set(CMAKE_VERBOSE_MAKEFILE ON)
3
+ set(CMAKE_CXX_STANDARD 20)
4
+
5
+ set(LIB_LITERAL RNLiveMarkdownSpec)
6
+ set(LIB_TARGET_NAME react_codegen_${LIB_LITERAL})
7
+
8
+ set(LIB_ANDROID_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..)
9
+ set(LIB_CPP_DIR ${LIB_ANDROID_DIR}/../cpp)
10
+ set(LIB_CUSTOM_SOURCES_DIR ${LIB_CPP_DIR}/react/renderer/components/${LIB_LITERAL})
11
+ set(LIB_ANDROID_GENERATED_JNI_DIR ${LIB_ANDROID_DIR}/build/generated/source/codegen/jni)
12
+ set(LIB_ANDROID_GENERATED_COMPONENTS_DIR ${LIB_ANDROID_GENERATED_JNI_DIR}/react/renderer/components/${LIB_LITERAL})
13
+
14
+ file(GLOB LIB_MODULE_SRCS CONFIGURE_DEPENDS *.cpp)
15
+ file(GLOB LIB_CUSTOM_SRCS CONFIGURE_DEPENDS ${LIB_CUSTOM_SOURCES_DIR}/*.cpp)
16
+ file(GLOB LIB_CODEGEN_SRCS CONFIGURE_DEPENDS ${LIB_ANDROID_GENERATED_COMPONENTS_DIR}/*.cpp)
17
+
18
+ set(RN_DIR ${LIB_ANDROID_DIR}/../example/node_modules/react-native)
19
+
20
+ add_library(
21
+ ${LIB_TARGET_NAME}
22
+ SHARED
23
+ ${LIB_MODULE_SRCS}
24
+ ${LIB_CUSTOM_SRCS}
25
+ ${LIB_CODEGEN_SRCS}
26
+ )
27
+
28
+ target_include_directories(
29
+ ${LIB_TARGET_NAME}
30
+ PUBLIC
31
+ .
32
+ ${LIB_ANDROID_GENERATED_JNI_DIR}
33
+ ${LIB_ANDROID_GENERATED_COMPONENTS_DIR}
34
+ ${LIB_CPP_DIR}
35
+ )
36
+
37
+ find_package(ReactAndroid REQUIRED CONFIG)
38
+
39
+ target_link_libraries(
40
+ ${LIB_TARGET_NAME}
41
+ ReactAndroid::rrc_text
42
+ ReactAndroid::rrc_textinput
43
+ ReactAndroid::react_render_textlayoutmanager
44
+ ReactAndroid::react_render_imagemanager
45
+ fabricjni
46
+ fbjni
47
+ folly_runtime
48
+ glog
49
+ jsi
50
+ react_codegen_rncore
51
+ react_debug
52
+ react_nativemodule_core
53
+ react_render_core
54
+ react_render_debug
55
+ react_render_graphics
56
+ react_render_mapbuffer
57
+ ReactAndroid::react_render_uimanager
58
+ ReactAndroid::react_render_scheduler
59
+ react_utils
60
+ runtimeexecutor
61
+ rrc_view
62
+ turbomodulejsijni
63
+ yoga
64
+ android
65
+ log
66
+ )
67
+
68
+ target_compile_options(
69
+ ${LIB_TARGET_NAME}
70
+ PRIVATE
71
+ -DLOG_TAG=\"ReactNative\"
72
+ -fexceptions
73
+ -frtti
74
+ -Wall
75
+ -std=c++20
76
+ )
77
+
78
+ target_include_directories(
79
+ ${CMAKE_PROJECT_NAME}
80
+ PUBLIC
81
+ ${CMAKE_CURRENT_SOURCE_DIR}
82
+ )