react-native-klarna-inapp-sdk 2.4.10 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/README.md +2 -1
  2. package/android/build.gradle +3 -1
  3. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/KlarnaMobileSDKPackage.java +5 -1
  4. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/checkout/KlarnaCheckoutViewManager.java +4 -10
  5. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/WebViewResizeObserver.java +28 -0
  6. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/event/ComponentEventSender.java +5 -1
  7. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/ResizeObserverWrapperView.java +2 -2
  8. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/WrapperView.java +2 -2
  9. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/YogaLayoutHelper.java +39 -0
  10. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewCallback.kt +30 -0
  11. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewEventSender.kt +80 -0
  12. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewManager.java +544 -0
  13. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandler.java +30 -0
  14. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSender.java +64 -0
  15. package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewManager.java +356 -0
  16. package/android/src/newarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaExpressCheckoutViewSpec.java +31 -0
  17. package/android/src/newarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaOSMViewSpec.java +31 -0
  18. package/android/src/oldarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaExpressCheckoutViewSpec.java +36 -0
  19. package/android/src/oldarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaOSMViewSpec.java +32 -0
  20. package/android/src/test/java/com/klarna/mobile/sdk/reactnative/KlarnaMobileSDKPackageTest.java +5 -1
  21. package/android/src/test/java/com/klarna/mobile/sdk/reactnative/common/ui/YogaLayoutHelperTest.java +79 -0
  22. package/android/src/test/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewManagerTest.java +259 -0
  23. package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandlerTest.java +136 -0
  24. package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSenderTest.java +184 -0
  25. package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewManagerTest.java +385 -0
  26. package/ios/RNKlarnaMobileSDK.xcodeproj/project.pbxproj +55 -1
  27. package/ios/Sources/KlarnaExpressCheckoutViewManager.h +5 -0
  28. package/ios/Sources/KlarnaExpressCheckoutViewManager.mm +39 -0
  29. package/ios/Sources/KlarnaOSMViewManager.h +5 -0
  30. package/ios/Sources/KlarnaOSMViewManager.mm +59 -0
  31. package/ios/Sources/view/KlarnaExpressCheckoutButtonDelegateProxy.swift +145 -0
  32. package/ios/Sources/view/KlarnaExpressCheckoutViewWrapper.h +41 -0
  33. package/ios/Sources/view/KlarnaOSMViewWrapper.h +48 -0
  34. package/ios/Sources/view/newarch/KlarnaExpressCheckoutViewWrapper.mm +228 -0
  35. package/ios/Sources/view/newarch/KlarnaOSMViewWrapper.mm +294 -0
  36. package/ios/Sources/view/newarch/KlarnaStandaloneWebViewWrapper.mm +20 -13
  37. package/ios/Sources/view/oldarch/KlarnaExpressCheckoutViewWrapper.mm +222 -0
  38. package/ios/Sources/view/oldarch/KlarnaOSMViewWrapper.mm +273 -0
  39. package/ios/Sources/view/oldarch/KlarnaStandaloneWebViewWrapper.mm +11 -13
  40. package/ios/Tests/KlarnaExpressCheckoutViewWrapperTests.m +391 -0
  41. package/ios/Tests/KlarnaOSMViewWrapperTests.m +450 -0
  42. package/ios/Tests/KlarnaStandaloneWebViewWrapperTests.m +105 -0
  43. package/lib/commonjs/KlarnaExpressCheckoutView.js +75 -0
  44. package/lib/commonjs/KlarnaExpressCheckoutView.js.map +1 -0
  45. package/lib/commonjs/KlarnaOSMView.js +94 -0
  46. package/lib/commonjs/KlarnaOSMView.js.map +1 -0
  47. package/lib/commonjs/index.js +99 -0
  48. package/lib/commonjs/index.js.map +1 -1
  49. package/lib/commonjs/specs/KlarnaExpressCheckoutViewNativeComponent.js +11 -0
  50. package/lib/commonjs/specs/KlarnaExpressCheckoutViewNativeComponent.js.map +1 -0
  51. package/lib/commonjs/specs/KlarnaOSMViewNativeComponent.js +16 -0
  52. package/lib/commonjs/specs/KlarnaOSMViewNativeComponent.js.map +1 -0
  53. package/lib/commonjs/types/button/KlarnaButtonShape.js +14 -0
  54. package/lib/commonjs/types/button/KlarnaButtonShape.js.map +1 -0
  55. package/lib/commonjs/types/button/KlarnaButtonStyle.js +13 -0
  56. package/lib/commonjs/types/button/KlarnaButtonStyle.js.map +1 -0
  57. package/lib/commonjs/types/button/KlarnaButtonTheme.js +14 -0
  58. package/lib/commonjs/types/button/KlarnaButtonTheme.js.map +1 -0
  59. package/lib/commonjs/types/common/KlarnaExpressCheckoutSessionOptions.js +9 -0
  60. package/lib/commonjs/types/common/KlarnaExpressCheckoutSessionOptions.js.map +1 -0
  61. package/lib/commonjs/types/common/KlarnaOSMPlacementKey.js +2 -0
  62. package/lib/commonjs/types/common/KlarnaOSMPlacementKey.js.map +1 -0
  63. package/lib/commonjs/types/common/KlarnaTheme.js +14 -0
  64. package/lib/commonjs/types/common/KlarnaTheme.js.map +1 -0
  65. package/lib/module/KlarnaExpressCheckoutView.js +64 -0
  66. package/lib/module/KlarnaExpressCheckoutView.js.map +1 -0
  67. package/lib/module/KlarnaOSMView.js +84 -0
  68. package/lib/module/KlarnaOSMView.js.map +1 -0
  69. package/lib/module/index.js +9 -0
  70. package/lib/module/index.js.map +1 -1
  71. package/lib/module/specs/KlarnaExpressCheckoutViewNativeComponent.js +3 -0
  72. package/lib/module/specs/KlarnaExpressCheckoutViewNativeComponent.js.map +1 -0
  73. package/lib/module/specs/KlarnaOSMViewNativeComponent.js +7 -0
  74. package/lib/module/specs/KlarnaOSMViewNativeComponent.js.map +1 -0
  75. package/lib/module/types/button/KlarnaButtonShape.js +7 -0
  76. package/lib/module/types/button/KlarnaButtonShape.js.map +1 -0
  77. package/lib/module/types/button/KlarnaButtonStyle.js +6 -0
  78. package/lib/module/types/button/KlarnaButtonStyle.js.map +1 -0
  79. package/lib/module/types/button/KlarnaButtonTheme.js +7 -0
  80. package/lib/module/types/button/KlarnaButtonTheme.js.map +1 -0
  81. package/lib/module/types/common/KlarnaExpressCheckoutSessionOptions.js +2 -0
  82. package/lib/module/types/common/KlarnaExpressCheckoutSessionOptions.js.map +1 -0
  83. package/lib/module/types/common/KlarnaOSMPlacementKey.js +2 -0
  84. package/lib/module/types/common/KlarnaOSMPlacementKey.js.map +1 -0
  85. package/lib/module/types/common/KlarnaTheme.js +7 -0
  86. package/lib/module/types/common/KlarnaTheme.js.map +1 -0
  87. package/lib/typescript/KlarnaExpressCheckoutView.d.ts +47 -0
  88. package/lib/typescript/KlarnaExpressCheckoutView.d.ts.map +1 -0
  89. package/lib/typescript/KlarnaOSMView.d.ts +36 -0
  90. package/lib/typescript/KlarnaOSMView.d.ts.map +1 -0
  91. package/lib/typescript/index.d.ts +9 -0
  92. package/lib/typescript/index.d.ts.map +1 -1
  93. package/lib/typescript/specs/KlarnaExpressCheckoutViewNativeComponent.d.ts +45 -0
  94. package/lib/typescript/specs/KlarnaExpressCheckoutViewNativeComponent.d.ts.map +1 -0
  95. package/lib/typescript/specs/KlarnaOSMViewNativeComponent.d.ts +35 -0
  96. package/lib/typescript/specs/KlarnaOSMViewNativeComponent.d.ts.map +1 -0
  97. package/lib/typescript/types/button/KlarnaButtonShape.d.ts +6 -0
  98. package/lib/typescript/types/button/KlarnaButtonShape.d.ts.map +1 -0
  99. package/lib/typescript/types/button/KlarnaButtonStyle.d.ts +5 -0
  100. package/lib/typescript/types/button/KlarnaButtonStyle.d.ts.map +1 -0
  101. package/lib/typescript/types/button/KlarnaButtonTheme.d.ts +6 -0
  102. package/lib/typescript/types/button/KlarnaButtonTheme.d.ts.map +1 -0
  103. package/lib/typescript/types/common/KlarnaExpressCheckoutSessionOptions.d.ts +10 -0
  104. package/lib/typescript/types/common/KlarnaExpressCheckoutSessionOptions.d.ts.map +1 -0
  105. package/lib/typescript/types/common/KlarnaOSMPlacementKey.d.ts +2 -0
  106. package/lib/typescript/types/common/KlarnaOSMPlacementKey.d.ts.map +1 -0
  107. package/lib/typescript/types/common/KlarnaTheme.d.ts +6 -0
  108. package/lib/typescript/types/common/KlarnaTheme.d.ts.map +1 -0
  109. package/package.json +5 -3
  110. package/react-native-klarna-inapp-sdk.podspec +2 -1
  111. package/src/KlarnaExpressCheckoutView.tsx +169 -0
  112. package/src/KlarnaOSMView.tsx +157 -0
  113. package/src/index.tsx +9 -0
  114. package/src/specs/KlarnaExpressCheckoutViewNativeComponent.ts +66 -0
  115. package/src/specs/KlarnaOSMViewNativeComponent.ts +50 -0
  116. package/src/types/button/KlarnaButtonShape.ts +5 -0
  117. package/src/types/button/KlarnaButtonStyle.ts +4 -0
  118. package/src/types/button/KlarnaButtonTheme.ts +5 -0
  119. package/src/types/common/KlarnaExpressCheckoutSessionOptions.ts +13 -0
  120. package/src/types/common/KlarnaOSMPlacementKey.ts +3 -0
  121. package/src/types/common/KlarnaTheme.ts +5 -0
@@ -0,0 +1,356 @@
1
+ package com.klarna.mobile.sdk.reactnative.osm;
2
+
3
+ import android.app.Activity;
4
+ import android.view.View;
5
+ import android.view.ViewTreeObserver;
6
+
7
+ import androidx.annotation.NonNull;
8
+ import androidx.annotation.Nullable;
9
+
10
+ import com.facebook.react.bridge.ReactContext;
11
+ import com.facebook.react.bridge.ReadableArray;
12
+ import com.facebook.react.uimanager.ThemedReactContext;
13
+ import com.facebook.react.uimanager.UIManagerHelper;
14
+ import com.facebook.react.uimanager.annotations.ReactProp;
15
+ import com.facebook.react.uimanager.events.EventDispatcher;
16
+ import com.klarna.mobile.sdk.api.osm.KlarnaOSMView;
17
+ import com.klarna.mobile.sdk.KlarnaMobileSDKError;
18
+ import com.klarna.mobile.sdk.reactnative.common.ui.WrapperView;
19
+ import com.klarna.mobile.sdk.reactnative.common.ui.YogaLayoutHelper;
20
+ import com.klarna.mobile.sdk.reactnative.spec.RNKlarnaOSMViewSpec;
21
+ import com.klarna.mobile.sdk.api.KlarnaRegion;
22
+ import com.klarna.mobile.sdk.api.KlarnaEnvironment;
23
+ import com.klarna.mobile.sdk.api.KlarnaTextStyleConfiguration;
24
+ import com.klarna.mobile.sdk.api.KlarnaTheme;
25
+ import com.klarna.mobile.sdk.api.osm.KlarnaOSMStyleConfiguration;
26
+
27
+ import com.facebook.react.uimanager.PixelUtil;
28
+
29
+ import java.util.Map;
30
+ import java.util.WeakHashMap;
31
+
32
+ public class KlarnaOSMViewManager extends RNKlarnaOSMViewSpec<WrapperView<KlarnaOSMView>> {
33
+
34
+ public static final String KLARNA_OSM_VIEW_REACT_CLASS = "RNKlarnaOSMView";
35
+ public static final String COMMAND_RENDER_OSM = "render";
36
+
37
+ private final Map<WrapperView<KlarnaOSMView>, EventDispatcher> viewToDispatcher;
38
+ private final KlarnaOSMViewEventSender eventSender;
39
+ private final KlarnaOSMViewEventHandler eventHandler;
40
+ private final Map<KlarnaOSMView, ViewTreeObserver.OnGlobalLayoutListener> layoutListeners = new WeakHashMap<>();
41
+ private final Map<KlarnaOSMView, String> pendingBackgroundColor = new WeakHashMap<>();
42
+ private final Map<KlarnaOSMView, String> pendingTextColor = new WeakHashMap<>();
43
+
44
+ public KlarnaOSMViewManager() {
45
+ super();
46
+ viewToDispatcher = new WeakHashMap<>();
47
+ eventSender = new KlarnaOSMViewEventSender(viewToDispatcher);
48
+ eventHandler = new KlarnaOSMViewEventHandler(eventSender);
49
+ }
50
+
51
+ @NonNull
52
+ @Override
53
+ public String getName() {
54
+ return KLARNA_OSM_VIEW_REACT_CLASS;
55
+ }
56
+
57
+ @NonNull
58
+ @Override
59
+ protected WrapperView<KlarnaOSMView> createViewInstance(@NonNull ThemedReactContext themedReactContext) {
60
+ Activity activity = themedReactContext.getCurrentActivity();
61
+ KlarnaOSMView osmView = new KlarnaOSMView(activity != null ? activity : themedReactContext);
62
+ osmView.setEventHandler(eventHandler);
63
+ if (activity != null) {
64
+ osmView.setHostActivity(activity);
65
+ }
66
+ WrapperView<KlarnaOSMView> view = new WrapperView<>(themedReactContext, null, osmView);
67
+
68
+ EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag((ReactContext) view.getContext(), view.getId());
69
+ viewToDispatcher.put(view, eventDispatcher);
70
+
71
+ view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
72
+ @Override
73
+ public void onViewAttachedToWindow(@NonNull View v) {
74
+ eventSender.sendOnOSMViewReadyEvent(osmView);
75
+ v.removeOnAttachStateChangeListener(this);
76
+ }
77
+
78
+ @Override
79
+ public void onViewDetachedFromWindow(@NonNull View v) {
80
+ removeLayoutListener(osmView);
81
+ }
82
+ });
83
+
84
+ return view;
85
+ }
86
+
87
+ @Override
88
+ public void onDropViewInstance(@NonNull WrapperView<KlarnaOSMView> view) {
89
+ removeLayoutListener(view.getView());
90
+ viewToDispatcher.remove(view);
91
+ super.onDropViewInstance(view);
92
+ }
93
+
94
+ private void removeLayoutListener(@Nullable KlarnaOSMView osmView) {
95
+ if (osmView == null) {
96
+ return;
97
+ }
98
+ ViewTreeObserver.OnGlobalLayoutListener listener = layoutListeners.remove(osmView);
99
+ if (listener != null) {
100
+ osmView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
101
+ }
102
+ }
103
+
104
+ @Nullable
105
+ @Override
106
+ public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
107
+ return eventSender.getExportedCustomDirectEventTypeConstants();
108
+ }
109
+
110
+ @Override
111
+ public void receiveCommand(@NonNull WrapperView<KlarnaOSMView> root, String commandId, @Nullable ReadableArray args) {
112
+ if (COMMAND_RENDER_OSM.equals(commandId)) {
113
+ render(root);
114
+ }
115
+ }
116
+
117
+ @ReactProp(name = "clientId")
118
+ @Override
119
+ public void setClientId(WrapperView<KlarnaOSMView> view, @Nullable String value) {
120
+ KlarnaOSMView osmView = view.getView();
121
+ if (osmView != null && value != null && !value.isEmpty()) {
122
+ osmView.setClientId(value);
123
+ }
124
+ }
125
+
126
+ @ReactProp(name = "placementKey")
127
+ @Override
128
+ public void setPlacementKey(WrapperView<KlarnaOSMView> view, @Nullable String value) {
129
+ KlarnaOSMView osmView = view.getView();
130
+ if (osmView != null && value != null && !value.isEmpty()) {
131
+ osmView.setPlacementKey(value);
132
+ }
133
+ }
134
+
135
+ @ReactProp(name = "locale")
136
+ @Override
137
+ public void setLocale(WrapperView<KlarnaOSMView> view, @Nullable String value) {
138
+ KlarnaOSMView osmView = view.getView();
139
+ if (osmView != null && value != null && !value.isEmpty()) {
140
+ osmView.setLocale(value);
141
+ }
142
+ }
143
+
144
+ @ReactProp(name = "purchaseAmount")
145
+ @Override
146
+ public void setPurchaseAmount(WrapperView<KlarnaOSMView> view, @Nullable String value) {
147
+ KlarnaOSMView osmView = view.getView();
148
+ if (osmView != null && value != null && !value.isEmpty()) {
149
+ try {
150
+ long amount = Long.parseLong(value);
151
+ osmView.setPurchaseAmount(amount);
152
+ } catch (NumberFormatException e) {
153
+ eventSender.sendError(osmView, "InvalidPurchaseAmount",
154
+ "Invalid purchaseAmount: '" + value + "'. Expected a numeric string.", false);
155
+ }
156
+ }
157
+ }
158
+
159
+ @ReactProp(name = "environment")
160
+ @Override
161
+ public void setEnvironment(WrapperView<KlarnaOSMView> view, @Nullable String value) {
162
+ KlarnaOSMView osmView = view.getView();
163
+ if (osmView != null && value != null && !value.isEmpty()) {
164
+ switch (value) {
165
+ case "playground":
166
+ osmView.setEnvironment(KlarnaEnvironment.PLAYGROUND);
167
+ break;
168
+ case "production":
169
+ osmView.setEnvironment(KlarnaEnvironment.PRODUCTION);
170
+ break;
171
+ case "staging":
172
+ osmView.setEnvironment(KlarnaEnvironment.STAGING);
173
+ break;
174
+ default:
175
+ break;
176
+ }
177
+ }
178
+ }
179
+
180
+ @ReactProp(name = "region")
181
+ @Override
182
+ public void setRegion(WrapperView<KlarnaOSMView> view, @Nullable String value) {
183
+ KlarnaOSMView osmView = view.getView();
184
+ if (osmView != null && value != null && !value.isEmpty()) {
185
+ switch (value) {
186
+ case "eu":
187
+ osmView.setRegion(KlarnaRegion.EU);
188
+ break;
189
+ case "na":
190
+ osmView.setRegion(KlarnaRegion.NA);
191
+ break;
192
+ case "oc":
193
+ osmView.setRegion(KlarnaRegion.OC);
194
+ break;
195
+ default:
196
+ break;
197
+ }
198
+ }
199
+ }
200
+
201
+ @ReactProp(name = "theme")
202
+ @Override
203
+ public void setTheme(WrapperView<KlarnaOSMView> view, @Nullable String value) {
204
+ KlarnaOSMView osmView = view.getView();
205
+ if (osmView != null && value != null && !value.isEmpty()) {
206
+ switch (value) {
207
+ case "light":
208
+ osmView.setTheme(KlarnaTheme.LIGHT);
209
+ break;
210
+ case "dark":
211
+ osmView.setTheme(KlarnaTheme.DARK);
212
+ break;
213
+ case "automatic":
214
+ osmView.setTheme(KlarnaTheme.AUTOMATIC);
215
+ break;
216
+ default:
217
+ break;
218
+ }
219
+ }
220
+ }
221
+
222
+ @ReactProp(name = "backgroundColor")
223
+ @Override
224
+ public void setBackgroundColor(WrapperView<KlarnaOSMView> view, @Nullable String value) {
225
+ KlarnaOSMView osmView = view.getView();
226
+ if (osmView != null) {
227
+ pendingBackgroundColor.put(osmView, value);
228
+ updateStyleConfiguration(osmView);
229
+ }
230
+ }
231
+
232
+ @ReactProp(name = "textColor")
233
+ @Override
234
+ public void setTextColor(WrapperView<KlarnaOSMView> view, @Nullable String value) {
235
+ KlarnaOSMView osmView = view.getView();
236
+ if (osmView != null) {
237
+ pendingTextColor.put(osmView, value);
238
+ updateStyleConfiguration(osmView);
239
+ }
240
+ }
241
+
242
+ @ReactProp(name = "returnUrl")
243
+ @Override
244
+ public void setReturnUrl(WrapperView<KlarnaOSMView> view, @Nullable String value) {
245
+ // No need to do anything here because KlarnaOSMView ignores the return URL set by merchant and uses an internally created return URL.
246
+ }
247
+
248
+ @Override
249
+ public void render(WrapperView<KlarnaOSMView> view) {
250
+ KlarnaOSMView osmView = view.getView();
251
+ if (osmView != null) {
252
+ ReactContext reactContext = (ReactContext) view.getContext();
253
+ Activity activity = reactContext.getCurrentActivity();
254
+ if (activity != null) {
255
+ osmView.setHostActivity(activity);
256
+ }
257
+ osmView.render(error -> {
258
+ if (error != null) {
259
+ eventSender.sendKlarnaMobileSDKError(osmView, error);
260
+ } else {
261
+ removeLayoutListener(osmView);
262
+ addLayoutListenerForMeasure(osmView, view);
263
+ }
264
+ });
265
+ }
266
+ }
267
+
268
+ private boolean tryMeasureContent(KlarnaOSMView osmView, WrapperView<KlarnaOSMView> wrapperView) {
269
+ if (osmView.getChildCount() > 0 && wrapperView.getWidth() > 0) {
270
+ View child = osmView.getChildAt(0);
271
+ YogaLayoutHelper.measureAndLayout(wrapperView, child);
272
+ int contentHeight = child.getMeasuredHeight();
273
+ if (contentHeight > 0) {
274
+ int contentHeightDp = Math.round(PixelUtil.toDIPFromPixel(contentHeight));
275
+ eventSender.sendOnResizedEvent(osmView, contentHeightDp);
276
+ return true;
277
+ }
278
+ }
279
+ return false;
280
+ }
281
+
282
+ private void updateStyleConfiguration(KlarnaOSMView osmView) {
283
+ String bgColorStr = pendingBackgroundColor.get(osmView);
284
+ String txtColorStr = pendingTextColor.get(osmView);
285
+
286
+ Integer bgColor = parseColor(bgColorStr);
287
+ Integer txtColor = parseColor(txtColorStr);
288
+
289
+ if (bgColor != null || txtColor != null) {
290
+ KlarnaOSMStyleConfiguration.Builder builder = new KlarnaOSMStyleConfiguration.Builder();
291
+ if (bgColor != null) {
292
+ builder.setBackgroundColor(bgColor);
293
+ }
294
+ if (txtColor != null) {
295
+ KlarnaTextStyleConfiguration textStyle = new KlarnaTextStyleConfiguration.Builder()
296
+ .setTextColor(txtColor)
297
+ .build();
298
+ builder.setTextStyle(textStyle);
299
+ }
300
+ osmView.setStyleConfiguration(builder.build());
301
+ } else {
302
+ osmView.setStyleConfiguration(null);
303
+ }
304
+ }
305
+
306
+ @Nullable
307
+ private Integer parseColor(@Nullable String colorString) {
308
+ if (colorString == null || colorString.isEmpty()) {
309
+ return null;
310
+ }
311
+ try {
312
+ return android.graphics.Color.parseColor(colorString.startsWith("#") ? colorString : "#" + colorString);
313
+ } catch (IllegalArgumentException e) {
314
+ return null;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Adds a ViewTreeObserver listener to measure content when layout is ready.
320
+ * The listener automatically removes itself after successful measurement.
321
+ */
322
+ private void addLayoutListenerForMeasure(KlarnaOSMView osmView, WrapperView<KlarnaOSMView> wrapperView) {
323
+ // Try measuring on the next frame — content may already be ready and no further
324
+ // layout event would fire, so the ViewTreeObserver alone is not sufficient.
325
+ osmView.post(() -> {
326
+ if (!osmView.isAttachedToWindow()) {
327
+ return;
328
+ }
329
+ if (tryMeasureContent(osmView, wrapperView)) {
330
+ removeLayoutListener(osmView);
331
+ }
332
+ });
333
+
334
+ ViewTreeObserver.OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
335
+ @Override
336
+ public void onGlobalLayout() {
337
+ if (!osmView.isAttachedToWindow()) {
338
+ removeThisListener();
339
+ return;
340
+ }
341
+
342
+ if (tryMeasureContent(osmView, wrapperView)) {
343
+ removeThisListener();
344
+ }
345
+ }
346
+
347
+ private void removeThisListener() {
348
+ layoutListeners.remove(osmView);
349
+ osmView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
350
+ }
351
+ };
352
+
353
+ layoutListeners.put(osmView, listener);
354
+ osmView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
355
+ }
356
+ }
@@ -0,0 +1,31 @@
1
+ package com.klarna.mobile.sdk.reactnative.spec;
2
+
3
+ import android.view.View;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
7
+
8
+ import com.facebook.react.bridge.ReadableArray;
9
+ import com.facebook.react.uimanager.SimpleViewManager;
10
+ import com.facebook.react.uimanager.ViewManagerDelegate;
11
+ import com.facebook.react.viewmanagers.RNKlarnaExpressCheckoutViewManagerDelegate;
12
+ import com.facebook.react.viewmanagers.RNKlarnaExpressCheckoutViewManagerInterface;
13
+
14
+ public abstract class RNKlarnaExpressCheckoutViewSpec<T extends View> extends SimpleViewManager<T> implements RNKlarnaExpressCheckoutViewManagerInterface<T> {
15
+ private final ViewManagerDelegate<T> mDelegate;
16
+
17
+ public RNKlarnaExpressCheckoutViewSpec() {
18
+ mDelegate = new RNKlarnaExpressCheckoutViewManagerDelegate<>(this);
19
+ }
20
+
21
+ @Nullable
22
+ @Override
23
+ protected ViewManagerDelegate<T> getDelegate() {
24
+ return mDelegate;
25
+ }
26
+
27
+ @Override
28
+ public void receiveCommand(@NonNull T root, String commandId, @Nullable ReadableArray args) {
29
+ mDelegate.receiveCommand(root, commandId, args);
30
+ }
31
+ }
@@ -0,0 +1,31 @@
1
+ package com.klarna.mobile.sdk.reactnative.spec;
2
+
3
+ import android.view.View;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
7
+
8
+ import com.facebook.react.bridge.ReadableArray;
9
+ import com.facebook.react.uimanager.SimpleViewManager;
10
+ import com.facebook.react.uimanager.ViewManagerDelegate;
11
+ import com.facebook.react.viewmanagers.RNKlarnaOSMViewManagerDelegate;
12
+ import com.facebook.react.viewmanagers.RNKlarnaOSMViewManagerInterface;
13
+
14
+ public abstract class RNKlarnaOSMViewSpec<T extends View> extends SimpleViewManager<T> implements RNKlarnaOSMViewManagerInterface<T> {
15
+ private final ViewManagerDelegate<T> mDelegate;
16
+
17
+ public RNKlarnaOSMViewSpec() {
18
+ mDelegate = new RNKlarnaOSMViewManagerDelegate<>(this);
19
+ }
20
+
21
+ @Nullable
22
+ @Override
23
+ protected ViewManagerDelegate<T> getDelegate() {
24
+ return mDelegate;
25
+ }
26
+
27
+ @Override
28
+ public void receiveCommand(@NonNull T root, String commandId, @Nullable ReadableArray args) {
29
+ mDelegate.receiveCommand(root, commandId, args);
30
+ }
31
+ }
@@ -0,0 +1,36 @@
1
+ package com.klarna.mobile.sdk.reactnative.spec;
2
+
3
+ import android.view.View;
4
+
5
+ import androidx.annotation.Nullable;
6
+
7
+ import com.facebook.react.uimanager.SimpleViewManager;
8
+
9
+ public abstract class RNKlarnaExpressCheckoutViewSpec<T extends View> extends SimpleViewManager<T> {
10
+
11
+ public abstract void setSessionType(T view, @Nullable String value);
12
+
13
+ public abstract void setClientId(T view, @Nullable String value);
14
+
15
+ public abstract void setClientToken(T view, @Nullable String value);
16
+
17
+ public abstract void setLocale(T view, @Nullable String value);
18
+
19
+ public abstract void setEnvironment(T view, @Nullable String value);
20
+
21
+ public abstract void setRegion(T view, @Nullable String value);
22
+
23
+ public abstract void setReturnUrl(T view, @Nullable String value);
24
+
25
+ public abstract void setTheme(T view, @Nullable String value);
26
+
27
+ public abstract void setShape(T view, @Nullable String value);
28
+
29
+ public abstract void setButtonStyle(T view, @Nullable String value);
30
+
31
+ public abstract void setAutoFinalize(T view, boolean value);
32
+
33
+ public abstract void setCollectShippingAddress(T view, boolean value);
34
+
35
+ public abstract void setSessionData(T view, @Nullable String value);
36
+ }
@@ -0,0 +1,32 @@
1
+ package com.klarna.mobile.sdk.reactnative.spec;
2
+
3
+ import android.view.View;
4
+
5
+ import androidx.annotation.Nullable;
6
+
7
+ import com.facebook.react.uimanager.SimpleViewManager;
8
+
9
+ public abstract class RNKlarnaOSMViewSpec<T extends View> extends SimpleViewManager<T> {
10
+
11
+ public abstract void setClientId(T view, @Nullable String value);
12
+
13
+ public abstract void setPlacementKey(T view, @Nullable String value);
14
+
15
+ public abstract void setLocale(T view, @Nullable String value);
16
+
17
+ public abstract void setPurchaseAmount(T view, @Nullable String value);
18
+
19
+ public abstract void setEnvironment(T view, @Nullable String value);
20
+
21
+ public abstract void setRegion(T view, @Nullable String value);
22
+
23
+ public abstract void setTheme(T view, @Nullable String value);
24
+
25
+ public abstract void setBackgroundColor(T view, @Nullable String value);
26
+
27
+ public abstract void setTextColor(T view, @Nullable String value);
28
+
29
+ public abstract void setReturnUrl(T view, @Nullable String value);
30
+
31
+ public abstract void render(T view);
32
+ }
@@ -8,9 +8,11 @@ import com.facebook.react.bridge.NativeModule;
8
8
  import com.facebook.react.bridge.ReactApplicationContext;
9
9
  import com.facebook.react.uimanager.ViewManager;
10
10
  import com.klarna.mobile.sdk.reactnative.checkout.KlarnaCheckoutViewManager;
11
+ import com.klarna.mobile.sdk.reactnative.expresscheckout.KlarnaExpressCheckoutViewManager;
11
12
  import com.klarna.mobile.sdk.reactnative.payments.KlarnaPaymentViewManager;
12
13
  import com.klarna.mobile.sdk.reactnative.spec.RNKlarnaSignInModuleSpec;
13
14
  import com.klarna.mobile.sdk.reactnative.standalonewebview.KlarnaStandaloneWebViewManager;
15
+ import com.klarna.mobile.sdk.reactnative.osm.KlarnaOSMViewManager;
14
16
 
15
17
  import org.junit.Assert;
16
18
  import org.junit.Before;
@@ -48,9 +50,11 @@ public class KlarnaMobileSDKPackageTest {
48
50
  List<ViewManager> nativeModules = paymentViewPackage.createViewManagers(reactContext);
49
51
  Assert.assertNotNull(nativeModules);
50
52
  Assert.assertFalse(nativeModules.isEmpty());
51
- Assert.assertEquals(3, nativeModules.size());
53
+ Assert.assertEquals(5, nativeModules.size());
52
54
  Assert.assertTrue(nativeModules.get(0) instanceof KlarnaPaymentViewManager);
53
55
  Assert.assertTrue(nativeModules.get(1) instanceof KlarnaStandaloneWebViewManager);
54
56
  Assert.assertTrue(nativeModules.get(2) instanceof KlarnaCheckoutViewManager);
57
+ Assert.assertTrue(nativeModules.get(3) instanceof KlarnaExpressCheckoutViewManager);
58
+ Assert.assertTrue(nativeModules.get(4) instanceof KlarnaOSMViewManager);
55
59
  }
56
60
  }
@@ -0,0 +1,79 @@
1
+ package com.klarna.mobile.sdk.reactnative.common.ui;
2
+
3
+ import android.view.View;
4
+ import android.view.ViewGroup;
5
+
6
+ import org.junit.Test;
7
+ import org.junit.runner.RunWith;
8
+ import org.mockito.Mockito;
9
+ import org.robolectric.RobolectricTestRunner;
10
+
11
+ @RunWith(RobolectricTestRunner.class)
12
+ public class YogaLayoutHelperTest {
13
+
14
+ // region measureAndLayout
15
+
16
+ @Test
17
+ public void testMeasureAndLayout_measuresChildWithExactWidthAndUnspecifiedHeight() {
18
+ ViewGroup wrapper = Mockito.mock(ViewGroup.class);
19
+ View child = Mockito.mock(View.class);
20
+
21
+ Mockito.when(wrapper.getMeasuredWidth()).thenReturn(400);
22
+ Mockito.when(child.getMeasuredHeight()).thenReturn(120);
23
+
24
+ YogaLayoutHelper.measureAndLayout(wrapper, child);
25
+
26
+ Mockito.verify(child).measure(
27
+ View.MeasureSpec.makeMeasureSpec(400, View.MeasureSpec.EXACTLY),
28
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
29
+ );
30
+ Mockito.verify(child).layout(0, 0, 400, 120);
31
+ }
32
+
33
+ @Test
34
+ public void testMeasureAndLayout_skipsWhenWrapperWidthIsZero() {
35
+ ViewGroup wrapper = Mockito.mock(ViewGroup.class);
36
+ View child = Mockito.mock(View.class);
37
+
38
+ Mockito.when(wrapper.getMeasuredWidth()).thenReturn(0);
39
+
40
+ YogaLayoutHelper.measureAndLayout(wrapper, child);
41
+
42
+ Mockito.verify(child, Mockito.never()).measure(Mockito.anyInt(), Mockito.anyInt());
43
+ Mockito.verify(child, Mockito.never()).layout(
44
+ Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt());
45
+ }
46
+
47
+ @Test
48
+ public void testMeasureAndLayout_skipsWhenWrapperWidthIsNegative() {
49
+ ViewGroup wrapper = Mockito.mock(ViewGroup.class);
50
+ View child = Mockito.mock(View.class);
51
+
52
+ Mockito.when(wrapper.getMeasuredWidth()).thenReturn(-1);
53
+
54
+ YogaLayoutHelper.measureAndLayout(wrapper, child);
55
+
56
+ Mockito.verify(child, Mockito.never()).measure(Mockito.anyInt(), Mockito.anyInt());
57
+ Mockito.verify(child, Mockito.never()).layout(
58
+ Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt(), Mockito.anyInt());
59
+ }
60
+
61
+ @Test
62
+ public void testMeasureAndLayout_handlesZeroChildHeight() {
63
+ ViewGroup wrapper = Mockito.mock(ViewGroup.class);
64
+ View child = Mockito.mock(View.class);
65
+
66
+ Mockito.when(wrapper.getMeasuredWidth()).thenReturn(300);
67
+ Mockito.when(child.getMeasuredHeight()).thenReturn(0);
68
+
69
+ YogaLayoutHelper.measureAndLayout(wrapper, child);
70
+
71
+ Mockito.verify(child).measure(
72
+ View.MeasureSpec.makeMeasureSpec(300, View.MeasureSpec.EXACTLY),
73
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
74
+ );
75
+ Mockito.verify(child).layout(0, 0, 300, 0);
76
+ }
77
+
78
+ // endregion
79
+ }