@shortkitsdk/react-native 0.2.45 → 0.2.47

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 (28) hide show
  1. package/android/libs/shortkit-release.aar +0 -0
  2. package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +17 -2
  3. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +2 -4
  4. package/ios/ShortKitBridge.swift +74 -1
  5. package/ios/ShortKitModule.mm +10 -0
  6. package/ios/ShortKitPushHostViewController.swift +59 -0
  7. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
  8. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +1296 -107
  9. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +30 -9
  10. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  11. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +30 -9
  12. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  13. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +9 -9
  14. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
  15. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +1296 -107
  16. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +30 -9
  17. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  18. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +30 -9
  19. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +1296 -107
  20. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +30 -9
  21. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
  22. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +30 -9
  23. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  24. package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +17 -17
  25. package/package.json +1 -1
  26. package/src/ShortKitCommands.ts +39 -0
  27. package/src/index.ts +5 -0
  28. package/src/specs/NativeShortKitModule.ts +4 -0
Binary file
@@ -69,6 +69,10 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
69
69
  layout(left, top, right, bottom)
70
70
  }
71
71
 
72
+ /** Tracks the deferred measureAndLayoutSurfaceView post from configure() so it can be
73
+ * cancelled in onDetachedFromWindow before the stale dimensions are applied. */
74
+ private var configureLayoutRunnable: Runnable? = null
75
+
72
76
  /**
73
77
  * Fabric may suppress layout propagation to native child views inside
74
78
  * the SDK's RecyclerView cells. Override to force a manual layout pass.
@@ -238,7 +242,13 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
238
242
  }
239
243
  ShortKitBridge.shared?.emitEvent("onOverlayItemChanged", params)
240
244
  }
241
- // Pre-size the surface view
245
+ // Pre-size the surface view. Deferred to avoid calling sv.measure() synchronously
246
+ // during RecyclerView.onBindViewHolder — which can trigger Fabric to dispatch pending
247
+ // mount items (e.g. react-native-screens activityState updates) that commit fragment
248
+ // transactions re-entrantly, detaching the RecyclerView mid-layout and crashing with
249
+ // "Tmp detached view should be removed from RecyclerView before it can be recycled".
250
+ // Safe to defer: createSurfaceIfNeeded() already pre-sizes new surfaces, and onLayout
251
+ // re-pushes correct dimensions during the same layout pass for recycled ones.
242
252
  val parentView = parent as? android.view.View
243
253
  val w = if (width > 0) width
244
254
  else if (parentView != null && parentView.width > 0) parentView.width
@@ -246,7 +256,10 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
246
256
  val h = if (height > 0) height
247
257
  else if (parentView != null && parentView.height > 0) parentView.height
248
258
  else context.resources.displayMetrics.heightPixels
249
- measureAndLayoutSurfaceView(w, h)
259
+ configureLayoutRunnable?.let { layoutHandler.removeCallbacks(it) }
260
+ val r = Runnable { measureAndLayoutSurfaceView(w, h) }
261
+ configureLayoutRunnable = r
262
+ layoutHandler.post(r)
250
263
  }
251
264
 
252
265
  override fun activatePlayback() {
@@ -300,6 +313,8 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
300
313
 
301
314
  override fun onDetachedFromWindow() {
302
315
  super.onDetachedFromWindow()
316
+ configureLayoutRunnable?.let { layoutHandler.removeCallbacks(it) }
317
+ configureLayoutRunnable = null
303
318
  layoutHandler.removeCallbacks(layoutRunnable)
304
319
  // Cancel flow subscriptions to avoid emitting events for off-screen cells.
305
320
  // Do NOT stop the surface — keep JS alive so subscriptions persist across
@@ -428,10 +428,8 @@ class ShortKitBridge(
428
428
  "video" -> {
429
429
  val playbackId = obj.optString("playbackId", null) ?: continue
430
430
  val fallbackUrl = obj.optString("fallbackUrl", null)
431
- val itemId = obj.optString("id", null)
432
- val nativePlaybackId =
433
- if (!itemId.isNullOrBlank() && !fallbackUrl.isNullOrBlank()) itemId else playbackId
434
- result.add(FeedInput.Video(nativePlaybackId, fallbackUrl))
431
+ val contentId = obj.optString("id", null).takeIf { !it.isNullOrBlank() }
432
+ result.add(FeedInput.Video(playbackId, fallbackUrl, contentId))
435
433
  }
436
434
  "imageCarousel" -> {
437
435
  val itemObj = obj.optJSONObject("item") ?: continue
@@ -1111,7 +1111,8 @@ import ShortKitSDK
1111
1111
  guard let playbackId = obj["playbackId"] as? String else { continue }
1112
1112
  let origin = (obj["origin"] as? String).flatMap { ContentOrigin(rawValue: $0) } ?? .other
1113
1113
  let fallbackUrl = obj["fallbackUrl"] as? String
1114
- result.append(.video(playbackId: playbackId, origin: origin, fallbackUrl: fallbackUrl))
1114
+ let contentId = obj["id"] as? String
1115
+ result.append(.video(playbackId: playbackId, origin: origin, fallbackUrl: fallbackUrl, contentId: contentId))
1115
1116
  case "imageCarousel":
1116
1117
  guard let itemData = obj["item"],
1117
1118
  let itemJSON = try? JSONSerialization.data(withJSONObject: itemData),
@@ -1210,6 +1211,78 @@ extension ShortKitBridge {
1210
1211
  }
1211
1212
  }
1212
1213
 
1214
+ // MARK: - Push Navigation
1215
+
1216
+ extension ShortKitBridge {
1217
+ /// Push a host-registered RN component onto the feed modal's
1218
+ /// UINavigationController with a native parallax animation. Finds the
1219
+ /// topmost presented UINavigationController via the UIKit presentation
1220
+ /// chain — works for both mask and standard feed expansions. Silently
1221
+ /// no-ops if no nav controller is currently presented (e.g. called when
1222
+ /// no feed modal is on screen).
1223
+ @objc public func pushFeedScreen(_ componentName: String, propsJSON: String) {
1224
+ DispatchQueue.main.async { [weak self] in
1225
+ guard let self,
1226
+ let navController = Self.topmostNavigationController(),
1227
+ let presenter = self.surfacePresenter else { return }
1228
+
1229
+ let props: [String: Any]
1230
+ if let data = propsJSON.data(using: .utf8),
1231
+ let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
1232
+ props = obj
1233
+ } else {
1234
+ props = [:]
1235
+ }
1236
+
1237
+ let pushVC = ShortKitPushHostViewController(
1238
+ moduleName: componentName,
1239
+ initialProps: props,
1240
+ surfacePresenter: presenter
1241
+ )
1242
+ navController.pushViewController(pushVC, animated: true)
1243
+ }
1244
+ }
1245
+
1246
+ /// Pop the topmost pushed screen from its navigation stack.
1247
+ ///
1248
+ /// Drills to the topmost *view controller* (not the nav controller itself)
1249
+ /// and calls popViewController on its navigationController. This is more
1250
+ /// robust than calling popViewController on the topmost nav controller
1251
+ /// directly: if a system sheet or other modal is presented on top of the
1252
+ /// pushed screen, that sheet's nav controller (nil) is used instead of
1253
+ /// the push host's nav controller, making the pop a safe no-op rather
1254
+ /// than popping from the wrong stack.
1255
+ @objc public func popFeedScreen() {
1256
+ DispatchQueue.main.async {
1257
+ Self.topmostViewController()?.navigationController?.popViewController(animated: true)
1258
+ }
1259
+ }
1260
+
1261
+ /// Walk the UIViewController presentation chain to find the topmost
1262
+ /// UINavigationController. Used by pushFeedScreen. Called on the main queue.
1263
+ private static func topmostNavigationController() -> UINavigationController? {
1264
+ guard let vc = topmostViewController() else { return nil }
1265
+ if let nav = vc as? UINavigationController { return nav }
1266
+ return vc.navigationController
1267
+ }
1268
+
1269
+ /// Walk the UIViewController presentation chain to its end, then drill
1270
+ /// into a UINavigationController's topViewController if present.
1271
+ private static func topmostViewController() -> UIViewController? {
1272
+ guard let windowScene = UIApplication.shared.connectedScenes
1273
+ .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene,
1274
+ let window = windowScene.windows.first(where: { $0.isKeyWindow }) else {
1275
+ return nil
1276
+ }
1277
+ var vc: UIViewController? = window.rootViewController
1278
+ while let presented = vc?.presentedViewController {
1279
+ vc = presented
1280
+ }
1281
+ if let nav = vc as? UINavigationController { return nav.topViewController }
1282
+ return vc
1283
+ }
1284
+ }
1285
+
1213
1286
  // MARK: - ShortKitDownloadDelegate
1214
1287
 
1215
1288
  extension ShortKitBridge: ShortKitDownloadDelegate {
@@ -387,6 +387,16 @@ RCT_EXPORT_METHOD(cancelDownload) {
387
387
  [_shortKitBridge cancelDownload];
388
388
  }
389
389
 
390
+ // MARK: - Push Navigation
391
+
392
+ RCT_EXPORT_METHOD(pushFeedScreen:(NSString *)componentName propsJSON:(NSString *)propsJSON) {
393
+ [_shortKitBridge pushFeedScreen:componentName propsJSON:propsJSON];
394
+ }
395
+
396
+ RCT_EXPORT_METHOD(popFeedScreen) {
397
+ [_shortKitBridge popFeedScreen];
398
+ }
399
+
390
400
  // MARK: - New Architecture (TurboModule)
391
401
 
392
402
  #ifdef RCT_NEW_ARCH_ENABLED
@@ -0,0 +1,59 @@
1
+ import UIKit
2
+
3
+ /// Full-screen UIViewController that hosts an RCTFabricSurface rendering a
4
+ /// host-registered RN component. Pushed onto the feed modal's
5
+ /// UINavigationController by `ShortKitBridge.pushFeedScreen(componentName:propsJSON:)`
6
+ /// to give a native parallax-push animation from within the expanded feed.
7
+ final class ShortKitPushHostViewController: UIViewController {
8
+
9
+ private let moduleName: String
10
+ private let initialProps: [String: Any]
11
+ private let surfacePresenter: AnyObject
12
+ private var surface: SKFabricSurfaceWrapper?
13
+ private var lastLayoutSize: CGSize = .zero
14
+
15
+ init(moduleName: String, initialProps: [String: Any], surfacePresenter: AnyObject) {
16
+ self.moduleName = moduleName
17
+ self.initialProps = initialProps
18
+ self.surfacePresenter = surfacePresenter
19
+ super.init(nibName: nil, bundle: nil)
20
+ }
21
+
22
+ @available(*, unavailable)
23
+ required init?(coder: NSCoder) { fatalError() }
24
+
25
+ override func viewDidLoad() {
26
+ super.viewDidLoad()
27
+ view.backgroundColor = .black
28
+
29
+ guard let surf = SKFabricSurfaceWrapper(
30
+ presenter: surfacePresenter,
31
+ moduleName: moduleName,
32
+ initialProperties: initialProps
33
+ ) else { return }
34
+ surf.start()
35
+
36
+ let surfaceView = surf.view
37
+ surfaceView.translatesAutoresizingMaskIntoConstraints = false
38
+ view.addSubview(surfaceView)
39
+ NSLayoutConstraint.activate([
40
+ surfaceView.topAnchor.constraint(equalTo: view.topAnchor),
41
+ surfaceView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
42
+ surfaceView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
43
+ surfaceView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
44
+ ])
45
+ self.surface = surf
46
+ }
47
+
48
+ override func viewDidLayoutSubviews() {
49
+ super.viewDidLayoutSubviews()
50
+ let size = view.bounds.size
51
+ guard size.width > 0, size.height > 0, size != lastLayoutSize else { return }
52
+ lastLayoutSize = size
53
+ surface?.setMinimumSize(size)
54
+ }
55
+
56
+ deinit {
57
+ surface?.stop()
58
+ }
59
+ }
@@ -11,9 +11,9 @@
11
11
  <key>CFBundlePackageType</key>
12
12
  <string>FMWK</string>
13
13
  <key>CFBundleVersion</key>
14
- <string>0.2.45</string>
14
+ <string>0.2.47</string>
15
15
  <key>CFBundleShortVersionString</key>
16
- <string>0.2.45</string>
16
+ <string>0.2.47</string>
17
17
  <key>MinimumOSVersion</key>
18
18
  <string>16.0</string>
19
19
  </dict>