@shortkitsdk/react-native 0.2.23 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +151 -0
  2. package/android/libs/shortkit-release.aar +0 -0
  3. package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +19 -1
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +10 -7
  5. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +43 -0
  6. package/ios/ReactCarouselOverlayHost.swift +51 -3
  7. package/ios/ReactOverlayHost.swift +67 -7
  8. package/ios/ReactVideoCarouselOverlayHost.swift +181 -19
  9. package/ios/SKFabricSurfaceWrapper.mm +7 -1
  10. package/ios/ShortKitBridge.swift +140 -3
  11. package/ios/ShortKitFeedView.swift +20 -0
  12. package/ios/ShortKitFeedViewManager.mm +1 -0
  13. package/ios/ShortKitModule.mm +56 -0
  14. package/ios/ShortKitSDK.xcframework/Info.plist +5 -5
  15. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
  16. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +4745 -456
  17. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +127 -5
  18. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  19. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +127 -5
  20. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  21. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +9 -9
  22. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
  23. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +4745 -456
  24. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +127 -5
  25. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  26. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +127 -5
  27. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +4745 -456
  28. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +127 -5
  29. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  30. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +127 -5
  31. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  32. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +17 -17
  33. package/package.json +1 -1
  34. package/src/ShortKitCarouselOverlaySurface.tsx +38 -10
  35. package/src/ShortKitCommands.ts +7 -0
  36. package/src/ShortKitContext.ts +6 -0
  37. package/src/ShortKitFeed.tsx +23 -7
  38. package/src/ShortKitOverlaySurface.tsx +59 -23
  39. package/src/ShortKitProvider.tsx +45 -1
  40. package/src/ShortKitVideoCarouselOverlaySurface.tsx +51 -5
  41. package/src/index.ts +4 -0
  42. package/src/serialization.ts +37 -1
  43. package/src/specs/NativeShortKitModule.ts +80 -2
  44. package/src/specs/ShortKitFeedViewNativeComponent.ts +8 -0
  45. package/src/types.ts +71 -2
  46. package/src/useShortKitCarousel.ts +80 -0
@@ -109,6 +109,25 @@ type FeedReadyEvent = Readonly<{
109
109
  feedId: string;
110
110
  }>;
111
111
 
112
+ type DownloadStartedEvent = Readonly<{
113
+ itemId: string;
114
+ }>;
115
+
116
+ type DownloadProgressEvent = Readonly<{
117
+ itemId: string;
118
+ progress: number;
119
+ }>;
120
+
121
+ type DownloadCompletedEvent = Readonly<{
122
+ itemId: string;
123
+ fileUrl: string;
124
+ }>;
125
+
126
+ type DownloadFailedEvent = Readonly<{
127
+ itemId: string;
128
+ error: string;
129
+ }>;
130
+
112
131
  // --- Overlay per-surface event payload types ---
113
132
 
114
133
  type OverlayActiveEvent = Readonly<{
@@ -158,12 +177,38 @@ type CarouselActiveImageEvent = Readonly<{
158
177
  activeImageIndex: Int32;
159
178
  }>;
160
179
 
180
+ type CarouselItemChangedEvent = Readonly<{
181
+ surfaceId: string;
182
+ item: string; // JSON-serialized ImageCarouselItem (with local file:// URLs where cached)
183
+ isActive: boolean;
184
+ activeImageIndex: Int32;
185
+ }>;
186
+
161
187
  type VideoCarouselActiveVideoEvent = Readonly<{
162
188
  surfaceId: string;
163
189
  activeVideo: string;
164
190
  activeVideoIndex: Int32;
165
191
  }>;
166
192
 
193
+ type VideoCarouselItemChangedEvent = Readonly<{
194
+ surfaceId: string;
195
+ carouselItem: string; // JSON-serialized VideoCarouselItem
196
+ activeVideo: string | null; // JSON-serialized ContentItem; null when item has no videos
197
+ activeVideoIndex: Int32;
198
+ isActive: boolean;
199
+ playerState: string;
200
+ isMuted: boolean;
201
+ }>;
202
+
203
+ type CarouselActiveVideoCompletedEvent = Readonly<{
204
+ surfaceId: string;
205
+ contentItem: string; // JSON-serialized ContentItem
206
+ indexInCarousel: Int32;
207
+ carouselItem: string; // JSON-serialized VideoCarouselItem
208
+ wasLast: boolean;
209
+ willAutoAdvance: boolean;
210
+ }>;
211
+
167
212
  type OverlayFullStateEvent = Readonly<{
168
213
  surfaceId: string;
169
214
  isActive: boolean;
@@ -171,8 +216,20 @@ type OverlayFullStateEvent = Readonly<{
171
216
  isMuted: boolean;
172
217
  playbackRate: Double;
173
218
  captionsEnabled: boolean;
174
- activeCue: string;
175
- feedScrollPhase: string;
219
+ activeCue: string | null;
220
+ feedScrollPhase: string | null;
221
+ }>;
222
+
223
+ type OverlayItemChangedEvent = Readonly<{
224
+ surfaceId: string;
225
+ item: string; // JSON-serialized ContentItem
226
+ isActive: boolean;
227
+ playerState: string;
228
+ isMuted: boolean;
229
+ playbackRate: Double;
230
+ captionsEnabled: boolean;
231
+ activeCue: string | null;
232
+ feedScrollPhase: string | null;
176
233
  }>;
177
234
 
178
235
  export interface Spec extends TurboModule {
@@ -198,6 +255,9 @@ export interface Spec extends TurboModule {
198
255
  seekAndPlay(seconds: Double): void;
199
256
  skipToNext(): void;
200
257
  skipToPrevious(): void;
258
+ carouselNext(): boolean;
259
+ carouselPrevious(): boolean;
260
+ carouselSetActiveIndex(index: Int32): boolean;
201
261
  setMuted(muted: boolean): void;
202
262
  setPlaybackRate(rate: Double): void;
203
263
  setCaptionsEnabled(enabled: boolean): void;
@@ -216,6 +276,14 @@ export interface Spec extends TurboModule {
216
276
  prefetchStoryboard(playbackId: string): void;
217
277
  getStoryboardData(playbackId: string): Promise<string>;
218
278
 
279
+ // --- Download ---
280
+ downloadVideo(itemId: string, mode: string): Promise<string>;
281
+ cancelDownload(): void;
282
+
283
+ // --- Carousel accessors ---
284
+ getCarouselActiveIndex(): Int32;
285
+ getCarouselVideoCount(): Int32;
286
+
219
287
  // --- Event emitters ---
220
288
  readonly onPlayerStateChanged: EventEmitter<PlayerStateEvent>;
221
289
  readonly onCurrentItemChanged: EventEmitter<CurrentItemEvent>;
@@ -247,8 +315,18 @@ export interface Spec extends TurboModule {
247
315
  readonly onOverlayFeedScrollPhaseChanged: EventEmitter<OverlayFeedScrollPhaseEvent>;
248
316
  readonly onOverlayTimeUpdate: EventEmitter<OverlayTimeUpdateEvent>;
249
317
  readonly onOverlayFullState: EventEmitter<OverlayFullStateEvent>;
318
+ readonly onOverlayItemChanged: EventEmitter<OverlayItemChangedEvent>;
250
319
  readonly onCarouselActiveImageChanged: EventEmitter<CarouselActiveImageEvent>;
320
+ readonly onCarouselItemChanged: EventEmitter<CarouselItemChangedEvent>;
251
321
  readonly onVideoCarouselActiveVideoChanged: EventEmitter<VideoCarouselActiveVideoEvent>;
322
+ readonly onVideoCarouselItemChanged: EventEmitter<VideoCarouselItemChangedEvent>;
323
+ readonly onCarouselActiveVideoCompleted: EventEmitter<CarouselActiveVideoCompletedEvent>;
324
+
325
+ // --- Download events ---
326
+ readonly onDownloadStarted: EventEmitter<DownloadStartedEvent>;
327
+ readonly onDownloadProgress: EventEmitter<DownloadProgressEvent>;
328
+ readonly onDownloadCompleted: EventEmitter<DownloadCompletedEvent>;
329
+ readonly onDownloadFailed: EventEmitter<DownloadFailedEvent>;
252
330
  }
253
331
 
254
332
  export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
@@ -6,6 +6,14 @@ export interface NativeProps extends ViewProps {
6
6
  feedId?: string;
7
7
  startAtItemId?: string;
8
8
  preloadId?: string;
9
+ /**
10
+ * URL of a thumbnail already rendered/cached on the host side (e.g. the
11
+ * grid tile the user tapped to open this feed). When expo-image or FastImage
12
+ * is used on iOS, SDK fetches the decoded UIImage from SDWebImage's memory
13
+ * cache and paints it onto the first cell with zero network and zero
14
+ * latency. Falls back to network fetch for non-SDWebImage clients.
15
+ */
16
+ seedThumbnailUrl?: string;
9
17
  }
10
18
 
11
19
  export default codegenNativeComponent<NativeProps>(
package/src/types.ts CHANGED
@@ -68,6 +68,21 @@ export interface ContentItem {
68
68
  articleUrl?: string;
69
69
  commentCount?: number;
70
70
  fallbackUrl?: string;
71
+ downloadUrl?: string;
72
+ }
73
+
74
+ export type DownloadStatus = 'idle' | 'downloading' | 'completed' | 'failed';
75
+
76
+ export interface DownloadState {
77
+ status: DownloadStatus;
78
+ /** ID of the content item being downloaded, if any. */
79
+ itemId?: string;
80
+ /** 0.0 to 1.0 progress fraction. */
81
+ progress: number;
82
+ /** Local file URL after successful download. */
83
+ fileUrl?: string;
84
+ /** Error message if download failed. */
85
+ error?: string;
71
86
  }
72
87
 
73
88
  // --- Custom Feed Types ---
@@ -98,10 +113,41 @@ export interface VideoCarouselItem {
98
113
  articleUrl?: string;
99
114
  }
100
115
 
116
+ export type ContentOrigin = 'ios_upload' | 'other';
117
+
118
+ /**
119
+ * Per-slide input for a VideoCarouselInput. Mirrors the `{ type: 'video' }`
120
+ * FeedInput variant — host apps pass a playback ID and the SDK constructs
121
+ * the streaming and thumbnail URLs internally.
122
+ */
123
+ export interface VideoCarouselVideoInput {
124
+ playbackId: string;
125
+ origin?: ContentOrigin;
126
+ fallbackUrl?: string;
127
+ }
128
+
129
+ /**
130
+ * Compact input type for video carousels passed to
131
+ * `{ type: 'videoCarousel', item: VideoCarouselInput }`.
132
+ *
133
+ * The SDK hydrates each playback ID into a full ContentItem internally
134
+ * before rendering. Overlays continue to receive the fully-hydrated
135
+ * VideoCarouselItem shape via VideoCarouselOverlayProps.carouselItem.
136
+ */
137
+ export interface VideoCarouselInput {
138
+ id: string;
139
+ videos: VideoCarouselVideoInput[];
140
+ title?: string;
141
+ description?: string;
142
+ author?: string;
143
+ section?: string;
144
+ articleUrl?: string;
145
+ }
146
+
101
147
  export type FeedInput =
102
- | { type: 'video'; playbackId: string; fallbackUrl?: string }
148
+ | { type: 'video'; playbackId: string; origin?: ContentOrigin; fallbackUrl?: string }
103
149
  | { type: 'imageCarousel'; item: ImageCarouselItem }
104
- | { type: 'videoCarousel'; item: VideoCarouselItem };
150
+ | { type: 'videoCarousel'; item: VideoCarouselInput };
105
151
 
106
152
  export type JSONValue =
107
153
  | string
@@ -257,6 +303,17 @@ export interface ShortKitFeedProps {
257
303
  style?: ViewStyle;
258
304
  /** Item ID to scroll to on initial load. */
259
305
  startAtItemId?: string;
306
+ /**
307
+ * URL of the thumbnail already displayed in your UI (e.g. the grid tile
308
+ * the user just tapped). If your app uses expo-image or FastImage, the SDK
309
+ * will extract the already-decoded UIImage from SDWebImage's memory cache
310
+ * and paint it on the first feed cell with zero network overhead — closing
311
+ * the black-frame gap before video playback begins.
312
+ *
313
+ * Falls back silently (no extra network beyond the existing fallback) for
314
+ * apps on React Native's core <Image> or other image libraries.
315
+ */
316
+ seedThumbnailUrl?: string;
260
317
  onLoop?: (event: LoopEvent) => void;
261
318
  onFeedTransition?: (event: FeedTransitionEvent) => void;
262
319
  onFormatChange?: (event: FormatChangeEvent) => void;
@@ -273,6 +330,15 @@ export interface ShortKitFeedProps {
273
330
  /** Called once when this feed has loaded content and assigned a player
274
331
  * to the first cell. Use to dismiss a splash screen or loading overlay. */
275
332
  onFeedReady?: () => void;
333
+ /** Called when the active video in a video carousel completes playback. */
334
+ onCarouselActiveVideoCompleted?: (event: {
335
+ surfaceId: string;
336
+ contentItem: ContentItem;
337
+ indexInCarousel: number;
338
+ carouselItem: VideoCarouselItem;
339
+ wasLast: boolean;
340
+ willAutoAdvance: boolean;
341
+ }) => void;
276
342
  }
277
343
 
278
344
  /**
@@ -339,6 +405,9 @@ export interface ShortKitWidgetProps {
339
405
 
340
406
  // --- Hook Return Types ---
341
407
 
408
+ export type { ShortKitCarouselState } from './useShortKitCarousel';
409
+
410
+
342
411
  export interface ShortKitPlayerState {
343
412
  playerState: PlayerState;
344
413
  currentItem: ContentItem | null;
@@ -0,0 +1,80 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+ import NativeShortKitModule from './specs/NativeShortKitModule';
3
+ import { ShortKitCommands } from './ShortKitCommands';
4
+ import type { VideoCarouselItem } from './types';
5
+
6
+ export interface ShortKitCarouselState {
7
+ /** Index of the currently active video in the carousel, or null if no carousel is active. */
8
+ activeIndex: number | null;
9
+ /** Number of videos in the active carousel item, or 0 if none. */
10
+ videoCount: number;
11
+ /** The active carousel item, or null if no carousel is active. */
12
+ activeCarouselItem: VideoCarouselItem | null;
13
+ /** Advance to the next video in the carousel. Returns true if the command was dispatched. */
14
+ next: () => boolean;
15
+ /** Go back to the previous video in the carousel. Returns true if the command was dispatched. */
16
+ previous: () => boolean;
17
+ /** Jump to a specific index in the carousel. Returns true if the command was dispatched. */
18
+ setActiveIndex: (index: number) => boolean;
19
+ }
20
+
21
+ /**
22
+ * Hook to access video carousel state and commands.
23
+ *
24
+ * Subscribes to `onVideoCarouselItemChanged` and
25
+ * `onVideoCarouselActiveVideoChanged` from the native TurboModule and surfaces
26
+ * carousel state as reactive values. The imperative commands (`next`,
27
+ * `previous`, `setActiveIndex`) delegate to `ShortKitCommands`.
28
+ *
29
+ * Does not require a `<ShortKitProvider>` — it subscribes directly to native
30
+ * events and is suitable for use inside overlay components.
31
+ */
32
+ export function useShortKitCarousel(): ShortKitCarouselState {
33
+ const [activeIndex, setActiveIndexState] = useState<number | null>(() => {
34
+ const v = NativeShortKitModule?.getCarouselActiveIndex?.() ?? -1;
35
+ return v < 0 ? null : v;
36
+ });
37
+ const [activeCarouselItem, setActiveCarouselItem] = useState<VideoCarouselItem | null>(null);
38
+
39
+ useEffect(() => {
40
+ if (!NativeShortKitModule) return;
41
+ const subItem = NativeShortKitModule.onVideoCarouselItemChanged((e: any) => {
42
+ try {
43
+ const item = e.carouselItem
44
+ ? (JSON.parse(e.carouselItem) as VideoCarouselItem)
45
+ : null;
46
+ setActiveCarouselItem(item);
47
+ setActiveIndexState(
48
+ typeof e.activeVideoIndex === 'number' ? e.activeVideoIndex : null,
49
+ );
50
+ } catch {
51
+ // ignore parse errors
52
+ }
53
+ });
54
+ const subIdx = NativeShortKitModule.onVideoCarouselActiveVideoChanged((e: any) => {
55
+ setActiveIndexState(
56
+ typeof e.activeVideoIndex === 'number' ? e.activeVideoIndex : null,
57
+ );
58
+ });
59
+ return () => {
60
+ subItem?.remove?.();
61
+ subIdx?.remove?.();
62
+ };
63
+ }, []);
64
+
65
+ const next = useCallback(() => ShortKitCommands.carouselNext(), []);
66
+ const previous = useCallback(() => ShortKitCommands.carouselPrevious(), []);
67
+ const setActiveIndex = useCallback(
68
+ (i: number) => ShortKitCommands.carouselSetActiveIndex(i),
69
+ [],
70
+ );
71
+
72
+ return {
73
+ activeIndex,
74
+ videoCount: activeCarouselItem?.videos?.length ?? 0,
75
+ activeCarouselItem,
76
+ next,
77
+ previous,
78
+ setActiveIndex,
79
+ };
80
+ }