@shortkitsdk/react-native 0.2.24 → 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.
- package/README.md +151 -0
- package/android/libs/shortkit-release.aar +0 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +19 -1
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +43 -0
- package/ios/ReactCarouselOverlayHost.swift +51 -3
- package/ios/ReactOverlayHost.swift +67 -7
- package/ios/ReactVideoCarouselOverlayHost.swift +181 -19
- package/ios/SKFabricSurfaceWrapper.mm +7 -1
- package/ios/ShortKitBridge.swift +43 -1
- package/ios/ShortKitFeedView.swift +20 -0
- package/ios/ShortKitFeedViewManager.mm +1 -0
- package/ios/ShortKitModule.mm +33 -0
- package/ios/ShortKitSDK.xcframework/Info.plist +5 -5
- 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 +3590 -382
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +104 -5
- 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 +104 -5
- 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 +3590 -382
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +104 -5
- 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 +104 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +3590 -382
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +104 -5
- 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 +104 -5
- 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/package.json +1 -1
- package/src/ShortKitCarouselOverlaySurface.tsx +38 -10
- package/src/ShortKitCommands.ts +4 -0
- package/src/ShortKitFeed.tsx +23 -7
- package/src/ShortKitOverlaySurface.tsx +59 -23
- package/src/ShortKitVideoCarouselOverlaySurface.tsx +51 -5
- package/src/index.ts +2 -0
- package/src/serialization.ts +37 -1
- package/src/specs/NativeShortKitModule.ts +51 -2
- package/src/specs/ShortKitFeedViewNativeComponent.ts +8 -0
- package/src/types.ts +27 -1
- package/src/useShortKitCarousel.ts +80 -0
|
@@ -13,7 +13,9 @@ import ShortKitSDK
|
|
|
13
13
|
// MARK: - Configuration
|
|
14
14
|
|
|
15
15
|
var surfacePresenter: AnyObject?
|
|
16
|
-
weak var bridge: ShortKitBridge?
|
|
16
|
+
weak var bridge: ShortKitBridge? {
|
|
17
|
+
didSet { subscribeToCarouselCompletion() }
|
|
18
|
+
}
|
|
17
19
|
|
|
18
20
|
/// Module name for the RCTFabricSurface. Set by the overlay factory
|
|
19
21
|
/// based on the feed config's video carousel overlay name.
|
|
@@ -37,16 +39,36 @@ import ShortKitSDK
|
|
|
37
39
|
/// Cached carouselItem JSON — setProperties replaces all props, doesn't merge.
|
|
38
40
|
private var carouselItemJSON: String?
|
|
39
41
|
|
|
42
|
+
/// Currently configured carousel item — used to detect item transitions
|
|
43
|
+
/// so we can emit an event instead of triggering a full Fabric remount.
|
|
44
|
+
private var currentCarouselItem: VideoCarouselItem?
|
|
45
|
+
|
|
46
|
+
/// Whether initial props have been pushed to the surface at least once.
|
|
47
|
+
/// First mount must go through setProperties (for item + surfaceId + initial
|
|
48
|
+
/// state). Subsequent item changes use the event path.
|
|
49
|
+
private var hasPushedInitialProps: Bool = false
|
|
50
|
+
|
|
40
51
|
// Player state
|
|
41
52
|
private var player: ShortKitPlayer?
|
|
42
53
|
private var cancellables = Set<AnyCancellable>()
|
|
54
|
+
/// Separate cancellable set for the carousel-completion subscription.
|
|
55
|
+
/// Kept out of `cancellables` so `subscribeToPlayer`'s removeAll() doesn't
|
|
56
|
+
/// wipe it out — the completion subscription is wired once at bridge didSet
|
|
57
|
+
/// time and must survive player re-attachments.
|
|
58
|
+
private var carouselCompletionCancellables = Set<AnyCancellable>()
|
|
43
59
|
private var isActive = false
|
|
60
|
+
private var isDragging: Bool = false // FIX: track drag phase
|
|
44
61
|
private var cachedPlayerState: String = "idle"
|
|
45
62
|
private var cachedIsMuted: Bool = true
|
|
46
63
|
private var cachedTime: (current: Double, duration: Double, buffered: Double) = (0, 0, 0)
|
|
47
64
|
private var timeCoalesceTimer: Timer?
|
|
48
65
|
private var timeDirty = false
|
|
49
66
|
|
|
67
|
+
// Tracks the last bounds.size pushed to the surface. Used in layoutSubviews
|
|
68
|
+
// to skip redundant setSize calls that would otherwise trigger a Fabric
|
|
69
|
+
// layout recalc on every frame during scroll.
|
|
70
|
+
private var lastLayoutSize: CGSize = .zero
|
|
71
|
+
|
|
50
72
|
// MARK: - Init
|
|
51
73
|
|
|
52
74
|
override init(frame: CGRect) {
|
|
@@ -67,12 +89,21 @@ import ShortKitSDK
|
|
|
67
89
|
// MARK: - VideoCarouselOverlay
|
|
68
90
|
|
|
69
91
|
public func configure(with item: VideoCarouselItem) {
|
|
92
|
+
let isSameItem = item.id == currentCarouselItem?.id
|
|
93
|
+
currentCarouselItem = item
|
|
94
|
+
|
|
70
95
|
isActive = false
|
|
96
|
+
isDragging = false
|
|
71
97
|
timeDirty = false
|
|
72
98
|
timeCoalesceTimer?.invalidate()
|
|
73
99
|
timeCoalesceTimer = nil
|
|
100
|
+
// Reset cached state so recycled cells don't flash stale values from
|
|
101
|
+
// the previous item's player. The new player's publishers will re-emit
|
|
102
|
+
// current values after attach(), so the defaults are only visible for
|
|
103
|
+
// the single frame between configure() and the first publisher tick.
|
|
74
104
|
cachedTime = (0, 0, 0)
|
|
75
105
|
cachedPlayerState = "idle"
|
|
106
|
+
cachedIsMuted = true
|
|
76
107
|
|
|
77
108
|
createSurfaceIfNeeded()
|
|
78
109
|
|
|
@@ -80,6 +111,35 @@ import ShortKitSDK
|
|
|
80
111
|
let json = String(data: data, encoding: .utf8) else { return }
|
|
81
112
|
carouselItemJSON = json
|
|
82
113
|
|
|
114
|
+
// Surface lifecycle on item change:
|
|
115
|
+
// - First mount: pushInitialProperties() via setProperties (unavoidable
|
|
116
|
+
// — React needs props at mount time). ALSO emit
|
|
117
|
+
// onVideoCarouselItemChanged so external subscribers that live
|
|
118
|
+
// outside the overlay's React tree (e.g. useShortKitCarousel() hook
|
|
119
|
+
// consumers) see the initial carousel too. The in-surface subscriber
|
|
120
|
+
// at ShortKitVideoCarouselOverlaySurface.tsx:153 is sid-filtered
|
|
121
|
+
// and sets the same JSON-parsed item, so it's a redundant second
|
|
122
|
+
// set with identical value — safe.
|
|
123
|
+
// - Subsequent item change with surface ready: emit event for React
|
|
124
|
+
// diff (no remount).
|
|
125
|
+
// - Same item: no-op.
|
|
126
|
+
if !hasPushedInitialProps {
|
|
127
|
+
let props = buildInitialProps(item: item, json: json)
|
|
128
|
+
if let surface {
|
|
129
|
+
surface.setProperties(props)
|
|
130
|
+
hasPushedInitialProps = true
|
|
131
|
+
} else {
|
|
132
|
+
pendingProps = props
|
|
133
|
+
}
|
|
134
|
+
emitItemChanged(item: item, json: json)
|
|
135
|
+
} else if !isSameItem {
|
|
136
|
+
emitItemChanged(item: item, json: json)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/// Build the initial-properties dictionary for an item. Used on first mount
|
|
141
|
+
/// (via setProperties) and shadowed by emitItemChanged() on subsequent changes.
|
|
142
|
+
private func buildInitialProps(item: VideoCarouselItem, json: String) -> [String: Any] {
|
|
83
143
|
var props: [String: Any] = [
|
|
84
144
|
"surfaceId": surfaceId,
|
|
85
145
|
"carouselItem": json,
|
|
@@ -93,11 +153,32 @@ import ShortKitSDK
|
|
|
93
153
|
props["activeVideo"] = videoJSON
|
|
94
154
|
props["activeVideoIndex"] = 0
|
|
95
155
|
}
|
|
96
|
-
|
|
97
|
-
|
|
156
|
+
return props
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Emit onVideoCarouselItemChanged with full initial state for the new item.
|
|
160
|
+
/// Replaces setProperties() on cell reuse, avoiding a full Fabric root remount.
|
|
161
|
+
private func emitItemChanged(item: VideoCarouselItem, json: String) {
|
|
162
|
+
var body: [String: Any] = [
|
|
163
|
+
"surfaceId": surfaceId,
|
|
164
|
+
"carouselItem": json,
|
|
165
|
+
"isActive": false,
|
|
166
|
+
"playerState": "idle",
|
|
167
|
+
"isMuted": cachedIsMuted,
|
|
168
|
+
"activeVideoIndex": 0,
|
|
169
|
+
]
|
|
170
|
+
if let firstVideo = item.videos.first,
|
|
171
|
+
let videoData = try? JSONEncoder().encode(firstVideo),
|
|
172
|
+
let videoJSON = String(data: videoData, encoding: .utf8) {
|
|
173
|
+
body["activeVideo"] = videoJSON
|
|
98
174
|
} else {
|
|
99
|
-
|
|
175
|
+
// Match buildInitialProps: omit the key rather than emitting an
|
|
176
|
+
// empty-string sentinel that JS treats as falsy (overlay would
|
|
177
|
+
// disappear on every cell reuse with a zero-video carousel).
|
|
178
|
+
body["activeVideo"] = NSNull()
|
|
100
179
|
}
|
|
180
|
+
|
|
181
|
+
bridge?.emit("onVideoCarouselItemChanged", body: body)
|
|
101
182
|
}
|
|
102
183
|
|
|
103
184
|
public func updateActiveVideo(index: Int, item: ContentItem) {
|
|
@@ -147,17 +228,20 @@ import ShortKitSDK
|
|
|
147
228
|
|
|
148
229
|
private func subscribeToPlayer(_ player: ShortKitPlayer) {
|
|
149
230
|
cancellables.removeAll()
|
|
231
|
+
// FIX: playerState, isMuted, and time are suppressed during .dragging to
|
|
232
|
+
// avoid unnecessary bridge traffic while scrolling. emitFullState() on
|
|
233
|
+
// drag→settled re-syncs cached values.
|
|
234
|
+
|
|
150
235
|
player.playerState
|
|
151
236
|
.receive(on: DispatchQueue.main)
|
|
152
237
|
.sink { [weak self] state in
|
|
153
238
|
guard let self else { return }
|
|
154
239
|
self.cachedPlayerState = Self.playerStateString(state)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
240
|
+
guard self.isActive, !self.isDragging else { return }
|
|
241
|
+
self.bridge?.emit("onOverlayPlayerStateChanged", body: [
|
|
242
|
+
"surfaceId": self.surfaceId,
|
|
243
|
+
"playerState": self.cachedPlayerState
|
|
244
|
+
])
|
|
161
245
|
}
|
|
162
246
|
.store(in: &cancellables)
|
|
163
247
|
|
|
@@ -166,23 +250,90 @@ import ShortKitSDK
|
|
|
166
250
|
.sink { [weak self] muted in
|
|
167
251
|
guard let self else { return }
|
|
168
252
|
self.cachedIsMuted = muted
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
253
|
+
guard self.isActive, !self.isDragging else { return }
|
|
254
|
+
self.bridge?.emit("onOverlayMutedChanged", body: [
|
|
255
|
+
"surfaceId": self.surfaceId,
|
|
256
|
+
"isMuted": self.cachedIsMuted
|
|
257
|
+
])
|
|
175
258
|
}
|
|
176
259
|
.store(in: &cancellables)
|
|
177
260
|
|
|
178
261
|
player.time
|
|
179
262
|
.receive(on: DispatchQueue.main)
|
|
180
263
|
.sink { [weak self] time in
|
|
181
|
-
guard let self, self.isActive else { return }
|
|
264
|
+
guard let self, self.isActive, !self.isDragging else { return }
|
|
182
265
|
self.cachedTime = (time.current, time.duration, time.buffered)
|
|
183
266
|
self.timeDirty = true
|
|
184
267
|
}
|
|
185
268
|
.store(in: &cancellables)
|
|
269
|
+
|
|
270
|
+
// FIX: subscribe to feedScrollPhase so we know when a drag starts/settles.
|
|
271
|
+
// On drag start: invalidate the time coalescing timer.
|
|
272
|
+
// On settle: restart timer and re-sync state via emitFullState().
|
|
273
|
+
player.feedScrollPhase
|
|
274
|
+
.receive(on: DispatchQueue.main)
|
|
275
|
+
.sink { [weak self] phase in
|
|
276
|
+
guard let self else { return }
|
|
277
|
+
switch phase {
|
|
278
|
+
case .dragging:
|
|
279
|
+
self.isDragging = true
|
|
280
|
+
// Stop the time coalescing timer during drags. Time updates
|
|
281
|
+
// are suppressed while isDragging is true, so the timer
|
|
282
|
+
// would just wake the main thread to check timeDirty.
|
|
283
|
+
self.timeCoalesceTimer?.invalidate()
|
|
284
|
+
self.timeCoalesceTimer = nil
|
|
285
|
+
case .settled:
|
|
286
|
+
let wasDragging = self.isDragging
|
|
287
|
+
self.isDragging = false
|
|
288
|
+
if self.isActive {
|
|
289
|
+
self.startTimeCoalescing()
|
|
290
|
+
if wasDragging {
|
|
291
|
+
// Re-sync state that was suppressed during the drag.
|
|
292
|
+
self.emitFullState()
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
.store(in: &cancellables)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// MARK: - Carousel Completion
|
|
301
|
+
|
|
302
|
+
/// Subscribe to the carousel's activeVideoCompleted publisher and emit
|
|
303
|
+
/// `onCarouselActiveVideoCompleted` with this host's surfaceId.
|
|
304
|
+
/// Called via `bridge` didSet so the subscription is wired once per host
|
|
305
|
+
/// instance, mirroring how `onVideoCarouselActiveVideoChanged` is emitted.
|
|
306
|
+
private func subscribeToCarouselCompletion() {
|
|
307
|
+
guard let carousel = bridge?.sdk?.carousel else { return }
|
|
308
|
+
carousel.activeVideoCompleted
|
|
309
|
+
.receive(on: DispatchQueue.main)
|
|
310
|
+
.sink { [weak self] event in
|
|
311
|
+
guard let self, self.isActive,
|
|
312
|
+
event.carouselItem.id == self.currentCarouselItem?.id else { return }
|
|
313
|
+
let contentItemJSON: String
|
|
314
|
+
if let data = try? JSONEncoder().encode(event.contentItem),
|
|
315
|
+
let json = String(data: data, encoding: .utf8) {
|
|
316
|
+
contentItemJSON = json
|
|
317
|
+
} else {
|
|
318
|
+
contentItemJSON = "{}"
|
|
319
|
+
}
|
|
320
|
+
let carouselItemJSON: String
|
|
321
|
+
if let data = try? JSONEncoder().encode(event.carouselItem),
|
|
322
|
+
let json = String(data: data, encoding: .utf8) {
|
|
323
|
+
carouselItemJSON = json
|
|
324
|
+
} else {
|
|
325
|
+
carouselItemJSON = "{}"
|
|
326
|
+
}
|
|
327
|
+
self.bridge?.emit("onCarouselActiveVideoCompleted", body: [
|
|
328
|
+
"surfaceId": self.surfaceId,
|
|
329
|
+
"contentItem": contentItemJSON,
|
|
330
|
+
"indexInCarousel": event.indexInCarousel,
|
|
331
|
+
"carouselItem": carouselItemJSON,
|
|
332
|
+
"wasLast": event.wasLast,
|
|
333
|
+
"willAutoAdvance": event.willAutoAdvance,
|
|
334
|
+
])
|
|
335
|
+
}
|
|
336
|
+
.store(in: &carouselCompletionCancellables)
|
|
186
337
|
}
|
|
187
338
|
|
|
188
339
|
// MARK: - Time Coalescing
|
|
@@ -190,7 +341,10 @@ import ShortKitSDK
|
|
|
190
341
|
private func startTimeCoalescing() {
|
|
191
342
|
timeCoalesceTimer?.invalidate()
|
|
192
343
|
timeCoalesceTimer = Timer.scheduledTimer(withTimeInterval: 0.25, repeats: true) { [weak self] _ in
|
|
193
|
-
guard let self
|
|
344
|
+
guard let self else { return }
|
|
345
|
+
// Timer is invalidated on drag start; this guard is belt-and-suspenders.
|
|
346
|
+
if self.isDragging { return }
|
|
347
|
+
guard self.timeDirty else { return }
|
|
194
348
|
self.timeDirty = false
|
|
195
349
|
self.bridge?.emit("onOverlayTimeUpdate", body: [
|
|
196
350
|
"surfaceId": self.surfaceId,
|
|
@@ -251,6 +405,7 @@ import ShortKitSDK
|
|
|
251
405
|
if let pending = pendingProps {
|
|
252
406
|
surf.setProperties(pending)
|
|
253
407
|
pendingProps = nil
|
|
408
|
+
hasPushedInitialProps = true
|
|
254
409
|
}
|
|
255
410
|
}
|
|
256
411
|
|
|
@@ -261,8 +416,15 @@ import ShortKitSDK
|
|
|
261
416
|
guard let surface else { return }
|
|
262
417
|
let size = bounds.size
|
|
263
418
|
guard size.width > 0, size.height > 0 else { return }
|
|
419
|
+
|
|
420
|
+
// Skip setSize when bounds haven't changed. UICollectionView calls
|
|
421
|
+
// layoutSubviews every frame during scroll; without this guard we'd
|
|
422
|
+
// trigger a Fabric layout recalc each time.
|
|
423
|
+
guard size != lastLayoutSize else { return }
|
|
424
|
+
lastLayoutSize = size
|
|
425
|
+
|
|
264
426
|
surface.setMinimumSize(size)
|
|
265
|
-
|
|
427
|
+
// setMaximumSize is a no-op in SKFabricSurfaceWrapper — not called.
|
|
266
428
|
}
|
|
267
429
|
|
|
268
430
|
// MARK: - Helpers
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
@implementation SKFabricSurfaceWrapper {
|
|
6
6
|
RCTFabricSurface *_surface;
|
|
7
7
|
NSDictionary *_lastProperties;
|
|
8
|
+
CGSize _lastSize;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
- (nullable instancetype)initWithPresenter:(nonnull id)surfacePresenter
|
|
@@ -46,7 +47,12 @@
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
- (void)setMinimumSize:(CGSize)size {
|
|
49
|
-
//
|
|
50
|
+
// Skip when size hasn't changed — belt-and-suspenders guard in case a
|
|
51
|
+
// caller other than the overlay hosts invokes this during scroll.
|
|
52
|
+
if (CGSizeEqualToSize(_lastSize, size)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
_lastSize = size;
|
|
50
56
|
[_surface setSize:size];
|
|
51
57
|
}
|
|
52
58
|
|
package/ios/ShortKitBridge.swift
CHANGED
|
@@ -207,6 +207,44 @@ import ShortKitSDK
|
|
|
207
207
|
shortKit?.player.skipToPrevious()
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
// MARK: - Carousel Commands
|
|
211
|
+
|
|
212
|
+
/// Advance the active carousel to the next video.
|
|
213
|
+
/// Returns NSNumber wrapping Bool: @(YES) on success, @(NO) when no carousel active.
|
|
214
|
+
@objc public func carouselNext() -> NSNumber {
|
|
215
|
+
return NSNumber(value: shortKit?.carousel.next() ?? false)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Move the active carousel to the previous video.
|
|
219
|
+
@objc public func carouselPrevious() -> NSNumber {
|
|
220
|
+
return NSNumber(value: shortKit?.carousel.previous() ?? false)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// Jump the active carousel to a specific zero-based index.
|
|
224
|
+
///
|
|
225
|
+
/// Accepts `Int` (bridged to ObjC `NSInteger` / encoding `q`), matching
|
|
226
|
+
/// what RN TurboModule codegen emits for a TS `Int32` parameter. Declaring
|
|
227
|
+
/// this with `NSNumber *` crashed at the Swift @objc thunk's ARC retain
|
|
228
|
+
/// because the codegen wrote a raw int into the NSInvocation slot, which
|
|
229
|
+
/// the thunk interpreted as an object pointer.
|
|
230
|
+
@objc public func carouselSetActiveIndex(_ index: Int) -> NSNumber {
|
|
231
|
+
return NSNumber(value: shortKit?.carousel.setActiveIndex(index) ?? false)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// MARK: - Carousel Accessors
|
|
235
|
+
|
|
236
|
+
/// Zero-based index of the currently playing carousel video, or -1 when no carousel is active.
|
|
237
|
+
@objc public func getCarouselActiveIndex() -> NSNumber {
|
|
238
|
+
guard let idx = shortKit?.carousel.activeIndexValue else { return NSNumber(value: -1) }
|
|
239
|
+
return NSNumber(value: idx)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// Total number of videos in the active carousel, or 0 when no carousel is active.
|
|
243
|
+
@objc public func getCarouselVideoCount() -> NSNumber {
|
|
244
|
+
guard let count = shortKit?.carousel.videoCountValue else { return NSNumber(value: 0) }
|
|
245
|
+
return NSNumber(value: count)
|
|
246
|
+
}
|
|
247
|
+
|
|
210
248
|
@objc public func setMuted(_ muted: Bool) {
|
|
211
249
|
shortKit?.player.setMuted(muted)
|
|
212
250
|
}
|
|
@@ -552,6 +590,9 @@ import ShortKitSDK
|
|
|
552
590
|
.store(in: &cancellables)
|
|
553
591
|
|
|
554
592
|
// Remaining content count — now handled per-feed via onRemainingContentCountChange callback
|
|
593
|
+
|
|
594
|
+
// Carousel video completion is emitted from ReactVideoCarouselOverlayHost
|
|
595
|
+
// using that host's surfaceId, so useOverlayEvent subscribers can match it.
|
|
555
596
|
}
|
|
556
597
|
|
|
557
598
|
// MARK: - Event Emission Helpers
|
|
@@ -881,8 +922,9 @@ import ShortKitSDK
|
|
|
881
922
|
switch type {
|
|
882
923
|
case "video":
|
|
883
924
|
guard let playbackId = obj["playbackId"] as? String else { continue }
|
|
925
|
+
let origin = (obj["origin"] as? String).flatMap { ContentOrigin(rawValue: $0) } ?? .other
|
|
884
926
|
let fallbackUrl = obj["fallbackUrl"] as? String
|
|
885
|
-
result.append(.video(playbackId: playbackId, fallbackUrl: fallbackUrl))
|
|
927
|
+
result.append(.video(playbackId: playbackId, origin: origin, fallbackUrl: fallbackUrl))
|
|
886
928
|
case "imageCarousel":
|
|
887
929
|
guard let itemData = obj["item"],
|
|
888
930
|
let itemJSON = try? JSONSerialization.data(withJSONObject: itemData),
|
|
@@ -20,6 +20,15 @@ import ShortKitSDK
|
|
|
20
20
|
didSet { /* used at init time only */ }
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/// URL of the thumbnail the host app is already rendering for the item that
|
|
24
|
+
/// will be the first active cell. Looked up synchronously via SDWebImage's
|
|
25
|
+
/// memory cache (supports expo-image / FastImage clients) and seeded onto
|
|
26
|
+
/// the feed VC before viewDidLoad fires. Falls back to patchMissingThumbnail
|
|
27
|
+
/// (network fetch) on cache miss.
|
|
28
|
+
@objc public var seedThumbnailUrl: String? {
|
|
29
|
+
didSet { /* used at embed time only */ }
|
|
30
|
+
}
|
|
31
|
+
|
|
23
32
|
@objc var feedId: String?
|
|
24
33
|
|
|
25
34
|
// MARK: - Child VC
|
|
@@ -104,6 +113,17 @@ import ShortKitSDK
|
|
|
104
113
|
let feedVC = ShortKitFeedViewController(shortKit: sdk, config: feedConfig, startAtItemId: startAtItemId)
|
|
105
114
|
feedVC.setBridgeManaged()
|
|
106
115
|
|
|
116
|
+
// Seed a thumbnail from the host app's image cache so the first cell
|
|
117
|
+
// renders with a visible thumbnail from frame zero. Synchronous
|
|
118
|
+
// SDWebImage lookup (memory then disk, via reflection) — no network.
|
|
119
|
+
// If nil (cache miss or SDWebImage not linked), the existing
|
|
120
|
+
// patchMissingThumbnailIfNeeded helper handles the fallback at
|
|
121
|
+
// feed-open time.
|
|
122
|
+
if let url = self.seedThumbnailUrl,
|
|
123
|
+
let image = SeedThumbnailResolver.resolveFromMemory(url: url) {
|
|
124
|
+
feedVC.seedThumbnail = image
|
|
125
|
+
}
|
|
126
|
+
|
|
107
127
|
if sdk.debugPanelEnabled {
|
|
108
128
|
feedVC.debugPanelFactory = { active, prev, next in
|
|
109
129
|
let panel = DebugPanelView(frame: CGRect(
|
package/ios/ShortKitModule.mm
CHANGED
|
@@ -82,8 +82,12 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
82
82
|
@"onOverlayFeedScrollPhaseChanged",
|
|
83
83
|
@"onOverlayTimeUpdate",
|
|
84
84
|
@"onOverlayFullState",
|
|
85
|
+
@"onOverlayItemChanged",
|
|
85
86
|
@"onCarouselActiveImageChanged",
|
|
87
|
+
@"onCarouselItemChanged",
|
|
86
88
|
@"onVideoCarouselActiveVideoChanged",
|
|
89
|
+
@"onVideoCarouselItemChanged",
|
|
90
|
+
@"onCarouselActiveVideoCompleted",
|
|
87
91
|
@"onDownloadStarted",
|
|
88
92
|
@"onDownloadProgress",
|
|
89
93
|
@"onDownloadCompleted",
|
|
@@ -197,6 +201,35 @@ RCT_EXPORT_METHOD(skipToPrevious) {
|
|
|
197
201
|
[_shortKitBridge skipToPrevious];
|
|
198
202
|
}
|
|
199
203
|
|
|
204
|
+
// MARK: - Carousel Commands
|
|
205
|
+
|
|
206
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, carouselNext)
|
|
207
|
+
{
|
|
208
|
+
return [_shortKitBridge carouselNext];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, carouselPrevious)
|
|
212
|
+
{
|
|
213
|
+
return [_shortKitBridge carouselPrevious];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, carouselSetActiveIndex:(NSInteger)index)
|
|
217
|
+
{
|
|
218
|
+
return [_shortKitBridge carouselSetActiveIndex:index];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// MARK: - Carousel Accessors
|
|
222
|
+
|
|
223
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getCarouselActiveIndex)
|
|
224
|
+
{
|
|
225
|
+
return [_shortKitBridge getCarouselActiveIndex];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSNumber *, getCarouselVideoCount)
|
|
229
|
+
{
|
|
230
|
+
return [_shortKitBridge getCarouselVideoCount];
|
|
231
|
+
}
|
|
232
|
+
|
|
200
233
|
RCT_EXPORT_METHOD(setMuted:(BOOL)muted) {
|
|
201
234
|
[_shortKitBridge setMuted:muted];
|
|
202
235
|
}
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>ShortKitSDK.framework/ShortKitSDK</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>ShortKitSDK.framework</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
-
<string>x86_64</string>
|
|
18
17
|
</array>
|
|
19
18
|
<key>SupportedPlatform</key>
|
|
20
19
|
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
20
|
</dict>
|
|
24
21
|
<dict>
|
|
25
22
|
<key>BinaryPath</key>
|
|
26
23
|
<string>ShortKitSDK.framework/ShortKitSDK</string>
|
|
27
24
|
<key>LibraryIdentifier</key>
|
|
28
|
-
<string>ios-
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
29
26
|
<key>LibraryPath</key>
|
|
30
27
|
<string>ShortKitSDK.framework</string>
|
|
31
28
|
<key>SupportedArchitectures</key>
|
|
32
29
|
<array>
|
|
33
30
|
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
34
32
|
</array>
|
|
35
33
|
<key>SupportedPlatform</key>
|
|
36
34
|
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
<key>CFBundlePackageType</key>
|
|
12
12
|
<string>FMWK</string>
|
|
13
13
|
<key>CFBundleVersion</key>
|
|
14
|
-
<string>0.2.
|
|
14
|
+
<string>0.2.25</string>
|
|
15
15
|
<key>CFBundleShortVersionString</key>
|
|
16
|
-
<string>0.2.
|
|
16
|
+
<string>0.2.25</string>
|
|
17
17
|
<key>MinimumOSVersion</key>
|
|
18
18
|
<string>16.0</string>
|
|
19
19
|
</dict>
|