react-native-klarna-inapp-sdk 2.4.9 → 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.
- package/README.md +2 -1
- package/android/build.gradle +3 -1
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/KlarnaMobileSDKPackage.java +5 -1
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/checkout/KlarnaCheckoutViewManager.java +4 -10
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/WebViewResizeObserver.java +28 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/event/ComponentEventSender.java +5 -1
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/ResizeObserverWrapperView.java +2 -2
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/WrapperView.java +2 -2
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/common/ui/YogaLayoutHelper.java +39 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewCallback.kt +30 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewEventSender.kt +80 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewManager.java +544 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandler.java +30 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSender.java +64 -0
- package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewManager.java +356 -0
- package/android/src/newarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaExpressCheckoutViewSpec.java +31 -0
- package/android/src/newarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaOSMViewSpec.java +31 -0
- package/android/src/oldarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaExpressCheckoutViewSpec.java +36 -0
- package/android/src/oldarch/java/com/klarna/mobile/sdk/reactnative/spec/RNKlarnaOSMViewSpec.java +32 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/KlarnaMobileSDKPackageTest.java +5 -1
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/common/ui/YogaLayoutHelperTest.java +79 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/expresscheckout/KlarnaExpressCheckoutViewManagerTest.java +259 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandlerTest.java +136 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSenderTest.java +184 -0
- package/android/src/test/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewManagerTest.java +385 -0
- package/ios/RNKlarnaMobileSDK.xcodeproj/project.pbxproj +55 -1
- package/ios/Sources/KlarnaExpressCheckoutViewManager.h +5 -0
- package/ios/Sources/KlarnaExpressCheckoutViewManager.mm +39 -0
- package/ios/Sources/KlarnaOSMViewManager.h +5 -0
- package/ios/Sources/KlarnaOSMViewManager.mm +59 -0
- package/ios/Sources/view/KlarnaExpressCheckoutButtonDelegateProxy.swift +145 -0
- package/ios/Sources/view/KlarnaExpressCheckoutViewWrapper.h +41 -0
- package/ios/Sources/view/KlarnaOSMViewWrapper.h +48 -0
- package/ios/Sources/view/newarch/KlarnaExpressCheckoutViewWrapper.mm +228 -0
- package/ios/Sources/view/newarch/KlarnaOSMViewWrapper.mm +294 -0
- package/ios/Sources/view/newarch/KlarnaStandaloneWebViewWrapper.mm +20 -13
- package/ios/Sources/view/oldarch/KlarnaExpressCheckoutViewWrapper.mm +222 -0
- package/ios/Sources/view/oldarch/KlarnaOSMViewWrapper.mm +273 -0
- package/ios/Sources/view/oldarch/KlarnaStandaloneWebViewWrapper.mm +11 -13
- package/ios/Tests/KlarnaExpressCheckoutViewWrapperTests.m +391 -0
- package/ios/Tests/KlarnaOSMViewWrapperTests.m +450 -0
- package/ios/Tests/KlarnaStandaloneWebViewWrapperTests.m +105 -0
- package/lib/commonjs/KlarnaExpressCheckoutView.js +75 -0
- package/lib/commonjs/KlarnaExpressCheckoutView.js.map +1 -0
- package/lib/commonjs/KlarnaOSMView.js +94 -0
- package/lib/commonjs/KlarnaOSMView.js.map +1 -0
- package/lib/commonjs/index.js +99 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/specs/KlarnaExpressCheckoutViewNativeComponent.js +11 -0
- package/lib/commonjs/specs/KlarnaExpressCheckoutViewNativeComponent.js.map +1 -0
- package/lib/commonjs/specs/KlarnaOSMViewNativeComponent.js +16 -0
- package/lib/commonjs/specs/KlarnaOSMViewNativeComponent.js.map +1 -0
- package/lib/commonjs/types/button/KlarnaButtonShape.js +14 -0
- package/lib/commonjs/types/button/KlarnaButtonShape.js.map +1 -0
- package/lib/commonjs/types/button/KlarnaButtonStyle.js +13 -0
- package/lib/commonjs/types/button/KlarnaButtonStyle.js.map +1 -0
- package/lib/commonjs/types/button/KlarnaButtonTheme.js +14 -0
- package/lib/commonjs/types/button/KlarnaButtonTheme.js.map +1 -0
- package/lib/commonjs/types/common/KlarnaExpressCheckoutSessionOptions.js +9 -0
- package/lib/commonjs/types/common/KlarnaExpressCheckoutSessionOptions.js.map +1 -0
- package/lib/commonjs/types/common/KlarnaOSMPlacementKey.js +2 -0
- package/lib/commonjs/types/common/KlarnaOSMPlacementKey.js.map +1 -0
- package/lib/commonjs/types/common/KlarnaTheme.js +14 -0
- package/lib/commonjs/types/common/KlarnaTheme.js.map +1 -0
- package/lib/module/KlarnaExpressCheckoutView.js +64 -0
- package/lib/module/KlarnaExpressCheckoutView.js.map +1 -0
- package/lib/module/KlarnaOSMView.js +84 -0
- package/lib/module/KlarnaOSMView.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/specs/KlarnaExpressCheckoutViewNativeComponent.js +3 -0
- package/lib/module/specs/KlarnaExpressCheckoutViewNativeComponent.js.map +1 -0
- package/lib/module/specs/KlarnaOSMViewNativeComponent.js +7 -0
- package/lib/module/specs/KlarnaOSMViewNativeComponent.js.map +1 -0
- package/lib/module/types/button/KlarnaButtonShape.js +7 -0
- package/lib/module/types/button/KlarnaButtonShape.js.map +1 -0
- package/lib/module/types/button/KlarnaButtonStyle.js +6 -0
- package/lib/module/types/button/KlarnaButtonStyle.js.map +1 -0
- package/lib/module/types/button/KlarnaButtonTheme.js +7 -0
- package/lib/module/types/button/KlarnaButtonTheme.js.map +1 -0
- package/lib/module/types/common/KlarnaExpressCheckoutSessionOptions.js +2 -0
- package/lib/module/types/common/KlarnaExpressCheckoutSessionOptions.js.map +1 -0
- package/lib/module/types/common/KlarnaOSMPlacementKey.js +2 -0
- package/lib/module/types/common/KlarnaOSMPlacementKey.js.map +1 -0
- package/lib/module/types/common/KlarnaTheme.js +7 -0
- package/lib/module/types/common/KlarnaTheme.js.map +1 -0
- package/lib/typescript/KlarnaExpressCheckoutView.d.ts +47 -0
- package/lib/typescript/KlarnaExpressCheckoutView.d.ts.map +1 -0
- package/lib/typescript/KlarnaOSMView.d.ts +36 -0
- package/lib/typescript/KlarnaOSMView.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +9 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/specs/KlarnaExpressCheckoutViewNativeComponent.d.ts +45 -0
- package/lib/typescript/specs/KlarnaExpressCheckoutViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/specs/KlarnaOSMViewNativeComponent.d.ts +35 -0
- package/lib/typescript/specs/KlarnaOSMViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/types/button/KlarnaButtonShape.d.ts +6 -0
- package/lib/typescript/types/button/KlarnaButtonShape.d.ts.map +1 -0
- package/lib/typescript/types/button/KlarnaButtonStyle.d.ts +5 -0
- package/lib/typescript/types/button/KlarnaButtonStyle.d.ts.map +1 -0
- package/lib/typescript/types/button/KlarnaButtonTheme.d.ts +6 -0
- package/lib/typescript/types/button/KlarnaButtonTheme.d.ts.map +1 -0
- package/lib/typescript/types/common/KlarnaExpressCheckoutSessionOptions.d.ts +10 -0
- package/lib/typescript/types/common/KlarnaExpressCheckoutSessionOptions.d.ts.map +1 -0
- package/lib/typescript/types/common/KlarnaOSMPlacementKey.d.ts +2 -0
- package/lib/typescript/types/common/KlarnaOSMPlacementKey.d.ts.map +1 -0
- package/lib/typescript/types/common/KlarnaTheme.d.ts +6 -0
- package/lib/typescript/types/common/KlarnaTheme.d.ts.map +1 -0
- package/package.json +8 -3
- package/react-native-klarna-inapp-sdk.podspec +2 -1
- package/src/KlarnaExpressCheckoutView.tsx +169 -0
- package/src/KlarnaOSMView.tsx +157 -0
- package/src/index.tsx +9 -0
- package/src/specs/KlarnaExpressCheckoutViewNativeComponent.ts +66 -0
- package/src/specs/KlarnaOSMViewNativeComponent.ts +50 -0
- package/src/types/button/KlarnaButtonShape.ts +5 -0
- package/src/types/button/KlarnaButtonStyle.ts +4 -0
- package/src/types/button/KlarnaButtonTheme.ts +5 -0
- package/src/types/common/KlarnaExpressCheckoutSessionOptions.ts +13 -0
- package/src/types/common/KlarnaOSMPlacementKey.ts +3 -0
- package/src/types/common/KlarnaTheme.ts +5 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
package com.klarna.mobile.sdk.reactnative.expresscheckout;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.graphics.Color;
|
|
6
|
+
import android.util.Log;
|
|
7
|
+
import android.view.Choreographer;
|
|
8
|
+
import android.view.View;
|
|
9
|
+
import android.view.ViewGroup;
|
|
10
|
+
import android.widget.FrameLayout;
|
|
11
|
+
|
|
12
|
+
import androidx.annotation.NonNull;
|
|
13
|
+
import androidx.annotation.Nullable;
|
|
14
|
+
|
|
15
|
+
import com.facebook.react.bridge.ReactContext;
|
|
16
|
+
import com.facebook.react.uimanager.ThemedReactContext;
|
|
17
|
+
import com.facebook.react.uimanager.UIManagerHelper;
|
|
18
|
+
import com.facebook.react.uimanager.annotations.ReactProp;
|
|
19
|
+
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
20
|
+
import com.klarna.mobile.sdk.api.KlarnaEnvironment;
|
|
21
|
+
import com.klarna.mobile.sdk.api.KlarnaRegion;
|
|
22
|
+
import com.klarna.mobile.sdk.api.button.KlarnaButtonShape;
|
|
23
|
+
import com.klarna.mobile.sdk.api.button.KlarnaButtonStyle;
|
|
24
|
+
import com.klarna.mobile.sdk.api.button.KlarnaButtonTheme;
|
|
25
|
+
import com.klarna.mobile.sdk.api.expresscheckout.KlarnaExpressCheckoutButton;
|
|
26
|
+
import com.klarna.mobile.sdk.api.expresscheckout.KlarnaExpressCheckoutButtonOptions;
|
|
27
|
+
import com.klarna.mobile.sdk.api.expresscheckout.KlarnaExpressCheckoutButtonStyleConfiguration;
|
|
28
|
+
import com.klarna.mobile.sdk.api.expresscheckout.KlarnaExpressCheckoutSessionOptions;
|
|
29
|
+
import com.klarna.mobile.sdk.reactnative.common.ui.YogaLayoutHelper;
|
|
30
|
+
import com.klarna.mobile.sdk.reactnative.spec.RNKlarnaExpressCheckoutViewSpec;
|
|
31
|
+
|
|
32
|
+
import java.util.Map;
|
|
33
|
+
import java.util.WeakHashMap;
|
|
34
|
+
|
|
35
|
+
public class KlarnaExpressCheckoutViewManager extends RNKlarnaExpressCheckoutViewSpec<FrameLayout> {
|
|
36
|
+
|
|
37
|
+
public static final String REACT_CLASS = "RNKlarnaExpressCheckoutView";
|
|
38
|
+
|
|
39
|
+
private final Map<FrameLayout, EventDispatcher> viewToDispatcher;
|
|
40
|
+
private final KlarnaExpressCheckoutViewEventSender eventSender;
|
|
41
|
+
|
|
42
|
+
// Stored props per view.
|
|
43
|
+
private final Map<FrameLayout, ExpressCheckoutProps> viewToProps;
|
|
44
|
+
|
|
45
|
+
// Tracks whether button creation has been scheduled/completed for this view instance.
|
|
46
|
+
private final Map<FrameLayout, Boolean> pendingCreate;
|
|
47
|
+
private final Map<FrameLayout, Boolean> isButtonCreated;
|
|
48
|
+
|
|
49
|
+
// Monotonic generation per view used to invalidate stale async callbacks.
|
|
50
|
+
private final Map<FrameLayout, Integer> viewToGeneration;
|
|
51
|
+
|
|
52
|
+
// Stores the layout change listener per view so it can be removed on teardown.
|
|
53
|
+
private final Map<FrameLayout, View.OnLayoutChangeListener> viewToLayoutListener;
|
|
54
|
+
|
|
55
|
+
// Strong reference to the callback — the SDK stores it via WeakReference internally,
|
|
56
|
+
// so without this the callback would be garbage collected.
|
|
57
|
+
private final Map<FrameLayout, KlarnaExpressCheckoutViewCallback> viewToCallback;
|
|
58
|
+
|
|
59
|
+
public KlarnaExpressCheckoutViewManager() {
|
|
60
|
+
super();
|
|
61
|
+
viewToDispatcher = new WeakHashMap<>();
|
|
62
|
+
viewToProps = new WeakHashMap<>();
|
|
63
|
+
pendingCreate = new WeakHashMap<>();
|
|
64
|
+
isButtonCreated = new WeakHashMap<>();
|
|
65
|
+
viewToGeneration = new WeakHashMap<>();
|
|
66
|
+
viewToLayoutListener = new WeakHashMap<>();
|
|
67
|
+
viewToCallback = new WeakHashMap<>();
|
|
68
|
+
eventSender = new KlarnaExpressCheckoutViewEventSender(viewToDispatcher);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@NonNull
|
|
72
|
+
@Override
|
|
73
|
+
public String getName() {
|
|
74
|
+
return REACT_CLASS;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@NonNull
|
|
78
|
+
@Override
|
|
79
|
+
protected FrameLayout createViewInstance(@NonNull ThemedReactContext themedReactContext) {
|
|
80
|
+
FrameLayout view = new FrameLayout(themedReactContext);
|
|
81
|
+
view.setBackgroundColor(Color.TRANSPARENT);
|
|
82
|
+
|
|
83
|
+
EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(
|
|
84
|
+
(ReactContext) view.getContext(), view.getId());
|
|
85
|
+
viewToDispatcher.put(view, eventDispatcher);
|
|
86
|
+
viewToProps.put(view, new ExpressCheckoutProps());
|
|
87
|
+
viewToGeneration.put(view, 0);
|
|
88
|
+
|
|
89
|
+
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
|
90
|
+
@Override
|
|
91
|
+
public void onViewAttachedToWindow(@NonNull View v) {
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@Override
|
|
95
|
+
public void onViewDetachedFromWindow(@NonNull View v) {
|
|
96
|
+
tearDownButton(view);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
return view;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Nullable
|
|
104
|
+
@Override
|
|
105
|
+
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
|
106
|
+
return eventSender.getExportedCustomDirectEventTypeConstants();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// region ReactProp setters
|
|
110
|
+
// Each setter stores the value in ExpressCheckoutProps and schedules (re)creation.
|
|
111
|
+
// The creation is deferred to the next frame so React can finish batching prop updates.
|
|
112
|
+
|
|
113
|
+
@ReactProp(name = "sessionType")
|
|
114
|
+
@Override
|
|
115
|
+
public void setSessionType(FrameLayout view, @Nullable String value) {
|
|
116
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
117
|
+
if (props != null) {
|
|
118
|
+
props.sessionType = value;
|
|
119
|
+
scheduleButtonCreation(view);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@ReactProp(name = "clientId")
|
|
124
|
+
@Override
|
|
125
|
+
public void setClientId(FrameLayout view, @Nullable String value) {
|
|
126
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
127
|
+
if (props != null) {
|
|
128
|
+
props.clientId = value;
|
|
129
|
+
scheduleButtonCreation(view);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@ReactProp(name = "clientToken")
|
|
134
|
+
@Override
|
|
135
|
+
public void setClientToken(FrameLayout view, @Nullable String value) {
|
|
136
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
137
|
+
if (props != null) {
|
|
138
|
+
props.clientToken = value;
|
|
139
|
+
scheduleButtonCreation(view);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@ReactProp(name = "locale")
|
|
144
|
+
@Override
|
|
145
|
+
public void setLocale(FrameLayout view, @Nullable String value) {
|
|
146
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
147
|
+
if (props != null) {
|
|
148
|
+
props.locale = value;
|
|
149
|
+
scheduleButtonCreation(view);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@ReactProp(name = "environment")
|
|
154
|
+
@Override
|
|
155
|
+
public void setEnvironment(FrameLayout view, @Nullable String value) {
|
|
156
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
157
|
+
if (props != null) {
|
|
158
|
+
props.environment = value;
|
|
159
|
+
scheduleButtonCreation(view);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@ReactProp(name = "region")
|
|
164
|
+
@Override
|
|
165
|
+
public void setRegion(FrameLayout view, @Nullable String value) {
|
|
166
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
167
|
+
if (props != null) {
|
|
168
|
+
props.region = value;
|
|
169
|
+
scheduleButtonCreation(view);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@ReactProp(name = "returnUrl")
|
|
174
|
+
@Override
|
|
175
|
+
public void setReturnUrl(FrameLayout view, @Nullable String value) {
|
|
176
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
177
|
+
if (props != null) {
|
|
178
|
+
props.returnUrl = value;
|
|
179
|
+
scheduleButtonCreation(view);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@ReactProp(name = "theme")
|
|
184
|
+
@Override
|
|
185
|
+
public void setTheme(FrameLayout view, @Nullable String value) {
|
|
186
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
187
|
+
if (props != null) {
|
|
188
|
+
props.theme = value;
|
|
189
|
+
scheduleButtonCreation(view);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@ReactProp(name = "shape")
|
|
194
|
+
@Override
|
|
195
|
+
public void setShape(FrameLayout view, @Nullable String value) {
|
|
196
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
197
|
+
if (props != null) {
|
|
198
|
+
props.shape = value;
|
|
199
|
+
scheduleButtonCreation(view);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
@ReactProp(name = "buttonStyle")
|
|
204
|
+
@Override
|
|
205
|
+
public void setButtonStyle(FrameLayout view, @Nullable String value) {
|
|
206
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
207
|
+
if (props != null) {
|
|
208
|
+
props.buttonStyle = value;
|
|
209
|
+
scheduleButtonCreation(view);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@ReactProp(name = "autoFinalize")
|
|
214
|
+
@Override
|
|
215
|
+
public void setAutoFinalize(FrameLayout view, boolean value) {
|
|
216
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
217
|
+
if (props != null) {
|
|
218
|
+
props.autoFinalize = value;
|
|
219
|
+
scheduleButtonCreation(view);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
@ReactProp(name = "collectShippingAddress")
|
|
224
|
+
@Override
|
|
225
|
+
public void setCollectShippingAddress(FrameLayout view, boolean value) {
|
|
226
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
227
|
+
if (props != null) {
|
|
228
|
+
props.collectShippingAddress = value;
|
|
229
|
+
scheduleButtonCreation(view);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
@ReactProp(name = "sessionData")
|
|
234
|
+
@Override
|
|
235
|
+
public void setSessionData(FrameLayout view, @Nullable String value) {
|
|
236
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
237
|
+
if (props != null) {
|
|
238
|
+
props.sessionData = value;
|
|
239
|
+
scheduleButtonCreation(view);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// endregion
|
|
244
|
+
|
|
245
|
+
@Override
|
|
246
|
+
public void onDropViewInstance(@NonNull FrameLayout view) {
|
|
247
|
+
viewToDispatcher.remove(view);
|
|
248
|
+
viewToProps.remove(view);
|
|
249
|
+
pendingCreate.remove(view);
|
|
250
|
+
isButtonCreated.remove(view);
|
|
251
|
+
viewToGeneration.remove(view);
|
|
252
|
+
View.OnLayoutChangeListener listener = viewToLayoutListener.remove(view);
|
|
253
|
+
if (listener != null) {
|
|
254
|
+
view.removeOnLayoutChangeListener(listener);
|
|
255
|
+
}
|
|
256
|
+
viewToCallback.remove(view);
|
|
257
|
+
super.onDropViewInstance(view);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// region Button creation
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Schedules button creation for the next frame so React prop batches can settle first.
|
|
264
|
+
* Subsequent prop changes within the same frame coalesce into a single recreate pass.
|
|
265
|
+
*
|
|
266
|
+
* <p><b>Note:</b> The KlarnaExpressCheckoutButton SDK provides no setters for individual
|
|
267
|
+
* properties — all configuration is passed through the constructor. Therefore every prop
|
|
268
|
+
* change requires a full tear-down and recreation of the button. Merchants should avoid
|
|
269
|
+
* changing props (e.g. theme, sessionData) once the user has tapped the button and an
|
|
270
|
+
* authorization flow may be in progress, as the button will be removed from the hierarchy.
|
|
271
|
+
* The SDK authorization UI runs in its own Activity/BottomSheet, so in practice the
|
|
272
|
+
* underlying React component rarely re-renders during that flow.
|
|
273
|
+
*/
|
|
274
|
+
private void scheduleButtonCreation(@NonNull FrameLayout view) {
|
|
275
|
+
if (Boolean.TRUE.equals(pendingCreate.get(view))) {
|
|
276
|
+
return; // already scheduled
|
|
277
|
+
}
|
|
278
|
+
pendingCreate.put(view, Boolean.TRUE);
|
|
279
|
+
Choreographer.getInstance().postFrameCallback(frameTimeNanos -> {
|
|
280
|
+
pendingCreate.remove(view);
|
|
281
|
+
tearDownButton(view);
|
|
282
|
+
createButton(view);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private void tearDownButton(@NonNull FrameLayout view) {
|
|
287
|
+
// Remove the previous layout change listener to avoid stale references.
|
|
288
|
+
View.OnLayoutChangeListener oldListener = viewToLayoutListener.remove(view);
|
|
289
|
+
if (oldListener != null) {
|
|
290
|
+
view.removeOnLayoutChangeListener(oldListener);
|
|
291
|
+
}
|
|
292
|
+
view.removeAllViews();
|
|
293
|
+
isButtonCreated.remove(view);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private void createButton(@NonNull FrameLayout view) {
|
|
297
|
+
ExpressCheckoutProps props = viewToProps.get(view);
|
|
298
|
+
if (props == null) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
Context context = view.getContext();
|
|
303
|
+
Activity activity = null;
|
|
304
|
+
if (context instanceof ThemedReactContext) {
|
|
305
|
+
activity = ((ThemedReactContext) context).getCurrentActivity();
|
|
306
|
+
}
|
|
307
|
+
Context buttonContext = activity != null ? activity : context;
|
|
308
|
+
String sessionData = props.sessionData != null ? props.sessionData : "";
|
|
309
|
+
|
|
310
|
+
int generation = nextGeneration(view);
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
// Build session options based on the explicit sessionType.
|
|
314
|
+
KlarnaExpressCheckoutSessionOptions sessionOptions;
|
|
315
|
+
if ("clientToken".equals(props.sessionType)) {
|
|
316
|
+
sessionOptions = new KlarnaExpressCheckoutSessionOptions.ServerSideSession(
|
|
317
|
+
props.clientToken != null ? props.clientToken : "",
|
|
318
|
+
sessionData,
|
|
319
|
+
props.autoFinalize,
|
|
320
|
+
props.collectShippingAddress
|
|
321
|
+
);
|
|
322
|
+
} else {
|
|
323
|
+
sessionOptions = new KlarnaExpressCheckoutSessionOptions.ClientSideSession(
|
|
324
|
+
props.clientId != null ? props.clientId : "",
|
|
325
|
+
sessionData,
|
|
326
|
+
props.autoFinalize,
|
|
327
|
+
props.collectShippingAddress
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Build style configuration
|
|
332
|
+
KlarnaExpressCheckoutButtonStyleConfiguration styleConfig =
|
|
333
|
+
new KlarnaExpressCheckoutButtonStyleConfiguration(
|
|
334
|
+
parseTheme(props.theme),
|
|
335
|
+
parseShape(props.shape),
|
|
336
|
+
parseButtonStyle(props.buttonStyle)
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Build callback — store a strong reference so the SDK's WeakReference doesn't lose it.
|
|
340
|
+
KlarnaExpressCheckoutViewCallback callback =
|
|
341
|
+
new KlarnaExpressCheckoutViewCallback(view, eventSender);
|
|
342
|
+
viewToCallback.put(view, callback);
|
|
343
|
+
|
|
344
|
+
// Build options
|
|
345
|
+
KlarnaExpressCheckoutButtonOptions options = new KlarnaExpressCheckoutButtonOptions(
|
|
346
|
+
sessionOptions,
|
|
347
|
+
callback,
|
|
348
|
+
props.locale,
|
|
349
|
+
styleConfig,
|
|
350
|
+
parseEnvironment(props.environment),
|
|
351
|
+
parseRegion(props.region),
|
|
352
|
+
null, // KlarnaTheme
|
|
353
|
+
null, // resourceEndpoint
|
|
354
|
+
null // loggingLevel
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
KlarnaExpressCheckoutButton expressButton = new KlarnaExpressCheckoutButton(buttonContext, options);
|
|
358
|
+
|
|
359
|
+
view.addView(expressButton, new ViewGroup.LayoutParams(
|
|
360
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
361
|
+
ViewGroup.LayoutParams.WRAP_CONTENT
|
|
362
|
+
));
|
|
363
|
+
|
|
364
|
+
isButtonCreated.put(view, Boolean.TRUE);
|
|
365
|
+
|
|
366
|
+
// React Native's Yoga layout engine suppresses requestLayout() from child views,
|
|
367
|
+
// so the SDK button won't measure/layout correctly without a manual layout pass.
|
|
368
|
+
setupManualLayout(view, expressButton, generation);
|
|
369
|
+
|
|
370
|
+
// Send initial resize event after button renders.
|
|
371
|
+
// getHeight() returns pixels; React Native expects density-independent pixels (dp).
|
|
372
|
+
expressButton.post(() -> {
|
|
373
|
+
if (isCurrentGeneration(view, generation)) {
|
|
374
|
+
maybeSendResizeEvent(view, expressButton);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
} catch (Exception e) {
|
|
378
|
+
Log.e("KlarnaExpressCheckout", "Failed to create express checkout button", e);
|
|
379
|
+
eventSender.sendKlarnaExpressCheckoutError(
|
|
380
|
+
view,
|
|
381
|
+
"ButtonCreationError",
|
|
382
|
+
e.getMessage() != null ? e.getMessage() : "Unknown error during button creation",
|
|
383
|
+
true
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* React Native's Yoga layout engine suppresses requestLayout() from native child views.
|
|
390
|
+
* Schedule a manual measure+layout pass every frame until the button is laid out,
|
|
391
|
+
* and re-layout whenever the wrapper's bounds change.
|
|
392
|
+
*/
|
|
393
|
+
private void setupManualLayout(
|
|
394
|
+
@NonNull FrameLayout wrapper,
|
|
395
|
+
@NonNull KlarnaExpressCheckoutButton button,
|
|
396
|
+
int generation
|
|
397
|
+
) {
|
|
398
|
+
final int[] lastHeightPx = new int[]{-1};
|
|
399
|
+
final int[] stableFrameCount = new int[]{0};
|
|
400
|
+
Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() {
|
|
401
|
+
@Override
|
|
402
|
+
public void doFrame(long frameTimeNanos) {
|
|
403
|
+
if (!isCurrentGeneration(wrapper, generation) || !button.isAttachedToWindow()) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
YogaLayoutHelper.measureAndLayout(wrapper, button);
|
|
407
|
+
maybeSendResizeEvent(wrapper, button);
|
|
408
|
+
|
|
409
|
+
int currentHeightPx = button.getHeight();
|
|
410
|
+
if (currentHeightPx <= 0) {
|
|
411
|
+
stableFrameCount[0] = 0;
|
|
412
|
+
} else if (currentHeightPx == lastHeightPx[0]) {
|
|
413
|
+
stableFrameCount[0] = stableFrameCount[0] + 1;
|
|
414
|
+
} else {
|
|
415
|
+
stableFrameCount[0] = 0;
|
|
416
|
+
}
|
|
417
|
+
lastHeightPx[0] = currentHeightPx;
|
|
418
|
+
|
|
419
|
+
// Keep posting while content is still unresolved or changing across frames.
|
|
420
|
+
if (currentHeightPx <= 0 || stableFrameCount[0] < 3) {
|
|
421
|
+
Choreographer.getInstance().postFrameCallback(this);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
Choreographer.getInstance().postFrameCallback(frameCallback);
|
|
426
|
+
|
|
427
|
+
View.OnLayoutChangeListener layoutListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
|
428
|
+
if (!isCurrentGeneration(wrapper, generation) || !button.isAttachedToWindow()) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
YogaLayoutHelper.measureAndLayout(wrapper, button);
|
|
432
|
+
maybeSendResizeEvent(wrapper, button);
|
|
433
|
+
};
|
|
434
|
+
viewToLayoutListener.put(wrapper, layoutListener);
|
|
435
|
+
wrapper.addOnLayoutChangeListener(layoutListener);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private int nextGeneration(@NonNull FrameLayout view) {
|
|
439
|
+
int next = (viewToGeneration.get(view) == null ? 0 : viewToGeneration.get(view)) + 1;
|
|
440
|
+
viewToGeneration.put(view, next);
|
|
441
|
+
return next;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private boolean isCurrentGeneration(@NonNull FrameLayout view, int generation) {
|
|
445
|
+
Integer current = viewToGeneration.get(view);
|
|
446
|
+
return current != null && current == generation;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private void maybeSendResizeEvent(
|
|
450
|
+
@NonNull FrameLayout wrapper,
|
|
451
|
+
@NonNull View button
|
|
452
|
+
) {
|
|
453
|
+
if (button.getHeight() <= 0) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
float density = wrapper.getResources().getDisplayMetrics().density;
|
|
457
|
+
int heightInDp = Math.round(button.getHeight() / density);
|
|
458
|
+
ExpressCheckoutProps props = viewToProps.get(wrapper);
|
|
459
|
+
if (props == null || props.lastSentHeightDp == heightInDp) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
props.lastSentHeightDp = heightInDp;
|
|
463
|
+
eventSender.sendOnResizedEvent(wrapper, heightInDp);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// endregion
|
|
467
|
+
|
|
468
|
+
// region Prop parsing helpers
|
|
469
|
+
|
|
470
|
+
@Nullable
|
|
471
|
+
static KlarnaButtonTheme parseTheme(@Nullable String value) {
|
|
472
|
+
if (value == null) return null;
|
|
473
|
+
switch (value) {
|
|
474
|
+
case "light": return KlarnaButtonTheme.LIGHT;
|
|
475
|
+
case "dark": return KlarnaButtonTheme.DARK;
|
|
476
|
+
case "auto": return KlarnaButtonTheme.AUTO;
|
|
477
|
+
default: return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
@Nullable
|
|
482
|
+
static KlarnaButtonShape parseShape(@Nullable String value) {
|
|
483
|
+
if (value == null) return null;
|
|
484
|
+
switch (value) {
|
|
485
|
+
case "roundedRect": return KlarnaButtonShape.ROUNDED_RECT;
|
|
486
|
+
case "pill": return KlarnaButtonShape.PILL;
|
|
487
|
+
case "rectangle": return KlarnaButtonShape.RECTANGLE;
|
|
488
|
+
default: return null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
@Nullable
|
|
493
|
+
static KlarnaButtonStyle parseButtonStyle(@Nullable String value) {
|
|
494
|
+
if (value == null) return null;
|
|
495
|
+
switch (value) {
|
|
496
|
+
case "filled": return KlarnaButtonStyle.FILLED;
|
|
497
|
+
case "outlined": return KlarnaButtonStyle.OUTLINED;
|
|
498
|
+
default: return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
@Nullable
|
|
503
|
+
static KlarnaEnvironment parseEnvironment(@Nullable String value) {
|
|
504
|
+
if (value == null) return null;
|
|
505
|
+
switch (value) {
|
|
506
|
+
case "playground": return KlarnaEnvironment.PLAYGROUND;
|
|
507
|
+
case "production": return KlarnaEnvironment.PRODUCTION;
|
|
508
|
+
default: return null;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
@Nullable
|
|
513
|
+
static KlarnaRegion parseRegion(@Nullable String value) {
|
|
514
|
+
if (value == null) return null;
|
|
515
|
+
switch (value) {
|
|
516
|
+
case "eu": return KlarnaRegion.EU;
|
|
517
|
+
case "na": return KlarnaRegion.NA;
|
|
518
|
+
case "oc": return KlarnaRegion.OC;
|
|
519
|
+
default: return null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// endregion
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Holds the current prop values for a single view instance.
|
|
527
|
+
*/
|
|
528
|
+
static class ExpressCheckoutProps {
|
|
529
|
+
String sessionType;
|
|
530
|
+
String clientId;
|
|
531
|
+
String clientToken;
|
|
532
|
+
String locale;
|
|
533
|
+
String environment;
|
|
534
|
+
String region;
|
|
535
|
+
String returnUrl;
|
|
536
|
+
String theme;
|
|
537
|
+
String shape;
|
|
538
|
+
String buttonStyle;
|
|
539
|
+
boolean autoFinalize = true;
|
|
540
|
+
boolean collectShippingAddress = false;
|
|
541
|
+
String sessionData;
|
|
542
|
+
int lastSentHeightDp = -1;
|
|
543
|
+
}
|
|
544
|
+
}
|
package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventHandler.java
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.klarna.mobile.sdk.reactnative.osm;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
import com.klarna.mobile.sdk.KlarnaMobileSDKError;
|
|
6
|
+
import com.klarna.mobile.sdk.api.KlarnaEventHandler;
|
|
7
|
+
import com.klarna.mobile.sdk.api.KlarnaProductEvent;
|
|
8
|
+
import com.klarna.mobile.sdk.api.component.KlarnaComponent;
|
|
9
|
+
import com.klarna.mobile.sdk.api.osm.KlarnaOSMView;
|
|
10
|
+
|
|
11
|
+
public class KlarnaOSMViewEventHandler implements KlarnaEventHandler {
|
|
12
|
+
|
|
13
|
+
private final KlarnaOSMViewEventSender eventSender;
|
|
14
|
+
|
|
15
|
+
public KlarnaOSMViewEventHandler(@NonNull KlarnaOSMViewEventSender eventSender) {
|
|
16
|
+
this.eventSender = eventSender;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public void onEvent(@NonNull KlarnaComponent klarnaComponent, @NonNull KlarnaProductEvent klarnaProductEvent) {
|
|
21
|
+
// OSM view does not emit product events
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Override
|
|
25
|
+
public void onError(@NonNull KlarnaComponent klarnaComponent, @NonNull KlarnaMobileSDKError klarnaMobileSDKError) {
|
|
26
|
+
if (klarnaComponent instanceof KlarnaOSMView) {
|
|
27
|
+
eventSender.sendKlarnaMobileSDKError((KlarnaOSMView) klarnaComponent, klarnaMobileSDKError);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/android/src/main/java/com/klarna/mobile/sdk/reactnative/osm/KlarnaOSMViewEventSender.java
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package com.klarna.mobile.sdk.reactnative.osm;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
import com.facebook.react.bridge.WritableMap;
|
|
7
|
+
import com.facebook.react.uimanager.events.EventDispatcher;
|
|
8
|
+
import com.klarna.mobile.sdk.KlarnaMobileSDKError;
|
|
9
|
+
import com.klarna.mobile.sdk.api.osm.KlarnaOSMView;
|
|
10
|
+
import com.klarna.mobile.sdk.reactnative.common.event.ComponentEventSender;
|
|
11
|
+
import com.klarna.mobile.sdk.reactnative.common.ui.WrapperView;
|
|
12
|
+
import com.klarna.mobile.sdk.reactnative.common.util.ArgumentsUtil;
|
|
13
|
+
|
|
14
|
+
import java.util.Arrays;
|
|
15
|
+
import java.util.Collection;
|
|
16
|
+
import java.util.HashMap;
|
|
17
|
+
import java.util.Map;
|
|
18
|
+
|
|
19
|
+
public class KlarnaOSMViewEventSender extends ComponentEventSender<WrapperView<KlarnaOSMView>> {
|
|
20
|
+
|
|
21
|
+
private static final String EVENT_NAME_ON_ERROR = "onError";
|
|
22
|
+
private static final String EVENT_NAME_ON_RESIZED = "onResized";
|
|
23
|
+
private static final String EVENT_NAME_ON_OSM_VIEW_READY = "onOSMViewReady";
|
|
24
|
+
|
|
25
|
+
KlarnaOSMViewEventSender(@NonNull final Map<WrapperView<KlarnaOSMView>, EventDispatcher> viewToDispatcher) {
|
|
26
|
+
super(viewToDispatcher);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Override
|
|
30
|
+
public Collection<String> getCallbackEventNames() {
|
|
31
|
+
return Arrays.asList(
|
|
32
|
+
EVENT_NAME_ON_ERROR,
|
|
33
|
+
EVENT_NAME_ON_RESIZED,
|
|
34
|
+
EVENT_NAME_ON_OSM_VIEW_READY
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public void sendKlarnaMobileSDKError(@Nullable KlarnaOSMView view, @NonNull KlarnaMobileSDKError klarnaMobileSDKError) {
|
|
39
|
+
sendError(view, klarnaMobileSDKError.getName(), klarnaMobileSDKError.getMessage(), klarnaMobileSDKError.isFatal());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public void sendError(@Nullable KlarnaOSMView view, @NonNull String name, @NonNull String message, boolean isFatal) {
|
|
43
|
+
WritableMap errorMap = ArgumentsUtil.createMap(new HashMap<String, Object>() {{
|
|
44
|
+
put("name", name);
|
|
45
|
+
put("message", message);
|
|
46
|
+
put("isFatal", isFatal);
|
|
47
|
+
}});
|
|
48
|
+
WritableMap params = ArgumentsUtil.createMap(new HashMap<String, Object>() {{
|
|
49
|
+
put("error", errorMap);
|
|
50
|
+
}});
|
|
51
|
+
postEventForView(view, EVENT_NAME_ON_ERROR, params);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public void sendOnResizedEvent(@Nullable KlarnaOSMView view, int height) {
|
|
55
|
+
WritableMap params = ArgumentsUtil.createMap(new HashMap<String, Object>() {{
|
|
56
|
+
put("height", String.valueOf(height));
|
|
57
|
+
}});
|
|
58
|
+
postEventForView(view, EVENT_NAME_ON_RESIZED, params);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public void sendOnOSMViewReadyEvent(@Nullable KlarnaOSMView view) {
|
|
62
|
+
postEventForView(view, EVENT_NAME_ON_OSM_VIEW_READY, null);
|
|
63
|
+
}
|
|
64
|
+
}
|