@shortkitsdk/react-native 0.2.12 → 0.2.15
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/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +47 -4
- package/android/src/main/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHost.kt +431 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +117 -2
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +7 -5
- package/ios/ReactCarouselOverlayHost.swift +67 -35
- package/ios/ReactOverlayHost.swift +85 -75
- package/ios/ReactVideoCarouselOverlayHost.swift +283 -0
- package/ios/ShortKitBridge.swift +122 -20
- package/ios/ShortKitModule.mm +15 -5
- package/ios/ShortKitSDK.xcframework/Info.plist +5 -4
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +5 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +2488 -281
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +65 -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 +65 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
- package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +2 -1
- package/ios/ShortKitSDK.xcframework/{ios-arm64-simulator → ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Info.plist +5 -1
- package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +8233 -3592
- package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +120 -19
- package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/{ShortKitSDK.xcframework.bak/ios-arm64-simulator → ShortKitSDK.xcframework/ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +120 -19
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +33558 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +925 -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 +925 -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 +212 -0
- package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
- package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +7338 -3272
- package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +106 -15
- package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +106 -15
- package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
- package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1838 -206
- package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +51 -1
- package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +51 -1
- package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.dev-backup/ios-arm64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +168 -0
- package/package.json +1 -1
- package/src/ShortKitCarouselOverlaySurface.tsx +57 -2
- package/src/ShortKitContext.ts +11 -0
- package/src/ShortKitFeed.tsx +25 -10
- package/src/ShortKitOverlaySurface.tsx +24 -11
- package/src/ShortKitProvider.tsx +4 -2
- package/src/ShortKitVideoCarouselOverlaySurface.tsx +156 -0
- package/src/ShortKitWidget.tsx +3 -3
- package/src/index.ts +5 -1
- package/src/serialization.ts +7 -0
- package/src/specs/NativeShortKitModule.ts +18 -2
- package/src/types.ts +48 -3
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/{ios-arm64-simulator → ios-arm64_x86_64-simulator}/ShortKitSDK.framework/Modules/module.modulemap +0 -0
- package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/Info.plist +4 -4
- /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Info.plist +0 -0
- /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +0 -0
- /package/ios/{ShortKitSDK.xcframework → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +0 -0
- /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +0 -0
- /package/ios/{ShortKitSDK.xcframework.bak → ShortKitSDK.xcframework.dev-backup}/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +0 -0
|
@@ -17,6 +17,7 @@ import com.shortkit.sdk.model.FeedInput
|
|
|
17
17
|
import com.shortkit.sdk.model.FeedScrollPhase
|
|
18
18
|
import com.shortkit.sdk.model.FeedTransitionPhase
|
|
19
19
|
import com.shortkit.sdk.model.ImageCarouselItem
|
|
20
|
+
import com.shortkit.sdk.model.VideoCarouselItem
|
|
20
21
|
import com.shortkit.sdk.model.JsonValue
|
|
21
22
|
import com.shortkit.sdk.model.PlayerState
|
|
22
23
|
import com.shortkit.sdk.config.AdOverlayMode
|
|
@@ -26,6 +27,8 @@ import com.shortkit.sdk.config.FeedHeight
|
|
|
26
27
|
import com.shortkit.sdk.config.FeedSource
|
|
27
28
|
import com.shortkit.sdk.config.ScrollAxis
|
|
28
29
|
import com.shortkit.sdk.config.SurveyOverlayMode
|
|
30
|
+
import com.shortkit.sdk.config.VideoCarouselOverlayMode
|
|
31
|
+
import com.shortkit.sdk.feed.ShortKitRefreshState
|
|
29
32
|
import com.shortkit.sdk.config.VideoOverlayMode
|
|
30
33
|
import com.shortkit.sdk.feed.FeedPreload
|
|
31
34
|
import com.shortkit.sdk.feed.ShortKitFeedFragment
|
|
@@ -152,16 +155,21 @@ class ShortKitBridge(
|
|
|
152
155
|
|
|
153
156
|
val carouselOverlayRaw = obj.optString("carouselOverlay", null)
|
|
154
157
|
val carouselOverlay = parseCarouselOverlay(carouselOverlayRaw, context)
|
|
158
|
+
val videoCarouselOverlayRaw = obj.optString("videoCarouselOverlay", null)
|
|
159
|
+
val videoCarouselOverlay = parseVideoCarouselOverlay(videoCarouselOverlayRaw, context)
|
|
155
160
|
val autoplay = obj.optBoolean("autoplay", true)
|
|
156
161
|
val filter = obj.optJSONObject("filter")?.let { parseFeedFilterToModel(it.toString()) }
|
|
157
162
|
|
|
158
163
|
val scrollAxisStr = obj.optString("scrollAxis", "vertical")
|
|
159
164
|
val scrollAxis = if (scrollAxisStr == "horizontal") ScrollAxis.Horizontal else ScrollAxis.Vertical
|
|
160
165
|
|
|
166
|
+
val pullToRefreshEnabled = obj.optBoolean("pullToRefreshEnabled", true)
|
|
167
|
+
|
|
161
168
|
FeedConfig(
|
|
162
169
|
feedHeight = feedHeight,
|
|
163
170
|
videoOverlay = videoOverlay,
|
|
164
171
|
carouselOverlay = carouselOverlay,
|
|
172
|
+
videoCarouselOverlay = videoCarouselOverlay,
|
|
165
173
|
surveyOverlay = SurveyOverlayMode.None,
|
|
166
174
|
adOverlay = AdOverlayMode.None,
|
|
167
175
|
muteOnStart = muteOnStart,
|
|
@@ -169,6 +177,7 @@ class ShortKitBridge(
|
|
|
169
177
|
feedSource = feedSource,
|
|
170
178
|
filter = filter,
|
|
171
179
|
scrollAxis = scrollAxis,
|
|
180
|
+
pullToRefreshEnabled = pullToRefreshEnabled,
|
|
172
181
|
)
|
|
173
182
|
} catch (e: Exception) {
|
|
174
183
|
android.util.Log.e("SK:Bridge", "parseFeedConfig: EXCEPTION parsing config, returning default. json=${json.take(300)}", e)
|
|
@@ -273,6 +282,43 @@ class ShortKitBridge(
|
|
|
273
282
|
}
|
|
274
283
|
}
|
|
275
284
|
|
|
285
|
+
/**
|
|
286
|
+
* Parse a double-stringified video carousel overlay JSON.
|
|
287
|
+
* - `"\"none\""` -> None
|
|
288
|
+
* - `"{\"type\":\"custom\"}"` -> Custom with ReactVideoCarouselOverlayHost factory
|
|
289
|
+
*/
|
|
290
|
+
private fun parseVideoCarouselOverlay(json: String?, context: android.content.Context?): VideoCarouselOverlayMode {
|
|
291
|
+
if (json.isNullOrEmpty()) return VideoCarouselOverlayMode.None
|
|
292
|
+
return try {
|
|
293
|
+
val parsed = json.trim()
|
|
294
|
+
|
|
295
|
+
if (parsed == "\"none\"" || parsed == "none") {
|
|
296
|
+
return VideoCarouselOverlayMode.None
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
val inner = if (parsed.startsWith("\"") && parsed.endsWith("\"")) {
|
|
300
|
+
JSONObject(parsed.substring(1, parsed.length - 1).replace("\\\"", "\""))
|
|
301
|
+
} else {
|
|
302
|
+
JSONObject(parsed)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (inner.optString("type") == "custom" && context != null) {
|
|
306
|
+
val name = inner.optString("name", "Default")
|
|
307
|
+
val ctx = context.applicationContext
|
|
308
|
+
VideoCarouselOverlayMode.Custom {
|
|
309
|
+
ReactVideoCarouselOverlayHost(ctx).apply {
|
|
310
|
+
videoCarouselOverlayName = name
|
|
311
|
+
prepareSurface()
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
VideoCarouselOverlayMode.None
|
|
316
|
+
}
|
|
317
|
+
} catch (_: Exception) {
|
|
318
|
+
VideoCarouselOverlayMode.None
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
276
322
|
private fun parseFeedFilter(json: String?): String? {
|
|
277
323
|
// On Android, the SDK accepts the raw JSON string for filtering.
|
|
278
324
|
// Return as-is if non-null/non-empty.
|
|
@@ -316,6 +362,26 @@ class ShortKitBridge(
|
|
|
316
362
|
val carouselItem = parseImageCarouselItem(itemObj) ?: continue
|
|
317
363
|
result.add(FeedInput.ImageCarousel(carouselItem))
|
|
318
364
|
}
|
|
365
|
+
"videoCarousel" -> {
|
|
366
|
+
val itemObj = obj.optJSONObject("item") ?: continue
|
|
367
|
+
val videosArr = itemObj.optJSONArray("videos") ?: continue
|
|
368
|
+
val videos = mutableListOf<ContentItem>()
|
|
369
|
+
for (i in 0 until videosArr.length()) {
|
|
370
|
+
val videoObj = videosArr.getJSONObject(i)
|
|
371
|
+
parseContentItem(videoObj)?.let { videos.add(it) }
|
|
372
|
+
}
|
|
373
|
+
if (videos.isEmpty()) continue
|
|
374
|
+
val carouselItem = VideoCarouselItem(
|
|
375
|
+
id = itemObj.getString("id"),
|
|
376
|
+
videos = videos,
|
|
377
|
+
title = itemObj.optString("title", null),
|
|
378
|
+
description = itemObj.optString("description", null),
|
|
379
|
+
author = itemObj.optString("author", null),
|
|
380
|
+
section = itemObj.optString("section", null),
|
|
381
|
+
articleUrl = itemObj.optString("articleUrl", null),
|
|
382
|
+
)
|
|
383
|
+
result.add(FeedInput.VideoCarousel(carouselItem))
|
|
384
|
+
}
|
|
319
385
|
}
|
|
320
386
|
}
|
|
321
387
|
result.ifEmpty { null }
|
|
@@ -349,6 +415,27 @@ class ShortKitBridge(
|
|
|
349
415
|
)
|
|
350
416
|
}
|
|
351
417
|
|
|
418
|
+
private fun parseContentItem(obj: JSONObject): ContentItem? {
|
|
419
|
+
val id = obj.optString("id", null) ?: return null
|
|
420
|
+
val title = obj.optString("title", null) ?: return null
|
|
421
|
+
val duration = obj.optDouble("duration", -1.0).takeIf { it >= 0 } ?: return null
|
|
422
|
+
val streamingUrl = obj.optString("streamingUrl", null) ?: return null
|
|
423
|
+
val thumbnailUrl = obj.optString("thumbnailUrl", null) ?: return null
|
|
424
|
+
return ContentItem(
|
|
425
|
+
id = id,
|
|
426
|
+
playbackId = obj.optString("playbackId", null),
|
|
427
|
+
title = title,
|
|
428
|
+
description = obj.optString("description", null),
|
|
429
|
+
duration = duration,
|
|
430
|
+
streamingUrl = streamingUrl,
|
|
431
|
+
thumbnailUrl = thumbnailUrl,
|
|
432
|
+
captionTracks = emptyList(),
|
|
433
|
+
author = obj.optString("author", null),
|
|
434
|
+
articleUrl = obj.optString("articleUrl", null),
|
|
435
|
+
fallbackUrl = obj.optString("fallbackUrl", null),
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
352
439
|
private fun buildCaptionTracksJSONArray(tracks: List<CaptionTrack>): JSONArray {
|
|
353
440
|
val arr = JSONArray()
|
|
354
441
|
for (track in tracks) {
|
|
@@ -405,6 +492,7 @@ class ShortKitBridge(
|
|
|
405
492
|
private val feedRegistry = mutableMapOf<String, WeakReference<ShortKitFeedFragment>>()
|
|
406
493
|
private val pendingOps = mutableMapOf<String, MutableList<(ShortKitFeedFragment) -> Unit>>()
|
|
407
494
|
private val pendingOpsLock = Any()
|
|
495
|
+
private var lastProgressEmitTime = 0L
|
|
408
496
|
|
|
409
497
|
/** Expose the underlying SDK for the Fabric feed view manager. */
|
|
410
498
|
val sdk: ShortKit? get() = shortKit
|
|
@@ -445,8 +533,35 @@ class ShortKitBridge(
|
|
|
445
533
|
emitEventOnMain("onContentTapped", params)
|
|
446
534
|
}
|
|
447
535
|
|
|
448
|
-
override fun
|
|
449
|
-
|
|
536
|
+
override fun onRefreshStateChanged(state: ShortKitRefreshState) {
|
|
537
|
+
// Throttle pulling events to max 1 per 16ms to avoid bridge saturation
|
|
538
|
+
if (state is ShortKitRefreshState.Pulling) {
|
|
539
|
+
val now = android.os.SystemClock.uptimeMillis()
|
|
540
|
+
if (now - lastProgressEmitTime < 16L) return
|
|
541
|
+
lastProgressEmitTime = now
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
val params = Arguments.createMap().apply {
|
|
545
|
+
when (state) {
|
|
546
|
+
is ShortKitRefreshState.Idle -> {
|
|
547
|
+
putString("status", "idle")
|
|
548
|
+
putDouble("progress", 0.0)
|
|
549
|
+
}
|
|
550
|
+
is ShortKitRefreshState.Pulling -> {
|
|
551
|
+
putString("status", "pulling")
|
|
552
|
+
putDouble("progress", state.progress.toDouble())
|
|
553
|
+
}
|
|
554
|
+
is ShortKitRefreshState.Triggered -> {
|
|
555
|
+
putString("status", "triggered")
|
|
556
|
+
putDouble("progress", 0.0)
|
|
557
|
+
}
|
|
558
|
+
is ShortKitRefreshState.Refreshing -> {
|
|
559
|
+
putString("status", "refreshing")
|
|
560
|
+
putDouble("progress", 0.0)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
emitEventOnMain("onRefreshStateChanged", params)
|
|
450
565
|
}
|
|
451
566
|
|
|
452
567
|
override fun onFeedContentFetched(items: List<ContentItem>) {
|
|
@@ -207,11 +207,11 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
207
207
|
@ReactMethod
|
|
208
208
|
override fun preloadFeed(configJSON: String, itemsJSON: String?, promise: Promise) {
|
|
209
209
|
val b = bridge
|
|
210
|
-
if (b
|
|
211
|
-
promise.resolve(
|
|
212
|
-
|
|
210
|
+
if (b != null) {
|
|
211
|
+
b.preloadFeed(configJSON, itemsJSON) { result -> promise.resolve(result) }
|
|
212
|
+
} else {
|
|
213
|
+
bufferOp { bridge?.preloadFeed(configJSON, itemsJSON) { result -> promise.resolve(result) } }
|
|
213
214
|
}
|
|
214
|
-
b.preloadFeed(configJSON, itemsJSON) { result -> promise.resolve(result) }
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
// -----------------------------------------------------------------
|
|
@@ -261,7 +261,7 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
261
261
|
"onSurveyResponse" -> emitOnSurveyResponse(params)
|
|
262
262
|
"onContentTapped" -> emitOnContentTapped(params)
|
|
263
263
|
"onDismiss" -> emitOnDismiss(params)
|
|
264
|
-
"
|
|
264
|
+
"onRefreshStateChanged" -> emitOnRefreshStateChanged(params)
|
|
265
265
|
"onDidFetchContentItems" -> emitOnDidFetchContentItems(params)
|
|
266
266
|
"onFeedReady" -> emitOnFeedReady(params)
|
|
267
267
|
"onOverlayActiveChanged" -> emitOnOverlayActiveChanged(params)
|
|
@@ -273,6 +273,8 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
273
273
|
"onOverlayFeedScrollPhaseChanged" -> emitOnOverlayFeedScrollPhaseChanged(params)
|
|
274
274
|
"onOverlayTimeUpdate" -> emitOnOverlayTimeUpdate(params)
|
|
275
275
|
"onOverlayFullState" -> emitOnOverlayFullState(params)
|
|
276
|
+
"onCarouselActiveImageChanged" -> emitOnCarouselActiveImageChanged(params)
|
|
277
|
+
"onVideoCarouselActiveVideoChanged" -> emitOnVideoCarouselActiveVideoChanged(params)
|
|
276
278
|
else -> {
|
|
277
279
|
android.util.Log.w("SK:Module", "sendEvent: unknown event name '$name', using legacy emitter")
|
|
278
280
|
reactApplicationContext
|
|
@@ -5,8 +5,8 @@ import ShortKitSDK
|
|
|
5
5
|
/// for rendering the developer's React carousel overlay component inside a feed cell.
|
|
6
6
|
///
|
|
7
7
|
/// Unlike ReactOverlayHost, this does not subscribe to player state (carousels
|
|
8
|
-
/// are image-based). It
|
|
9
|
-
///
|
|
8
|
+
/// are image-based). It delivers `isActive` and `activeImageIndex` via bridge
|
|
9
|
+
/// events (routed by surfaceId) to avoid Fabric remounts.
|
|
10
10
|
@objc public class ReactCarouselOverlayHost: UIView, @unchecked Sendable, CarouselOverlay {
|
|
11
11
|
|
|
12
12
|
// MARK: - Configuration
|
|
@@ -20,9 +20,6 @@ import ShortKitSDK
|
|
|
20
20
|
|
|
21
21
|
// MARK: - Eager Surface Creation
|
|
22
22
|
|
|
23
|
-
/// Eagerly create the RN surface so it's ready before the first configure().
|
|
24
|
-
/// Called by the overlay factory right after setting surfacePresenter and moduleName.
|
|
25
|
-
/// Mirrors ReactOverlayHost.attach(player:) which does the same for video overlays.
|
|
26
23
|
func prepareSurface() {
|
|
27
24
|
createSurfaceIfNeeded()
|
|
28
25
|
}
|
|
@@ -37,6 +34,14 @@ import ShortKitSDK
|
|
|
37
34
|
private var surface: SKFabricSurfaceWrapper?
|
|
38
35
|
private var surfaceView: UIView?
|
|
39
36
|
private var pendingProps: [String: Any]?
|
|
37
|
+
private var isActive = false
|
|
38
|
+
private var activeImageIndex = 0
|
|
39
|
+
|
|
40
|
+
/// Serial queue for JPEG encoding + temp file writes (off main thread).
|
|
41
|
+
private static let imageWriteQueue = DispatchQueue(label: "com.shortkit.carousel-image-write", qos: .userInitiated)
|
|
42
|
+
|
|
43
|
+
/// Unique identifier for this overlay instance, used for event routing.
|
|
44
|
+
let surfaceId = UUID().uuidString
|
|
40
45
|
|
|
41
46
|
// MARK: - Init
|
|
42
47
|
|
|
@@ -57,19 +62,32 @@ import ShortKitSDK
|
|
|
57
62
|
// MARK: - CarouselOverlay
|
|
58
63
|
|
|
59
64
|
public func configure(with item: ImageCarouselItem) {
|
|
65
|
+
isActive = false
|
|
66
|
+
activeImageIndex = 0
|
|
60
67
|
createSurfaceIfNeeded()
|
|
61
68
|
|
|
62
|
-
// Replace remote URLs with local file URLs for any natively-cached images
|
|
63
|
-
//
|
|
64
|
-
//
|
|
69
|
+
// Replace remote URLs with local file URLs for any natively-cached images
|
|
70
|
+
// that already have a temp file on disk (fast path: FileManager.fileExists).
|
|
71
|
+
// If the native cache has the image but no temp file exists yet, use the
|
|
72
|
+
// remote URL immediately and schedule a background write so the file://
|
|
73
|
+
// URL is available on the next configure() for this image.
|
|
65
74
|
var modifiedItem = item
|
|
66
75
|
if let cachedImage {
|
|
67
76
|
var localImages: [CarouselImage] = []
|
|
68
77
|
for image in item.images {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
78
|
+
let tempURL = Self.tempFileURL(for: image.url)
|
|
79
|
+
if FileManager.default.fileExists(atPath: tempURL.path) {
|
|
80
|
+
// Fast path: temp file already written — use file:// URL
|
|
81
|
+
localImages.append(CarouselImage(url: tempURL.absoluteString, alt: image.alt))
|
|
82
|
+
} else if let uiImage = cachedImage(image.url) {
|
|
83
|
+
// Native cache hit but no temp file yet — use remote URL now,
|
|
84
|
+
// write temp file in background for next time.
|
|
85
|
+
localImages.append(image)
|
|
86
|
+
Self.imageWriteQueue.async {
|
|
87
|
+
Self.writeTempImage(uiImage, to: tempURL)
|
|
88
|
+
}
|
|
72
89
|
} else {
|
|
90
|
+
// Not cached — use remote URL
|
|
73
91
|
localImages.append(image)
|
|
74
92
|
}
|
|
75
93
|
}
|
|
@@ -85,10 +103,12 @@ import ShortKitSDK
|
|
|
85
103
|
)
|
|
86
104
|
}
|
|
87
105
|
|
|
88
|
-
// Build props — push now if surface exists, otherwise store for later
|
|
89
106
|
if let data = try? JSONEncoder().encode(modifiedItem),
|
|
90
107
|
let json = String(data: data, encoding: .utf8) {
|
|
91
|
-
let props: [String: Any] = [
|
|
108
|
+
let props: [String: Any] = [
|
|
109
|
+
"surfaceId": surfaceId,
|
|
110
|
+
"item": json,
|
|
111
|
+
]
|
|
92
112
|
if let surface {
|
|
93
113
|
surface.setProperties(props)
|
|
94
114
|
} else {
|
|
@@ -97,32 +117,46 @@ import ShortKitSDK
|
|
|
97
117
|
}
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
120
|
+
public func activatePlayback() {
|
|
121
|
+
isActive = true
|
|
122
|
+
bridge?.emit("onOverlayFullState", body: [
|
|
123
|
+
"surfaceId": surfaceId,
|
|
124
|
+
"isActive": true,
|
|
125
|
+
// Include required fields for the full state event
|
|
126
|
+
"playerState": "idle",
|
|
127
|
+
"isMuted": true,
|
|
128
|
+
"playbackRate": 1.0,
|
|
129
|
+
"captionsEnabled": false,
|
|
130
|
+
"activeCue": NSNull(),
|
|
131
|
+
"feedScrollPhase": NSNull(),
|
|
132
|
+
])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public func updateActiveImage(index: Int) {
|
|
136
|
+
activeImageIndex = index
|
|
137
|
+
bridge?.emit("onCarouselActiveImageChanged", body: [
|
|
138
|
+
"surfaceId": surfaceId,
|
|
139
|
+
"activeImageIndex": index,
|
|
140
|
+
])
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Deterministic temp file URL for a given remote image URL.
|
|
144
|
+
private static func tempFileURL(for remoteURL: String) -> URL {
|
|
103
145
|
let hash = remoteURL.hash
|
|
104
146
|
let fileName = "sk-carousel-\(abs(hash)).jpg"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
// Skip if already written
|
|
108
|
-
if FileManager.default.fileExists(atPath: fileURL.path) {
|
|
109
|
-
return fileURL.absoluteString
|
|
110
|
-
}
|
|
147
|
+
return URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)
|
|
148
|
+
}
|
|
111
149
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
150
|
+
/// JPEG-encode a UIImage and write to the given file URL.
|
|
151
|
+
/// Safe to call from any queue — no UIKit main-thread requirements.
|
|
152
|
+
private static func writeTempImage(_ image: UIImage, to fileURL: URL) {
|
|
153
|
+
guard !FileManager.default.fileExists(atPath: fileURL.path),
|
|
154
|
+
let data = image.jpegData(compressionQuality: 0.9) else { return }
|
|
155
|
+
try? data.write(to: fileURL, options: .atomic)
|
|
119
156
|
}
|
|
120
157
|
|
|
121
158
|
public func resetState() {
|
|
122
|
-
// Don't clear surface props —
|
|
123
|
-
// configure() will overwrite with new props directly, letting React do an
|
|
124
|
-
// in-place re-render (old→new) instead of an unmount+remount (old→empty→new).
|
|
125
|
-
// The overlay's synchronous reset pattern handles state transitions.
|
|
159
|
+
// Don't clear surface props — configure() will overwrite.
|
|
126
160
|
}
|
|
127
161
|
|
|
128
162
|
// MARK: - Surface Creation
|
|
@@ -130,7 +164,6 @@ import ShortKitSDK
|
|
|
130
164
|
private func createSurfaceIfNeeded() {
|
|
131
165
|
guard surface == nil, let presenter = surfacePresenter else { return }
|
|
132
166
|
|
|
133
|
-
// Must always dispatch to main queue — see ReactOverlayHost for details.
|
|
134
167
|
DispatchQueue.main.async { [weak self] in
|
|
135
168
|
guard let self, self.surface == nil else { return }
|
|
136
169
|
self.installSurface(presenter: presenter)
|
|
@@ -157,7 +190,6 @@ import ShortKitSDK
|
|
|
157
190
|
surfaceView = view
|
|
158
191
|
surface = surf
|
|
159
192
|
|
|
160
|
-
// Flush any props that arrived before surface was ready
|
|
161
193
|
if let pending = pendingProps {
|
|
162
194
|
surf.setProperties(pending)
|
|
163
195
|
pendingProps = nil
|