@shortkitsdk/react-native 0.2.33 → 0.2.35
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/android/libs/shortkit-release.aar +0 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +6 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +8 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHost.kt +7 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPackage.kt +0 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +357 -35
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +9 -9
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +357 -35
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +357 -35
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +17 -17
- package/ios/ShortKitWidgetNativeView.swift +40 -7
- package/ios/ShortKitWidgetNativeViewManager.mm +1 -0
- package/package.json +1 -1
- package/src/ShortKitPlayer.tsx +20 -1
- package/src/ShortKitWidget.tsx +63 -15
- package/src/specs/ShortKitWidgetViewNativeComponent.ts +15 -3
- package/src/types.ts +5 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +0 -149
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerViewManager.kt +0 -35
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +0 -149
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetViewManager.kt +0 -30
|
@@ -10,39 +10,39 @@
|
|
|
10
10
|
</data>
|
|
11
11
|
<key>Info.plist</key>
|
|
12
12
|
<data>
|
|
13
|
-
|
|
13
|
+
OvrkhBvYgEeQNGBIL8Sk+zwmd+8=
|
|
14
14
|
</data>
|
|
15
15
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json</key>
|
|
16
16
|
<data>
|
|
17
|
-
|
|
17
|
+
gUMbcAXcwCbvhUG1oUpN35PTUFU=
|
|
18
18
|
</data>
|
|
19
19
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
|
|
20
20
|
<data>
|
|
21
|
-
|
|
21
|
+
h6Cg/BD9N7YWvWXoaZDHIvsBs+Q=
|
|
22
22
|
</data>
|
|
23
23
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc</key>
|
|
24
24
|
<data>
|
|
25
|
-
|
|
25
|
+
WN66xa1ykIuY1q8/q3xX14rO1qU=
|
|
26
26
|
</data>
|
|
27
27
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface</key>
|
|
28
28
|
<data>
|
|
29
|
-
|
|
29
|
+
h6Cg/BD9N7YWvWXoaZDHIvsBs+Q=
|
|
30
30
|
</data>
|
|
31
31
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
|
32
32
|
<data>
|
|
33
|
-
|
|
33
|
+
gUMbcAXcwCbvhUG1oUpN35PTUFU=
|
|
34
34
|
</data>
|
|
35
35
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
|
36
36
|
<data>
|
|
37
|
-
|
|
37
|
+
MiQorgvuaKDH14aSEh9ZJVYQfjo=
|
|
38
38
|
</data>
|
|
39
39
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
|
40
40
|
<data>
|
|
41
|
-
|
|
41
|
+
K085KBZpASu5rTlrMsWTnd618bw=
|
|
42
42
|
</data>
|
|
43
43
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
|
44
44
|
<data>
|
|
45
|
-
|
|
45
|
+
MiQorgvuaKDH14aSEh9ZJVYQfjo=
|
|
46
46
|
</data>
|
|
47
47
|
<key>Modules/module.modulemap</key>
|
|
48
48
|
<data>
|
|
@@ -66,56 +66,56 @@
|
|
|
66
66
|
<dict>
|
|
67
67
|
<key>hash2</key>
|
|
68
68
|
<data>
|
|
69
|
-
|
|
69
|
+
98/sM6XrtDCNV+236cKUe+xeL8df0JeaP/P+lEE01ew=
|
|
70
70
|
</data>
|
|
71
71
|
</dict>
|
|
72
72
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface</key>
|
|
73
73
|
<dict>
|
|
74
74
|
<key>hash2</key>
|
|
75
75
|
<data>
|
|
76
|
-
|
|
76
|
+
znlbvvD4pnPCRu9Aei74T4NdRxjdD2cIswo6AC4CsTA=
|
|
77
77
|
</data>
|
|
78
78
|
</dict>
|
|
79
79
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc</key>
|
|
80
80
|
<dict>
|
|
81
81
|
<key>hash2</key>
|
|
82
82
|
<data>
|
|
83
|
-
|
|
83
|
+
Bn3ZIMIwiUnv4XCjNMicV7dy8dh3SrBhja0tyxB9BhU=
|
|
84
84
|
</data>
|
|
85
85
|
</dict>
|
|
86
86
|
<key>Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface</key>
|
|
87
87
|
<dict>
|
|
88
88
|
<key>hash2</key>
|
|
89
89
|
<data>
|
|
90
|
-
|
|
90
|
+
znlbvvD4pnPCRu9Aei74T4NdRxjdD2cIswo6AC4CsTA=
|
|
91
91
|
</data>
|
|
92
92
|
</dict>
|
|
93
93
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json</key>
|
|
94
94
|
<dict>
|
|
95
95
|
<key>hash2</key>
|
|
96
96
|
<data>
|
|
97
|
-
|
|
97
|
+
98/sM6XrtDCNV+236cKUe+xeL8df0JeaP/P+lEE01ew=
|
|
98
98
|
</data>
|
|
99
99
|
</dict>
|
|
100
100
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface</key>
|
|
101
101
|
<dict>
|
|
102
102
|
<key>hash2</key>
|
|
103
103
|
<data>
|
|
104
|
-
|
|
104
|
+
xoM3qxlgST57eK/vJqzmI4rFvll9ZmfMr4+El60Pq/c=
|
|
105
105
|
</data>
|
|
106
106
|
</dict>
|
|
107
107
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc</key>
|
|
108
108
|
<dict>
|
|
109
109
|
<key>hash2</key>
|
|
110
110
|
<data>
|
|
111
|
-
|
|
111
|
+
IjhLI8nlyrN3P1GlQbXjVHfr4vkxgKw5OqJNmlk0Fv4=
|
|
112
112
|
</data>
|
|
113
113
|
</dict>
|
|
114
114
|
<key>Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface</key>
|
|
115
115
|
<dict>
|
|
116
116
|
<key>hash2</key>
|
|
117
117
|
<data>
|
|
118
|
-
|
|
118
|
+
xoM3qxlgST57eK/vJqzmI4rFvll9ZmfMr4+El60Pq/c=
|
|
119
119
|
</data>
|
|
120
120
|
</dict>
|
|
121
121
|
<key>Modules/module.modulemap</key>
|
|
@@ -25,12 +25,24 @@ import ShortKitSDK
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/// Stable per-instance widget identifier set by the JS wrapper
|
|
28
|
+
/// Stable per-instance widget identifier set by the JS wrapper when the
|
|
29
|
+
/// host has wired `onCardTap` and wants to handle taps itself. Echoed
|
|
29
30
|
/// back on `onWidgetCardTap` events so the JS wrapper can route the
|
|
30
|
-
/// global emit to the correct `<ShortKitWidget>` instance when more
|
|
31
|
-
/// one is mounted simultaneously.
|
|
31
|
+
/// global emit to the correct `<ShortKitWidget>` instance when more
|
|
32
|
+
/// than one is mounted simultaneously. Presence of this id signals
|
|
33
|
+
/// "host takes over taps" — the native side wires `vc.onCardTap`
|
|
34
|
+
/// which makes the SDK skip its built-in modal-present path.
|
|
32
35
|
@objc public var widgetId: String?
|
|
33
36
|
|
|
37
|
+
/// Stable per-instance modal-feed identifier set by the JS wrapper when
|
|
38
|
+
/// the host has wired per-feed callbacks (e.g.
|
|
39
|
+
/// `feedConfig.onDidFetchContentItems`) and wants to receive scoped
|
|
40
|
+
/// events from the SDK's built-in modal feed. Independent from
|
|
41
|
+
/// `widgetId` — the host can wire modal callbacks WITHOUT taking over
|
|
42
|
+
/// taps. Used as the `feedId` under which the modal feed VC is
|
|
43
|
+
/// registered with the bridge on present.
|
|
44
|
+
@objc public var modalFeedId: String?
|
|
45
|
+
|
|
34
46
|
/// Mirrors `ShortKitWidgetViewController.active`. Hosts use this to
|
|
35
47
|
/// release the widget's pool tile when it shouldn't be playing
|
|
36
48
|
/// (e.g. inactive tab, scrolled off-screen). Defaults to `true` so
|
|
@@ -172,6 +184,21 @@ import ShortKitSDK
|
|
|
172
184
|
}
|
|
173
185
|
}
|
|
174
186
|
|
|
187
|
+
// Register the SDK's built-in modal feed VC with the bridge under
|
|
188
|
+
// the JS-supplied `modalFeedId` so per-feed callbacks wired on the
|
|
189
|
+
// widget's `feedConfig` (e.g. `onDidFetchContentItems`) get scoped
|
|
190
|
+
// attribution. The JS wrapper supplies this id only when at least
|
|
191
|
+
// one such callback is set; absent it, the modal still presents
|
|
192
|
+
// and runs normally — events just emit with empty feedId.
|
|
193
|
+
if let id = modalFeedId {
|
|
194
|
+
vc.onModalFeedPresented = { feedVC in
|
|
195
|
+
ShortKitBridge.shared?.registerFeed(id: id, viewController: feedVC)
|
|
196
|
+
}
|
|
197
|
+
vc.onModalFeedDismissed = {
|
|
198
|
+
ShortKitBridge.shared?.unregisterFeed(id: id)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
175
202
|
parentVC.addChild(vc)
|
|
176
203
|
vc.view.frame = bounds
|
|
177
204
|
vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
@@ -229,11 +256,17 @@ import ShortKitSDK
|
|
|
229
256
|
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
230
257
|
return .none
|
|
231
258
|
}
|
|
232
|
-
// The serializer emits feedMask either as the string "none"
|
|
233
|
-
// as a {type, name} object
|
|
234
|
-
//
|
|
259
|
+
// The serializer emits feedMask either as the bare string "none"
|
|
260
|
+
// or as a {type, name} object. JSONSerialization.data rejects
|
|
261
|
+
// top-level fragments without .fragmentsAllowed and raises an
|
|
262
|
+
// Obj-C NSInvalidArgumentException — which `try?` cannot catch
|
|
263
|
+
// — so the String case must be handled before re-serializing.
|
|
235
264
|
if let mask = obj["feedMask"] {
|
|
236
|
-
if
|
|
265
|
+
if mask is String {
|
|
266
|
+
return .none
|
|
267
|
+
}
|
|
268
|
+
if let dict = mask as? [String: Any],
|
|
269
|
+
let data = try? JSONSerialization.data(withJSONObject: dict),
|
|
237
270
|
let json = String(data: data, encoding: .utf8) {
|
|
238
271
|
return ShortKitBridge.parseFeedMask(json)
|
|
239
272
|
}
|
|
@@ -24,6 +24,7 @@ RCT_EXPORT_MODULE(ShortKitWidgetView)
|
|
|
24
24
|
RCT_EXPORT_VIEW_PROPERTY(config, NSString)
|
|
25
25
|
RCT_EXPORT_VIEW_PROPERTY(items, NSString)
|
|
26
26
|
RCT_EXPORT_VIEW_PROPERTY(widgetId, NSString)
|
|
27
|
+
RCT_EXPORT_VIEW_PROPERTY(modalFeedId, NSString)
|
|
27
28
|
RCT_EXPORT_VIEW_PROPERTY(active, BOOL)
|
|
28
29
|
RCT_EXPORT_VIEW_PROPERTY(feedItems, NSString)
|
|
29
30
|
|
package/package.json
CHANGED
package/src/ShortKitPlayer.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useLayoutEffect, useMemo } from 'react';
|
|
2
|
-
import { View, StyleSheet } from 'react-native';
|
|
2
|
+
import { Platform, View, StyleSheet } from 'react-native';
|
|
3
3
|
import type { ShortKitPlayerProps } from './types';
|
|
4
4
|
import ShortKitPlayerView from './specs/ShortKitPlayerViewNativeComponent';
|
|
5
5
|
import { serializePlayerConfig } from './serialization';
|
|
@@ -8,6 +8,11 @@ import { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurfa
|
|
|
8
8
|
import { registerVideoCarouselOverlayComponent } from './ShortKitVideoCarouselOverlaySurface';
|
|
9
9
|
import { registerFeedMaskComponent } from './ShortKitFeedMaskSurface';
|
|
10
10
|
|
|
11
|
+
// One-time dev warning when ShortKitPlayer is rendered on Android. The
|
|
12
|
+
// Android SDK was scoped down to feed-only in #246; the JS surface stays
|
|
13
|
+
// for iOS but renders an empty container on Android.
|
|
14
|
+
let didWarnAndroidPlayerUnavailable = false;
|
|
15
|
+
|
|
11
16
|
/**
|
|
12
17
|
* Single-video player component. Displays one video with thumbnail fallback
|
|
13
18
|
* and optional overlay. Wraps a native Fabric view.
|
|
@@ -75,6 +80,20 @@ export function ShortKitPlayer(props: ShortKitPlayerProps) {
|
|
|
75
80
|
// is fully invisible to hit testing.
|
|
76
81
|
const nativeTouchPassthrough = clickAction === 'none';
|
|
77
82
|
|
|
83
|
+
// Android: the native ViewManager backing ShortKitPlayer was removed in
|
|
84
|
+
// #246 (feed-only scope-down). The JS surface stays so cross-platform
|
|
85
|
+
// host code compiles, but renders an empty container.
|
|
86
|
+
if (Platform.OS === 'android') {
|
|
87
|
+
if (__DEV__ && !didWarnAndroidPlayerUnavailable) {
|
|
88
|
+
didWarnAndroidPlayerUnavailable = true;
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.warn(
|
|
91
|
+
'[ShortKit] <ShortKitPlayer> is not supported on Android — rendering an empty container.',
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return <View style={[styles.container, style]} />;
|
|
95
|
+
}
|
|
96
|
+
|
|
78
97
|
return (
|
|
79
98
|
<View
|
|
80
99
|
style={[styles.container, style]}
|
package/src/ShortKitWidget.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
|
2
|
-
import { View, StyleSheet } from 'react-native';
|
|
2
|
+
import { Platform, View, StyleSheet } from 'react-native';
|
|
3
3
|
import type { ShortKitWidgetProps } from './types';
|
|
4
4
|
import ShortKitWidgetView from './specs/ShortKitWidgetViewNativeComponent';
|
|
5
5
|
import NativeShortKitModule from './specs/NativeShortKitModule';
|
|
@@ -10,6 +10,11 @@ import { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurfa
|
|
|
10
10
|
import { registerVideoCarouselOverlayComponent } from './ShortKitVideoCarouselOverlaySurface';
|
|
11
11
|
import { registerFeedMaskComponent } from './ShortKitFeedMaskSurface';
|
|
12
12
|
|
|
13
|
+
// One-time dev warning when ShortKitWidget is rendered on Android. The
|
|
14
|
+
// Android SDK was scoped down to feed-only in #246; the JS surface stays
|
|
15
|
+
// for iOS but renders an empty container on Android.
|
|
16
|
+
let didWarnAndroidWidgetUnavailable = false;
|
|
17
|
+
|
|
13
18
|
// Local UUID generator. We don't pull in a runtime dep just for this; a
|
|
14
19
|
// timestamp + random suffix is more than unique enough to disambiguate
|
|
15
20
|
// concurrently-mounted widgets within a single app session.
|
|
@@ -31,28 +36,36 @@ export function ShortKitWidget(props: ShortKitWidgetProps) {
|
|
|
31
36
|
throw new Error('ShortKitWidget must be used within a ShortKitProvider');
|
|
32
37
|
}
|
|
33
38
|
|
|
34
|
-
// Stable widget ID
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// that don't opt in. Backwards compatible.
|
|
39
|
+
// Stable widget ID, generated only when the host wires `onCardTap`.
|
|
40
|
+
// Presence on the native side signals "host handles taps" and the SDK
|
|
41
|
+
// skips its built-in modal-presentation path. Backwards compatible —
|
|
42
|
+
// hosts who don't wire `onCardTap` see no behavior change.
|
|
39
43
|
const widgetId = useMemo(
|
|
40
44
|
() => (onCardTap ? generateWidgetId() : undefined),
|
|
41
|
-
// We deliberately compute this once per "host wired callback" lifetime,
|
|
42
|
-
// not per render. The boolean test on first render is what matters; if
|
|
43
|
-
// the host swaps onCardTap from set → unset later we keep the same
|
|
44
|
-
// widgetId (harmless — no events will fire because the JS subscription
|
|
45
|
-
// below tears down). Going the other way (unset → set), we generate a
|
|
46
|
-
// fresh id then.
|
|
47
45
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
48
46
|
[Boolean(onCardTap)],
|
|
49
47
|
);
|
|
50
48
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
49
|
+
// Stable modal-feed ID, generated only when the host wires per-feed
|
|
50
|
+
// callbacks on the expanded modal feed (currently
|
|
51
|
+
// `feedConfig.onDidFetchContentItems`). Independent from `widgetId` —
|
|
52
|
+
// hosts can subscribe to modal events without taking over taps. The
|
|
53
|
+
// native side registers the modal feed VC under this id so emits get
|
|
54
|
+
// scoped back to this widget's callbacks.
|
|
55
|
+
const onDidFetchContentItems = config?.feedConfig?.onDidFetchContentItems;
|
|
56
|
+
const modalFeedId = useMemo(
|
|
57
|
+
() => (onDidFetchContentItems ? generateWidgetId() : undefined),
|
|
58
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
59
|
+
[Boolean(onDidFetchContentItems)],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Keep stable refs to the latest callbacks so subscriptions don't need to
|
|
63
|
+
// tear down + rebuild every time the host re-renders with a new closure
|
|
64
|
+
// identity.
|
|
54
65
|
const onCardTapRef = useRef(onCardTap);
|
|
55
66
|
onCardTapRef.current = onCardTap;
|
|
67
|
+
const onDidFetchContentItemsRef = useRef(onDidFetchContentItems);
|
|
68
|
+
onDidFetchContentItemsRef.current = onDidFetchContentItems;
|
|
56
69
|
|
|
57
70
|
useLayoutEffect(() => {
|
|
58
71
|
if (config?.overlay && config.overlay !== 'none') {
|
|
@@ -88,6 +101,26 @@ export function ShortKitWidget(props: ShortKitWidgetProps) {
|
|
|
88
101
|
return () => subscription.remove();
|
|
89
102
|
}, [widgetId, onCardTap]);
|
|
90
103
|
|
|
104
|
+
// Subscribe to `onDidFetchContentItems` scoped to this widget's modal
|
|
105
|
+
// feed. Native side registers the modal feed VC under `modalFeedId` on
|
|
106
|
+
// present and emits with that feedId; filter to it and route to the
|
|
107
|
+
// host callback wired on `feedConfig.onDidFetchContentItems`.
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!NativeShortKitModule || !modalFeedId || !onDidFetchContentItems) return;
|
|
110
|
+
|
|
111
|
+
const subscription = NativeShortKitModule.onDidFetchContentItems((event) => {
|
|
112
|
+
if (event.feedId !== modalFeedId) return;
|
|
113
|
+
try {
|
|
114
|
+
const items = JSON.parse(event.items);
|
|
115
|
+
onDidFetchContentItemsRef.current?.(items);
|
|
116
|
+
} catch {
|
|
117
|
+
// ignore malformed JSON
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return () => subscription.remove();
|
|
122
|
+
}, [modalFeedId, onDidFetchContentItems]);
|
|
123
|
+
|
|
91
124
|
const serializedConfig = useMemo(() => {
|
|
92
125
|
return serializeWidgetConfig(config ?? {});
|
|
93
126
|
}, [config]);
|
|
@@ -105,6 +138,20 @@ export function ShortKitWidget(props: ShortKitWidgetProps) {
|
|
|
105
138
|
return JSON.stringify(feedItems);
|
|
106
139
|
}, [feedItems]);
|
|
107
140
|
|
|
141
|
+
// Android: the native ViewManager backing ShortKitWidget was removed in
|
|
142
|
+
// #246 (feed-only scope-down). The JS surface stays so cross-platform
|
|
143
|
+
// host code compiles, but renders an empty container.
|
|
144
|
+
if (Platform.OS === 'android') {
|
|
145
|
+
if (__DEV__ && !didWarnAndroidWidgetUnavailable) {
|
|
146
|
+
didWarnAndroidWidgetUnavailable = true;
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.warn(
|
|
149
|
+
'[ShortKit] <ShortKitWidget> is not supported on Android — rendering an empty container.',
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return <View style={[styles.container, style]} />;
|
|
153
|
+
}
|
|
154
|
+
|
|
108
155
|
return (
|
|
109
156
|
<View style={[styles.container, style]}>
|
|
110
157
|
<ShortKitWidgetView
|
|
@@ -112,6 +159,7 @@ export function ShortKitWidget(props: ShortKitWidgetProps) {
|
|
|
112
159
|
config={serializedConfig}
|
|
113
160
|
items={serializedItems}
|
|
114
161
|
widgetId={widgetId}
|
|
162
|
+
modalFeedId={modalFeedId}
|
|
115
163
|
active={active}
|
|
116
164
|
feedItems={serializedFeedItems}
|
|
117
165
|
/>
|
|
@@ -5,11 +5,23 @@ export interface NativeProps extends ViewProps {
|
|
|
5
5
|
config: string;
|
|
6
6
|
items?: string;
|
|
7
7
|
/**
|
|
8
|
-
* Stable per-instance widget identifier.
|
|
9
|
-
* `
|
|
10
|
-
*
|
|
8
|
+
* Stable per-instance widget identifier. Set only when the host has
|
|
9
|
+
* wired `onCardTap` — its presence signals "host handles taps" to the
|
|
10
|
+
* native side. Echoed back on `onWidgetCardTap` events so the JS
|
|
11
|
+
* wrapper can route the global emit to the correct `<ShortKitWidget>`
|
|
12
|
+
* instance when more than one is mounted.
|
|
11
13
|
*/
|
|
12
14
|
widgetId?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Stable per-instance modal-feed identifier. Set only when the host
|
|
17
|
+
* has wired per-feed callbacks (e.g.
|
|
18
|
+
* `feedConfig.onDidFetchContentItems`). The native side registers the
|
|
19
|
+
* SDK's built-in modal feed VC with the bridge under this id so events
|
|
20
|
+
* fired by the modal can be scoped back to the correct widget's
|
|
21
|
+
* callbacks. Independent from `widgetId` — a host can wire modal
|
|
22
|
+
* callbacks without taking over taps.
|
|
23
|
+
*/
|
|
24
|
+
modalFeedId?: string;
|
|
13
25
|
/**
|
|
14
26
|
* Whether the widget is currently allowed to play. Defaults to true on
|
|
15
27
|
* the native side. Hosts use this to release the widget's pool tile
|
package/src/types.ts
CHANGED
|
@@ -32,6 +32,11 @@ export interface FeedConfig {
|
|
|
32
32
|
autoplay?: boolean;
|
|
33
33
|
filter?: FeedFilter;
|
|
34
34
|
pullToRefreshEnabled?: boolean;
|
|
35
|
+
/** Called when the modal feed presented from `<ShortKitWidget>` /
|
|
36
|
+
* `<ShortKitPlayer>` fetches content items. Use to pre-fetch your own
|
|
37
|
+
* metadata for the items shown in the modal. Mirrors the same callback
|
|
38
|
+
* on `<ShortKitFeed>` props. */
|
|
39
|
+
onDidFetchContentItems?: (items: ContentItem[]) => void;
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
export type FeedHeight =
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
package com.shortkit.reactnative
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.view.MotionEvent
|
|
5
|
-
import android.widget.FrameLayout
|
|
6
|
-
import com.shortkit.sdk.config.PlayerClickAction
|
|
7
|
-
import com.shortkit.sdk.config.FeedConfig
|
|
8
|
-
import com.shortkit.sdk.config.PlayerConfig
|
|
9
|
-
import com.shortkit.sdk.config.VideoOverlayMode
|
|
10
|
-
import com.shortkit.sdk.model.ContentItem
|
|
11
|
-
import com.shortkit.sdk.player.ShortKitPlayerView
|
|
12
|
-
import org.json.JSONObject
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Fabric native view wrapping [ShortKitPlayerView] for use as a
|
|
16
|
-
* single-video player in React Native.
|
|
17
|
-
*/
|
|
18
|
-
class ShortKitPlayerNativeView(context: Context) : FrameLayout(context) {
|
|
19
|
-
|
|
20
|
-
private var playerView: ShortKitPlayerView? = null
|
|
21
|
-
private var configJson: String? = null
|
|
22
|
-
private var contentItemJson: String? = null
|
|
23
|
-
|
|
24
|
-
var config: String?
|
|
25
|
-
get() = configJson
|
|
26
|
-
set(value) {
|
|
27
|
-
if (value == configJson) return
|
|
28
|
-
configJson = value
|
|
29
|
-
rebuildIfNeeded()
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
var contentItem: String?
|
|
33
|
-
get() = contentItemJson
|
|
34
|
-
set(value) {
|
|
35
|
-
if (value == contentItemJson) return
|
|
36
|
-
contentItemJson = value
|
|
37
|
-
applyContentItem()
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
var active: Boolean = true
|
|
41
|
-
set(value) {
|
|
42
|
-
if (field == value) return
|
|
43
|
-
field = value
|
|
44
|
-
applyActive()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
|
48
|
-
return super.onInterceptTouchEvent(ev)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
52
|
-
return super.dispatchTouchEvent(ev)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
override fun onAttachedToWindow() {
|
|
56
|
-
super.onAttachedToWindow()
|
|
57
|
-
rebuildIfNeeded()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
override fun onDetachedFromWindow() {
|
|
61
|
-
playerView?.deactivate()
|
|
62
|
-
super.onDetachedFromWindow()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private fun rebuildIfNeeded() {
|
|
66
|
-
if (playerView != null) return
|
|
67
|
-
|
|
68
|
-
val sdk = ShortKitBridge.shared?.sdk ?: return
|
|
69
|
-
val playerConfig = parsePlayerConfig(configJson)
|
|
70
|
-
|
|
71
|
-
val view = ShortKitPlayerView(context).apply {
|
|
72
|
-
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
73
|
-
}
|
|
74
|
-
view.initialize(sdk, playerConfig)
|
|
75
|
-
addView(view)
|
|
76
|
-
playerView = view
|
|
77
|
-
|
|
78
|
-
applyContentItem()
|
|
79
|
-
if (active) view.activate()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private fun applyContentItem() {
|
|
83
|
-
val json = contentItemJson ?: return
|
|
84
|
-
val view = playerView ?: return
|
|
85
|
-
val item = parseContentItem(json) ?: return
|
|
86
|
-
view.configure(item)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private fun applyActive() {
|
|
90
|
-
val view = playerView ?: return
|
|
91
|
-
if (active) view.activate() else view.deactivate()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
private fun parsePlayerConfig(json: String?): PlayerConfig {
|
|
95
|
-
if (json.isNullOrEmpty()) return PlayerConfig()
|
|
96
|
-
return try {
|
|
97
|
-
val obj = JSONObject(json)
|
|
98
|
-
PlayerConfig(
|
|
99
|
-
cornerRadius = obj.optDouble("cornerRadius", 12.0).toFloat(),
|
|
100
|
-
clickAction = when (obj.optString("clickAction", "feed")) {
|
|
101
|
-
"feed" -> PlayerClickAction.FEED
|
|
102
|
-
"mute" -> PlayerClickAction.MUTE
|
|
103
|
-
"none" -> PlayerClickAction.NONE
|
|
104
|
-
else -> PlayerClickAction.FEED
|
|
105
|
-
},
|
|
106
|
-
autoplay = obj.optBoolean("autoplay", true),
|
|
107
|
-
loop = obj.optBoolean("loop", true),
|
|
108
|
-
muteOnStart = obj.optBoolean("muteOnStart", true),
|
|
109
|
-
videoOverlay = parseOverlay(obj),
|
|
110
|
-
feedConfig = obj.optString("feedConfig", "").let { fcStr ->
|
|
111
|
-
if (fcStr.isNotEmpty()) ShortKitBridge.parseFeedConfig(fcStr, context) else FeedConfig()
|
|
112
|
-
},
|
|
113
|
-
)
|
|
114
|
-
} catch (_: Exception) {
|
|
115
|
-
PlayerConfig()
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private fun parseOverlay(obj: JSONObject): VideoOverlayMode {
|
|
120
|
-
val overlay = obj.opt("overlay") ?: return VideoOverlayMode.None
|
|
121
|
-
if (overlay is JSONObject && overlay.optString("type") == "custom") {
|
|
122
|
-
return VideoOverlayMode.Custom {
|
|
123
|
-
ReactOverlayHost(context)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return VideoOverlayMode.None
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private fun parseContentItem(json: String): ContentItem? {
|
|
130
|
-
return try {
|
|
131
|
-
val obj = JSONObject(json)
|
|
132
|
-
ContentItem(
|
|
133
|
-
id = obj.getString("id"),
|
|
134
|
-
title = obj.getString("title"),
|
|
135
|
-
description = obj.optString("description", null),
|
|
136
|
-
duration = obj.getDouble("duration"),
|
|
137
|
-
streamingUrl = obj.getString("streamingUrl"),
|
|
138
|
-
thumbnailUrl = obj.getString("thumbnailUrl"),
|
|
139
|
-
captionTracks = emptyList(),
|
|
140
|
-
customMetadata = null,
|
|
141
|
-
author = obj.optString("author", null),
|
|
142
|
-
articleUrl = obj.optString("articleUrl", null),
|
|
143
|
-
commentCount = if (obj.has("commentCount")) obj.getInt("commentCount") else null,
|
|
144
|
-
)
|
|
145
|
-
} catch (_: Exception) {
|
|
146
|
-
null
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
package com.shortkit.reactnative
|
|
2
|
-
|
|
3
|
-
import com.facebook.react.module.annotations.ReactModule
|
|
4
|
-
import com.facebook.react.uimanager.SimpleViewManager
|
|
5
|
-
import com.facebook.react.uimanager.ThemedReactContext
|
|
6
|
-
import com.facebook.react.uimanager.annotations.ReactProp
|
|
7
|
-
|
|
8
|
-
@ReactModule(name = ShortKitPlayerViewManager.REACT_CLASS)
|
|
9
|
-
class ShortKitPlayerViewManager : SimpleViewManager<ShortKitPlayerNativeView>() {
|
|
10
|
-
|
|
11
|
-
override fun getName(): String = REACT_CLASS
|
|
12
|
-
|
|
13
|
-
override fun createViewInstance(context: ThemedReactContext): ShortKitPlayerNativeView {
|
|
14
|
-
return ShortKitPlayerNativeView(context)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@ReactProp(name = "config")
|
|
18
|
-
fun setConfig(view: ShortKitPlayerNativeView, config: String?) {
|
|
19
|
-
view.config = config
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
@ReactProp(name = "contentItem")
|
|
23
|
-
fun setContentItem(view: ShortKitPlayerNativeView, contentItem: String?) {
|
|
24
|
-
view.contentItem = contentItem
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@ReactProp(name = "active", defaultBoolean = true)
|
|
28
|
-
fun setActive(view: ShortKitPlayerNativeView, active: Boolean) {
|
|
29
|
-
view.active = active
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
companion object {
|
|
33
|
-
const val REACT_CLASS = "ShortKitPlayerView"
|
|
34
|
-
}
|
|
35
|
-
}
|