@shortkitsdk/react-native 0.2.6 → 0.2.12
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/ShortKitReactNative.podspec +1 -0
- package/android/build.gradle.kts +17 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +379 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +570 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +1029 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +212 -219
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +17 -3
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +157 -742
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +11 -2
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +2 -2
- package/ios/ReactCarouselOverlayHost.swift +177 -0
- package/ios/ReactLoadingHost.swift +38 -0
- package/ios/ReactOverlayHost.swift +444 -0
- package/ios/SKFabricSurfaceWrapper.h +18 -0
- package/ios/SKFabricSurfaceWrapper.mm +57 -0
- package/ios/ShortKitBridge.swift +220 -63
- package/ios/ShortKitFeedView.swift +82 -228
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +69 -37
- package/ios/ShortKitPlayerNativeView.swift +39 -8
- package/ios/ShortKitReactNative-Bridging-Header.h +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3683 -1249
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +56 -15
- 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 +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3683 -1249
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/Info.plist +43 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +28917 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +28917 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitWidgetNativeView.swift +3 -3
- package/package.json +1 -1
- package/src/ShortKitCarouselOverlaySurface.tsx +55 -0
- package/src/ShortKitCommands.ts +31 -0
- package/src/ShortKitContext.ts +6 -24
- package/src/ShortKitFeed.tsx +124 -41
- package/src/ShortKitLoadingSurface.tsx +24 -0
- package/src/ShortKitOverlaySurface.tsx +313 -0
- package/src/ShortKitPlayer.tsx +30 -9
- package/src/ShortKitProvider.tsx +28 -285
- package/src/index.ts +9 -3
- package/src/serialization.ts +20 -39
- package/src/specs/NativeShortKitModule.ts +74 -45
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +84 -16
- package/src/useShortKit.ts +1 -3
- package/src/useShortKitPlayer.ts +7 -7
- package/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +0 -48
- package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +0 -128
- package/ios/ShortKitCarouselOverlayBridge.swift +0 -219
- package/ios/ShortKitOverlayBridge.swift +0 -111
- package/src/CarouselOverlayManager.tsx +0 -70
- package/src/OverlayManager.tsx +0 -87
- package/src/useShortKitCarousel.ts +0 -29
package/src/ShortKitProvider.tsx
CHANGED
|
@@ -6,7 +6,8 @@ import type { ShortKitContextValue } from './ShortKitContext';
|
|
|
6
6
|
import type {
|
|
7
7
|
ShortKitProviderProps,
|
|
8
8
|
ContentItem,
|
|
9
|
-
|
|
9
|
+
FeedConfig,
|
|
10
|
+
FeedFilter,
|
|
10
11
|
FeedInput,
|
|
11
12
|
PlayerTime,
|
|
12
13
|
PlayerState,
|
|
@@ -16,13 +17,13 @@ import type {
|
|
|
16
17
|
StoryboardData,
|
|
17
18
|
} from './types';
|
|
18
19
|
import {
|
|
19
|
-
|
|
20
|
-
serializeFeedInputs,
|
|
20
|
+
serializeFeedConfig,
|
|
21
21
|
deserializePlayerState,
|
|
22
22
|
deserializeContentItem,
|
|
23
23
|
deserializePlayerTime,
|
|
24
24
|
} from './serialization';
|
|
25
25
|
import NativeShortKitModule from './specs/NativeShortKitModule';
|
|
26
|
+
import { registerLoadingComponent } from './ShortKitLoadingSurface';
|
|
26
27
|
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
28
29
|
// State & Reducer
|
|
@@ -31,7 +32,6 @@ import NativeShortKitModule from './specs/NativeShortKitModule';
|
|
|
31
32
|
interface State {
|
|
32
33
|
playerState: PlayerState;
|
|
33
34
|
currentItem: ContentItem | null;
|
|
34
|
-
nextItem: ContentItem | null;
|
|
35
35
|
time: PlayerTime;
|
|
36
36
|
isMuted: boolean;
|
|
37
37
|
playbackRate: number;
|
|
@@ -39,22 +39,13 @@ interface State {
|
|
|
39
39
|
activeCaptionTrack: CaptionTrack | null;
|
|
40
40
|
activeCue: { text: string; startTime: number; endTime: number } | null;
|
|
41
41
|
prefetchedAheadCount: number;
|
|
42
|
-
remainingContentCount: number;
|
|
43
42
|
isActive: boolean;
|
|
44
43
|
feedScrollPhase: FeedScrollPhase | null;
|
|
45
|
-
lastOverlayTap: number;
|
|
46
|
-
lastOverlayDoubleTap: { x: number; y: number; id: number } | null;
|
|
47
|
-
currentCarouselItem: ImageCarouselItem | null;
|
|
48
|
-
nextCarouselItem: ImageCarouselItem | null;
|
|
49
|
-
isCarouselActive: boolean;
|
|
50
|
-
currentCarouselPage: number;
|
|
51
|
-
activeCellType: 'video' | 'carousel' | null;
|
|
52
44
|
}
|
|
53
45
|
|
|
54
46
|
const initialState: State = {
|
|
55
47
|
playerState: 'idle',
|
|
56
48
|
currentItem: null,
|
|
57
|
-
nextItem: null,
|
|
58
49
|
time: { current: 0, duration: 0, buffered: 0 },
|
|
59
50
|
isMuted: true,
|
|
60
51
|
playbackRate: 1.0,
|
|
@@ -62,16 +53,8 @@ const initialState: State = {
|
|
|
62
53
|
activeCaptionTrack: null,
|
|
63
54
|
activeCue: null,
|
|
64
55
|
prefetchedAheadCount: 0,
|
|
65
|
-
remainingContentCount: 0,
|
|
66
56
|
isActive: false,
|
|
67
57
|
feedScrollPhase: null,
|
|
68
|
-
lastOverlayTap: 0,
|
|
69
|
-
lastOverlayDoubleTap: null,
|
|
70
|
-
currentCarouselItem: null,
|
|
71
|
-
nextCarouselItem: null,
|
|
72
|
-
isCarouselActive: false,
|
|
73
|
-
currentCarouselPage: 0,
|
|
74
|
-
activeCellType: null,
|
|
75
58
|
};
|
|
76
59
|
|
|
77
60
|
type Action =
|
|
@@ -84,38 +67,20 @@ type Action =
|
|
|
84
67
|
| { type: 'CAPTION_TRACK'; payload: CaptionTrack | null }
|
|
85
68
|
| { type: 'CUE'; payload: { text: string; startTime: number; endTime: number } | null }
|
|
86
69
|
| { type: 'PREFETCH_COUNT'; payload: number }
|
|
87
|
-
| { type: 'REMAINING_COUNT'; payload: number }
|
|
88
|
-
| { type: 'OVERLAY_CONFIGURE'; payload: ContentItem }
|
|
89
|
-
| { type: 'OVERLAY_ACTIVATE'; payload: ContentItem }
|
|
90
|
-
| { type: 'OVERLAY_RESET' }
|
|
91
|
-
| { type: 'OVERLAY_TAP' }
|
|
92
|
-
| { type: 'OVERLAY_DOUBLE_TAP'; payload: { x: number; y: number } }
|
|
93
|
-
| { type: 'ACTIVE_CELL_TYPE'; payload: 'video' | 'carousel' | null }
|
|
94
|
-
| { type: 'FEED_TRANSITION_ENDED'; payload: { isVideo: boolean } }
|
|
95
|
-
| { type: 'CAROUSEL_OVERLAY_CONFIGURE'; payload: ImageCarouselItem }
|
|
96
|
-
| { type: 'CAROUSEL_OVERLAY_ACTIVATE'; payload: ImageCarouselItem }
|
|
97
|
-
| { type: 'CAROUSEL_OVERLAY_RESET' }
|
|
98
|
-
| { type: 'CAROUSEL_PAGE_CHANGED'; payload: number }
|
|
99
70
|
| { type: 'FEED_SCROLL_PHASE'; payload: FeedScrollPhase };
|
|
100
71
|
|
|
101
72
|
function reducer(state: State, action: Action): State {
|
|
102
73
|
switch (action.type) {
|
|
103
74
|
case 'PLAYER_STATE': {
|
|
104
|
-
// Only allow isActive to transition TO true from player state.
|
|
105
|
-
// Once active, the overlay stays mounted — hiding during transient
|
|
106
|
-
// states like "loading" between videos would cause a visible flash.
|
|
107
75
|
const isPlaybackActive =
|
|
108
76
|
action.payload === 'playing' ||
|
|
109
77
|
action.payload === 'paused' ||
|
|
110
78
|
action.payload === 'buffering' ||
|
|
111
79
|
action.payload === 'seeking';
|
|
112
|
-
const becameActive = !state.isActive && isPlaybackActive;
|
|
113
80
|
return {
|
|
114
81
|
...state,
|
|
115
82
|
playerState: action.payload,
|
|
116
83
|
isActive: state.isActive || isPlaybackActive,
|
|
117
|
-
// First playback means a video cell is active (initial load)
|
|
118
|
-
activeCellType: becameActive ? 'video' : state.activeCellType,
|
|
119
84
|
};
|
|
120
85
|
}
|
|
121
86
|
case 'CURRENT_ITEM':
|
|
@@ -134,84 +99,6 @@ function reducer(state: State, action: Action): State {
|
|
|
134
99
|
return { ...state, activeCue: action.payload };
|
|
135
100
|
case 'PREFETCH_COUNT':
|
|
136
101
|
return { ...state, prefetchedAheadCount: action.payload };
|
|
137
|
-
case 'REMAINING_COUNT':
|
|
138
|
-
return { ...state, remainingContentCount: action.payload };
|
|
139
|
-
case 'OVERLAY_CONFIGURE':
|
|
140
|
-
return { ...state, nextItem: action.payload };
|
|
141
|
-
case 'OVERLAY_ACTIVATE':
|
|
142
|
-
// Clear nextItem so overlay-next doesn't show stale data from the
|
|
143
|
-
// handleSwipe re-configure. The next OVERLAY_CONFIGURE (from UIKit
|
|
144
|
-
// prefetching cell N+2) will set it to the correct upcoming item.
|
|
145
|
-
// Also clear carousel state — a video cell is now active.
|
|
146
|
-
console.log('[ShortKitProvider] OVERLAY_ACTIVATE (video):', action.payload.playbackId);
|
|
147
|
-
return {
|
|
148
|
-
...state,
|
|
149
|
-
currentItem: action.payload,
|
|
150
|
-
isActive: true,
|
|
151
|
-
time: { current: 0, duration: action.payload.duration ?? 0, buffered: 0 },
|
|
152
|
-
nextItem: null,
|
|
153
|
-
currentCarouselItem: null,
|
|
154
|
-
isCarouselActive: false,
|
|
155
|
-
nextCarouselItem: null,
|
|
156
|
-
currentCarouselPage: 0,
|
|
157
|
-
};
|
|
158
|
-
case 'OVERLAY_RESET':
|
|
159
|
-
// Don't set isActive = false — the overlay stays mounted during
|
|
160
|
-
// transitions. In the native SDK each cell has its own overlay
|
|
161
|
-
// instance, so there's no gap. We replicate this by keeping the
|
|
162
|
-
// single JS overlay mounted and updating its content on activate.
|
|
163
|
-
console.log('[ShortKitProvider] OVERLAY_RESET (video)');
|
|
164
|
-
return { ...state, feedScrollPhase: null };
|
|
165
|
-
case 'OVERLAY_TAP':
|
|
166
|
-
return { ...state, lastOverlayTap: state.lastOverlayTap + 1 };
|
|
167
|
-
case 'OVERLAY_DOUBLE_TAP':
|
|
168
|
-
return {
|
|
169
|
-
...state,
|
|
170
|
-
lastOverlayDoubleTap: {
|
|
171
|
-
x: action.payload.x,
|
|
172
|
-
y: action.payload.y,
|
|
173
|
-
id: (state.lastOverlayDoubleTap?.id ?? 0) + 1,
|
|
174
|
-
},
|
|
175
|
-
};
|
|
176
|
-
case 'ACTIVE_CELL_TYPE':
|
|
177
|
-
console.log('[ShortKitProvider] ACTIVE_CELL_TYPE:', action.payload);
|
|
178
|
-
return { ...state, activeCellType: action.payload };
|
|
179
|
-
case 'FEED_TRANSITION_ENDED': {
|
|
180
|
-
if (action.payload.isVideo) {
|
|
181
|
-
return {
|
|
182
|
-
...state,
|
|
183
|
-
activeCellType: 'video',
|
|
184
|
-
currentCarouselItem: null,
|
|
185
|
-
isCarouselActive: false,
|
|
186
|
-
nextCarouselItem: null,
|
|
187
|
-
currentCarouselPage: 0,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
// Non-video destination. If a carousel configure was received, activate it.
|
|
191
|
-
if (state.nextCarouselItem) {
|
|
192
|
-
console.log('[ShortKitProvider] FEED_TRANSITION_ENDED → carousel activate:', state.nextCarouselItem.id);
|
|
193
|
-
return {
|
|
194
|
-
...state,
|
|
195
|
-
activeCellType: 'carousel',
|
|
196
|
-
currentCarouselItem: state.nextCarouselItem,
|
|
197
|
-
isCarouselActive: true,
|
|
198
|
-
nextCarouselItem: null,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
// Unknown non-video type (survey, ad, etc.)
|
|
202
|
-
return { ...state, activeCellType: null };
|
|
203
|
-
}
|
|
204
|
-
case 'CAROUSEL_OVERLAY_CONFIGURE':
|
|
205
|
-
console.log('[ShortKitProvider] CAROUSEL_OVERLAY_CONFIGURE:', action.payload.id);
|
|
206
|
-
return { ...state, nextCarouselItem: action.payload };
|
|
207
|
-
case 'CAROUSEL_OVERLAY_ACTIVATE':
|
|
208
|
-
console.log('[ShortKitProvider] CAROUSEL_OVERLAY_ACTIVATE:', action.payload.id);
|
|
209
|
-
return { ...state, currentCarouselItem: action.payload, isCarouselActive: true, nextCarouselItem: null };
|
|
210
|
-
case 'CAROUSEL_OVERLAY_RESET':
|
|
211
|
-
console.log('[ShortKitProvider] CAROUSEL_OVERLAY_RESET');
|
|
212
|
-
return { ...state, currentCarouselItem: null, isCarouselActive: false, nextCarouselItem: null, currentCarouselPage: 0 };
|
|
213
|
-
case 'CAROUSEL_PAGE_CHANGED':
|
|
214
|
-
return { ...state, currentCarouselPage: action.payload };
|
|
215
102
|
case 'FEED_SCROLL_PHASE':
|
|
216
103
|
return { ...state, feedScrollPhase: action.payload };
|
|
217
104
|
default:
|
|
@@ -225,11 +112,11 @@ function reducer(state: State, action: Action): State {
|
|
|
225
112
|
|
|
226
113
|
export function ShortKitProvider({
|
|
227
114
|
apiKey,
|
|
228
|
-
config,
|
|
229
115
|
userId,
|
|
230
116
|
clientAppName,
|
|
231
117
|
clientAppVersion,
|
|
232
118
|
customDimensions,
|
|
119
|
+
loadingViewComponent,
|
|
233
120
|
children,
|
|
234
121
|
}: ShortKitProviderProps): React.JSX.Element {
|
|
235
122
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
@@ -241,14 +128,17 @@ export function ShortKitProvider({
|
|
|
241
128
|
useEffect(() => {
|
|
242
129
|
if (!NativeShortKitModule) return;
|
|
243
130
|
|
|
244
|
-
|
|
131
|
+
if (loadingViewComponent) {
|
|
132
|
+
registerLoadingComponent(loadingViewComponent);
|
|
133
|
+
}
|
|
134
|
+
|
|
245
135
|
const serializedDimensions = customDimensions
|
|
246
136
|
? JSON.stringify(customDimensions)
|
|
247
137
|
: undefined;
|
|
248
138
|
|
|
249
139
|
NativeShortKitModule.initialize(
|
|
250
140
|
apiKey,
|
|
251
|
-
|
|
141
|
+
!!loadingViewComponent,
|
|
252
142
|
clientAppName,
|
|
253
143
|
clientAppVersion,
|
|
254
144
|
serializedDimensions,
|
|
@@ -341,6 +231,7 @@ export function ShortKitProvider({
|
|
|
341
231
|
author: event.author,
|
|
342
232
|
articleUrl: event.articleUrl,
|
|
343
233
|
commentCount: event.commentCount,
|
|
234
|
+
fallbackUrl: event.fallbackUrl,
|
|
344
235
|
};
|
|
345
236
|
dispatch({ type: 'CURRENT_ITEM', payload: item });
|
|
346
237
|
}),
|
|
@@ -409,96 +300,11 @@ export function ShortKitProvider({
|
|
|
409
300
|
}),
|
|
410
301
|
);
|
|
411
302
|
|
|
412
|
-
//
|
|
413
|
-
subscriptions.push(
|
|
414
|
-
NativeShortKitModule.onRemainingContentCountChanged((event) => {
|
|
415
|
-
dispatch({ type: 'REMAINING_COUNT', payload: event.count });
|
|
416
|
-
}),
|
|
417
|
-
);
|
|
418
|
-
|
|
419
|
-
// Overlay lifecycle events
|
|
420
|
-
subscriptions.push(
|
|
421
|
-
NativeShortKitModule.onOverlayConfigure((event) => {
|
|
422
|
-
const item = deserializeContentItem(event.item);
|
|
423
|
-
if (item) {
|
|
424
|
-
dispatch({ type: 'OVERLAY_CONFIGURE', payload: item });
|
|
425
|
-
}
|
|
426
|
-
}),
|
|
427
|
-
);
|
|
428
|
-
|
|
429
|
-
subscriptions.push(
|
|
430
|
-
NativeShortKitModule.onOverlayActivate((event) => {
|
|
431
|
-
const item = deserializeContentItem(event.item);
|
|
432
|
-
if (item) {
|
|
433
|
-
dispatch({ type: 'OVERLAY_ACTIVATE', payload: item });
|
|
434
|
-
}
|
|
435
|
-
}),
|
|
436
|
-
);
|
|
437
|
-
|
|
438
|
-
subscriptions.push(
|
|
439
|
-
NativeShortKitModule.onOverlayReset((_event) => {
|
|
440
|
-
dispatch({ type: 'OVERLAY_RESET' });
|
|
441
|
-
}),
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
// Overlay tap events
|
|
445
|
-
subscriptions.push(
|
|
446
|
-
NativeShortKitModule.onOverlayTap((_event) => {
|
|
447
|
-
dispatch({ type: 'OVERLAY_TAP' });
|
|
448
|
-
}),
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
subscriptions.push(
|
|
452
|
-
NativeShortKitModule.onOverlayDoubleTap((event) => {
|
|
453
|
-
dispatch({
|
|
454
|
-
type: 'OVERLAY_DOUBLE_TAP',
|
|
455
|
-
payload: { x: event.x, y: event.y },
|
|
456
|
-
});
|
|
457
|
-
}),
|
|
458
|
-
);
|
|
459
|
-
|
|
460
|
-
// Feed transition — track active cell type
|
|
461
|
-
subscriptions.push(
|
|
462
|
-
NativeShortKitModule.onFeedTransition((event) => {
|
|
463
|
-
console.log('[ShortKitProvider] onFeedTransition:', event.phase, 'toItem:', event.toItem != null ? 'video' : 'non-video');
|
|
464
|
-
if (event.phase === 'ended') {
|
|
465
|
-
// toItem is null when the destination cell is non-video (carousel, survey, ad).
|
|
466
|
-
// The reducer checks nextCarouselItem to distinguish carousel from other types.
|
|
467
|
-
const isVideo = event.toItem != null;
|
|
468
|
-
dispatch({
|
|
469
|
-
type: 'FEED_TRANSITION_ENDED',
|
|
470
|
-
payload: { isVideo },
|
|
471
|
-
});
|
|
472
|
-
}
|
|
473
|
-
}),
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
// Carousel overlay lifecycle events
|
|
477
|
-
subscriptions.push(
|
|
478
|
-
NativeShortKitModule.onCarouselOverlayConfigure((event) => {
|
|
479
|
-
try {
|
|
480
|
-
const item = JSON.parse(event.item) as ImageCarouselItem;
|
|
481
|
-
dispatch({ type: 'CAROUSEL_OVERLAY_CONFIGURE', payload: item });
|
|
482
|
-
} catch {
|
|
483
|
-
// Ignore malformed JSON
|
|
484
|
-
}
|
|
485
|
-
}),
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
subscriptions.push(
|
|
489
|
-
NativeShortKitModule.onCarouselOverlayActivate((event) => {
|
|
490
|
-
try {
|
|
491
|
-
const item = JSON.parse(event.item) as ImageCarouselItem;
|
|
492
|
-
dispatch({ type: 'CAROUSEL_OVERLAY_ACTIVATE', payload: item });
|
|
493
|
-
} catch {
|
|
494
|
-
// Ignore malformed JSON
|
|
495
|
-
}
|
|
496
|
-
}),
|
|
497
|
-
);
|
|
498
|
-
|
|
303
|
+
// Feed transition
|
|
499
304
|
subscriptions.push(
|
|
500
|
-
NativeShortKitModule.
|
|
501
|
-
|
|
305
|
+
NativeShortKitModule.onFeedTransition((_event) => {
|
|
306
|
+
// Feed transition events are consumed by ShortKitFeed callback props.
|
|
307
|
+
// No state to update here in the simplified provider.
|
|
502
308
|
}),
|
|
503
309
|
);
|
|
504
310
|
|
|
@@ -520,7 +326,7 @@ export function ShortKitProvider({
|
|
|
520
326
|
);
|
|
521
327
|
|
|
522
328
|
// Note: Feed-level callback events (onDidLoop, onFeedTransition,
|
|
523
|
-
//
|
|
329
|
+
// onDismiss, etc.) are subscribed by the ShortKitFeed component
|
|
524
330
|
// not here. The provider only manages state-driving events.
|
|
525
331
|
|
|
526
332
|
return () => {
|
|
@@ -530,40 +336,6 @@ export function ShortKitProvider({
|
|
|
530
336
|
};
|
|
531
337
|
}, []);
|
|
532
338
|
|
|
533
|
-
// -----------------------------------------------------------------------
|
|
534
|
-
// Overlay ready signal — tell native it's safe to swap overlay transforms.
|
|
535
|
-
// This fires after React has re-rendered overlay-current with the new
|
|
536
|
-
// content item (and the overlay component has reset its opacity).
|
|
537
|
-
// Child effects (e.g. NewsOverlay's content-change reset) fire before
|
|
538
|
-
// this parent effect, guaranteeing the overlay is visually ready.
|
|
539
|
-
// -----------------------------------------------------------------------
|
|
540
|
-
const currentItemIdRef = useRef<string | null>(null);
|
|
541
|
-
useEffect(() => {
|
|
542
|
-
const newId = state.currentItem?.id ?? null;
|
|
543
|
-
if (newId && newId !== currentItemIdRef.current) {
|
|
544
|
-
currentItemIdRef.current = newId;
|
|
545
|
-
NativeShortKitModule?.notifyOverlayReady();
|
|
546
|
-
}
|
|
547
|
-
}, [state.currentItem?.id]);
|
|
548
|
-
|
|
549
|
-
// Carousel overlay ready signal — same mechanism as video overlays.
|
|
550
|
-
const currentCarouselIdRef = useRef<string | null>(null);
|
|
551
|
-
useEffect(() => {
|
|
552
|
-
const newId = state.currentCarouselItem?.id ?? null;
|
|
553
|
-
if (newId && newId !== currentCarouselIdRef.current) {
|
|
554
|
-
currentCarouselIdRef.current = newId;
|
|
555
|
-
NativeShortKitModule?.notifyOverlayReady();
|
|
556
|
-
}
|
|
557
|
-
}, [state.currentCarouselItem?.id]);
|
|
558
|
-
|
|
559
|
-
// Safety net: if a carousel configure arrives before any feed transition
|
|
560
|
-
// (e.g. the feed starts on a carousel cell), activate after one render cycle.
|
|
561
|
-
useEffect(() => {
|
|
562
|
-
if (state.nextCarouselItem && !state.currentCarouselItem && !state.activeCellType) {
|
|
563
|
-
dispatch({ type: 'CAROUSEL_OVERLAY_ACTIVATE', payload: state.nextCarouselItem });
|
|
564
|
-
}
|
|
565
|
-
}, [state.nextCarouselItem, state.currentCarouselItem, state.activeCellType]);
|
|
566
|
-
|
|
567
339
|
// -----------------------------------------------------------------------
|
|
568
340
|
// Commands (stable refs)
|
|
569
341
|
// -----------------------------------------------------------------------
|
|
@@ -623,17 +395,10 @@ export function ShortKitProvider({
|
|
|
623
395
|
NativeShortKitModule?.clearUserId();
|
|
624
396
|
}, []);
|
|
625
397
|
|
|
626
|
-
const
|
|
627
|
-
NativeShortKitModule?.setFeedItems(serializeFeedInputs(items));
|
|
628
|
-
}, []);
|
|
629
|
-
|
|
630
|
-
const appendFeedItemsCmd = useCallback((items: FeedInput[]) => {
|
|
631
|
-
NativeShortKitModule?.appendFeedItems(serializeFeedInputs(items));
|
|
632
|
-
}, []);
|
|
633
|
-
|
|
634
|
-
const fetchContentCmd = useCallback(async (limit: number = 10): Promise<ContentItem[]> => {
|
|
398
|
+
const fetchContentCmd = useCallback(async (limit: number = 10, filter?: FeedFilter): Promise<ContentItem[]> => {
|
|
635
399
|
if (!NativeShortKitModule) return [];
|
|
636
|
-
const
|
|
400
|
+
const filterJSON = filter ? JSON.stringify(filter) : null;
|
|
401
|
+
const json = await NativeShortKitModule.fetchContent(limit, filterJSON);
|
|
637
402
|
try {
|
|
638
403
|
return JSON.parse(json) as ContentItem[];
|
|
639
404
|
} catch {
|
|
@@ -656,6 +421,13 @@ export function ShortKitProvider({
|
|
|
656
421
|
}
|
|
657
422
|
}, []);
|
|
658
423
|
|
|
424
|
+
const preloadFeedCmd = useCallback(async (config?: Partial<FeedConfig>, items?: FeedInput[]): Promise<string> => {
|
|
425
|
+
if (!NativeShortKitModule) return '';
|
|
426
|
+
const configJSON = config ? serializeFeedConfig(config as FeedConfig) : '{}';
|
|
427
|
+
const itemsJSON = items ? JSON.stringify(items) : null;
|
|
428
|
+
return NativeShortKitModule.preloadFeed(configJSON, itemsJSON);
|
|
429
|
+
}, []);
|
|
430
|
+
|
|
659
431
|
// -----------------------------------------------------------------------
|
|
660
432
|
// Context value (memoized to avoid unnecessary re-renders)
|
|
661
433
|
// -----------------------------------------------------------------------
|
|
@@ -664,7 +436,6 @@ export function ShortKitProvider({
|
|
|
664
436
|
// State
|
|
665
437
|
playerState: state.playerState,
|
|
666
438
|
currentItem: state.currentItem,
|
|
667
|
-
nextItem: state.nextItem,
|
|
668
439
|
time: state.time,
|
|
669
440
|
isMuted: state.isMuted,
|
|
670
441
|
playbackRate: state.playbackRate,
|
|
@@ -672,11 +443,8 @@ export function ShortKitProvider({
|
|
|
672
443
|
activeCaptionTrack: state.activeCaptionTrack,
|
|
673
444
|
activeCue: state.activeCue,
|
|
674
445
|
prefetchedAheadCount: state.prefetchedAheadCount,
|
|
675
|
-
remainingContentCount: state.remainingContentCount,
|
|
676
446
|
isActive: state.isActive,
|
|
677
447
|
feedScrollPhase: state.feedScrollPhase,
|
|
678
|
-
lastOverlayTap: state.lastOverlayTap,
|
|
679
|
-
lastOverlayDoubleTap: state.lastOverlayDoubleTap,
|
|
680
448
|
|
|
681
449
|
// Commands
|
|
682
450
|
play,
|
|
@@ -693,28 +461,14 @@ export function ShortKitProvider({
|
|
|
693
461
|
setMaxBitrate,
|
|
694
462
|
setUserId: setUserIdCmd,
|
|
695
463
|
clearUserId: clearUserIdCmd,
|
|
696
|
-
setFeedItems: setFeedItemsCmd,
|
|
697
|
-
appendFeedItems: appendFeedItemsCmd,
|
|
698
464
|
fetchContent: fetchContentCmd,
|
|
465
|
+
preloadFeed: preloadFeedCmd,
|
|
699
466
|
prefetchStoryboard: prefetchStoryboardCmd,
|
|
700
467
|
getStoryboardData: getStoryboardDataCmd,
|
|
701
|
-
|
|
702
|
-
// Active cell type
|
|
703
|
-
activeCellType: state.activeCellType,
|
|
704
|
-
|
|
705
|
-
// Carousel overlay state
|
|
706
|
-
currentCarouselItem: state.currentCarouselItem,
|
|
707
|
-
nextCarouselItem: state.nextCarouselItem,
|
|
708
|
-
isCarouselActive: state.isCarouselActive,
|
|
709
|
-
currentCarouselPage: state.currentCarouselPage,
|
|
710
|
-
// Internal — consumed by ShortKitFeed to pass to OverlayManager
|
|
711
|
-
_overlayConfig: config.overlay ?? 'none',
|
|
712
|
-
_carouselOverlayConfig: config.carouselOverlay ?? 'none',
|
|
713
468
|
}),
|
|
714
469
|
[
|
|
715
470
|
state.playerState,
|
|
716
471
|
state.currentItem,
|
|
717
|
-
state.nextItem,
|
|
718
472
|
state.time,
|
|
719
473
|
state.isMuted,
|
|
720
474
|
state.playbackRate,
|
|
@@ -722,16 +476,8 @@ export function ShortKitProvider({
|
|
|
722
476
|
state.activeCaptionTrack,
|
|
723
477
|
state.activeCue,
|
|
724
478
|
state.prefetchedAheadCount,
|
|
725
|
-
state.remainingContentCount,
|
|
726
479
|
state.isActive,
|
|
727
480
|
state.feedScrollPhase,
|
|
728
|
-
state.lastOverlayTap,
|
|
729
|
-
state.lastOverlayDoubleTap,
|
|
730
|
-
state.activeCellType,
|
|
731
|
-
state.currentCarouselItem,
|
|
732
|
-
state.nextCarouselItem,
|
|
733
|
-
state.isCarouselActive,
|
|
734
|
-
state.currentCarouselPage,
|
|
735
481
|
play,
|
|
736
482
|
pause,
|
|
737
483
|
seek,
|
|
@@ -746,13 +492,10 @@ export function ShortKitProvider({
|
|
|
746
492
|
setMaxBitrate,
|
|
747
493
|
setUserIdCmd,
|
|
748
494
|
clearUserIdCmd,
|
|
749
|
-
setFeedItemsCmd,
|
|
750
|
-
appendFeedItemsCmd,
|
|
751
495
|
fetchContentCmd,
|
|
496
|
+
preloadFeedCmd,
|
|
752
497
|
prefetchStoryboardCmd,
|
|
753
498
|
getStoryboardDataCmd,
|
|
754
|
-
config.overlay,
|
|
755
|
-
config.carouselOverlay,
|
|
756
499
|
],
|
|
757
500
|
);
|
|
758
501
|
|
package/src/index.ts
CHANGED
|
@@ -4,10 +4,10 @@ export { ShortKitPlayer } from './ShortKitPlayer';
|
|
|
4
4
|
export { ShortKitWidget } from './ShortKitWidget';
|
|
5
5
|
export { useShortKitPlayer } from './useShortKitPlayer';
|
|
6
6
|
export { useShortKit } from './useShortKit';
|
|
7
|
-
export { useShortKitCarousel } from './useShortKitCarousel';
|
|
8
7
|
export type {
|
|
9
8
|
FeedConfig,
|
|
10
9
|
FeedHeight,
|
|
10
|
+
ScrollAxis,
|
|
11
11
|
FeedSource,
|
|
12
12
|
OverlayConfig,
|
|
13
13
|
CarouselOverlayConfig,
|
|
@@ -31,13 +31,19 @@ export type {
|
|
|
31
31
|
FormatChangeEvent,
|
|
32
32
|
ContentSignal,
|
|
33
33
|
SurveyOption,
|
|
34
|
-
|
|
34
|
+
FeedFilter,
|
|
35
|
+
CaptionSource,
|
|
35
36
|
ShortKitProviderProps,
|
|
36
37
|
ShortKitFeedProps,
|
|
38
|
+
ShortKitFeedHandle,
|
|
37
39
|
ShortKitPlayerProps,
|
|
38
40
|
ShortKitWidgetProps,
|
|
39
41
|
ShortKitPlayerState,
|
|
40
42
|
StoryboardData,
|
|
41
43
|
StoryboardTile,
|
|
42
44
|
} from './types';
|
|
43
|
-
export
|
|
45
|
+
export { ShortKitCommands } from './ShortKitCommands';
|
|
46
|
+
export { default as NativeShortKitModule } from './specs/NativeShortKitModule';
|
|
47
|
+
export { registerOverlayComponent } from './ShortKitOverlaySurface';
|
|
48
|
+
export { registerCarouselOverlayComponent } from './ShortKitCarouselOverlaySurface';
|
|
49
|
+
export type { OverlayProps, CarouselOverlayProps } from './types';
|
package/src/serialization.ts
CHANGED
|
@@ -7,54 +7,35 @@ import type {
|
|
|
7
7
|
} from './types';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
* Used by ShortKitFeedView native component props.
|
|
12
|
-
*/
|
|
13
|
-
export interface SerializedFeedConfig {
|
|
14
|
-
feedHeight: string;
|
|
15
|
-
overlay: string;
|
|
16
|
-
carouselOverlay: string;
|
|
17
|
-
surveyMode: string;
|
|
18
|
-
muteOnStart: boolean;
|
|
19
|
-
feedSource: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Serialize FeedConfig for the bridge.
|
|
10
|
+
* Serialize FeedConfig into a JSON string for the native Fabric view prop.
|
|
24
11
|
* Complex union types are JSON-stringified; the `component` field on custom
|
|
25
12
|
* overlays is stripped because React component references cannot cross the bridge.
|
|
13
|
+
* The overlay `name` is included so native can look up the correct Fabric surface.
|
|
26
14
|
*/
|
|
27
|
-
export function serializeFeedConfig(config: FeedConfig):
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
// Strip the component ref — it stays on the JS side for OverlayManager
|
|
34
|
-
overlay = JSON.stringify({ type: 'custom' });
|
|
35
|
-
}
|
|
15
|
+
export function serializeFeedConfig(config: FeedConfig): string {
|
|
16
|
+
const overlay = (() => {
|
|
17
|
+
const raw = config.overlay ?? 'none';
|
|
18
|
+
if (typeof raw === 'string') return JSON.stringify(raw);
|
|
19
|
+
return JSON.stringify({ type: 'custom', name: raw.name });
|
|
20
|
+
})();
|
|
36
21
|
|
|
37
|
-
|
|
22
|
+
const carouselOverlay = (() => {
|
|
23
|
+
const raw = config.carouselOverlay ?? 'none';
|
|
24
|
+
if (typeof raw === 'string') return JSON.stringify(raw);
|
|
25
|
+
return JSON.stringify({ type: 'custom', name: raw.name });
|
|
26
|
+
})();
|
|
27
|
+
|
|
28
|
+
return JSON.stringify({
|
|
38
29
|
feedHeight: JSON.stringify(config.feedHeight ?? { type: 'fullscreen' }),
|
|
30
|
+
scrollAxis: config.scrollAxis ?? 'vertical',
|
|
39
31
|
overlay,
|
|
40
|
-
carouselOverlay
|
|
41
|
-
const raw = config.carouselOverlay ?? 'none';
|
|
42
|
-
if (typeof raw === 'string') return JSON.stringify(raw);
|
|
43
|
-
return JSON.stringify({ type: 'custom' });
|
|
44
|
-
})(),
|
|
32
|
+
carouselOverlay,
|
|
45
33
|
surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
|
|
46
34
|
muteOnStart: config.muteOnStart ?? true,
|
|
47
35
|
feedSource: config.feedSource ?? 'algorithmic',
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Serialize a FeedConfig into a single JSON string for the TurboModule
|
|
53
|
-
* `initialize(config:)` parameter.
|
|
54
|
-
*/
|
|
55
|
-
export function serializeFeedConfigForModule(config: FeedConfig): string {
|
|
56
|
-
const serialized = serializeFeedConfig(config);
|
|
57
|
-
return JSON.stringify(serialized);
|
|
36
|
+
autoplay: config.autoplay ?? true,
|
|
37
|
+
filter: config.filter ? JSON.stringify(config.filter) : undefined,
|
|
38
|
+
});
|
|
58
39
|
}
|
|
59
40
|
|
|
60
41
|
/**
|