@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
|
@@ -4,46 +4,28 @@ import ShortKitSDK
|
|
|
4
4
|
/// Fabric native view that embeds `ShortKitFeedViewController` using
|
|
5
5
|
/// UIViewController containment. Props are set by the view manager via
|
|
6
6
|
/// `@objc` setters.
|
|
7
|
-
///
|
|
8
|
-
/// Also tracks the feed's scroll offset via KVO and applies a native
|
|
9
|
-
/// transform to the sibling RN overlay view so it moves with the active
|
|
10
|
-
/// cell during swipe transitions.
|
|
11
7
|
@objc public class ShortKitFeedView: UIView {
|
|
12
8
|
|
|
13
9
|
// MARK: - Props (set by RCTViewManager)
|
|
14
10
|
|
|
15
11
|
@objc public var config: String? {
|
|
16
|
-
didSet { /*
|
|
12
|
+
didSet { /* config is read once at embed time; changes require remount */ }
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
@objc public var
|
|
20
|
-
didSet { /*
|
|
15
|
+
@objc public var startAtItemId: String? {
|
|
16
|
+
didSet { /* used at init time only */ }
|
|
21
17
|
}
|
|
22
18
|
|
|
19
|
+
@objc public var preloadId: String? {
|
|
20
|
+
didSet { /* used at init time only */ }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@objc var feedId: String?
|
|
24
|
+
|
|
23
25
|
// MARK: - Child VC
|
|
24
26
|
|
|
25
27
|
private var feedViewController: ShortKitFeedViewController?
|
|
26
28
|
|
|
27
|
-
// MARK: - Scroll Tracking
|
|
28
|
-
|
|
29
|
-
private var scrollObservation: NSKeyValueObservation?
|
|
30
|
-
/// Video overlay for the currently active cell (nativeID="overlay-current").
|
|
31
|
-
private weak var currentOverlayView: UIView?
|
|
32
|
-
/// Video overlay for the upcoming cell (nativeID="overlay-next").
|
|
33
|
-
private weak var nextOverlayView: UIView?
|
|
34
|
-
/// Carousel overlay for the currently active cell (nativeID="carousel-overlay-current").
|
|
35
|
-
private weak var currentCarouselOverlayView: UIView?
|
|
36
|
-
/// Carousel overlay for the upcoming cell (nativeID="carousel-overlay-next").
|
|
37
|
-
private weak var nextCarouselOverlayView: UIView?
|
|
38
|
-
/// The page index used for overlay transform calculations.
|
|
39
|
-
private var currentPage: Int = 0
|
|
40
|
-
/// Closure to execute the pending overlay transform swap once JS signals ready.
|
|
41
|
-
private var pendingSwap: (() -> Void)?
|
|
42
|
-
/// Fallback timer in case JS never calls notifyOverlayReady.
|
|
43
|
-
private var swapFallbackWorkItem: DispatchWorkItem?
|
|
44
|
-
/// Observer for the overlay-ready notification from the JS bridge.
|
|
45
|
-
private var overlayReadyObserver: NSObjectProtocol?
|
|
46
|
-
|
|
47
29
|
// MARK: - Lifecycle
|
|
48
30
|
|
|
49
31
|
public override func didMoveToWindow() {
|
|
@@ -58,7 +40,7 @@ import ShortKitSDK
|
|
|
58
40
|
super.willMove(toWindow: newWindow)
|
|
59
41
|
|
|
60
42
|
if newWindow == nil {
|
|
61
|
-
|
|
43
|
+
suspendFeedViewController()
|
|
62
44
|
}
|
|
63
45
|
}
|
|
64
46
|
|
|
@@ -67,237 +49,109 @@ import ShortKitSDK
|
|
|
67
49
|
feedViewController?.view.frame = bounds
|
|
68
50
|
}
|
|
69
51
|
|
|
52
|
+
deinit {
|
|
53
|
+
destroyFeedViewController()
|
|
54
|
+
}
|
|
55
|
+
|
|
70
56
|
// MARK: - VC Containment
|
|
71
57
|
|
|
72
58
|
private func embedFeedViewControllerIfNeeded() {
|
|
73
|
-
// Already embedded
|
|
74
|
-
guard feedViewController == nil else { return }
|
|
75
|
-
|
|
76
|
-
guard let sdk = ShortKitBridge.shared?.sdk else {
|
|
77
|
-
NSLog("[ShortKitFeedView] ShortKit SDK not initialized. Call ShortKitModule.initialize() first.")
|
|
78
|
-
return
|
|
79
|
-
}
|
|
80
|
-
|
|
81
59
|
guard let parentVC = findParentViewController() else {
|
|
82
|
-
NSLog("[ShortKitFeedView] Could not find parent UIViewController.")
|
|
83
60
|
return
|
|
84
61
|
}
|
|
85
62
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
private func removeFeedViewController() {
|
|
100
|
-
pendingSwap = nil
|
|
101
|
-
swapFallbackWorkItem?.cancel()
|
|
102
|
-
swapFallbackWorkItem = nil
|
|
103
|
-
if let observer = overlayReadyObserver {
|
|
104
|
-
NotificationCenter.default.removeObserver(observer)
|
|
105
|
-
overlayReadyObserver = nil
|
|
63
|
+
// Re-attach an existing suspended VC (e.g. after native stack pop)
|
|
64
|
+
if let feedVC = feedViewController {
|
|
65
|
+
parentVC.addChild(feedVC)
|
|
66
|
+
feedVC.view.frame = bounds
|
|
67
|
+
addSubview(feedVC.view)
|
|
68
|
+
feedVC.didMove(toParent: parentVC)
|
|
69
|
+
feedVC.activate()
|
|
70
|
+
if let feedId = self.feedId {
|
|
71
|
+
ShortKitBridge.shared?.registerFeed(id: feedId, viewController: feedVC)
|
|
72
|
+
}
|
|
73
|
+
return
|
|
106
74
|
}
|
|
107
|
-
scrollObservation?.invalidate()
|
|
108
|
-
scrollObservation = nil
|
|
109
|
-
currentOverlayView?.transform = .identity
|
|
110
|
-
nextOverlayView?.transform = .identity
|
|
111
|
-
currentCarouselOverlayView?.transform = .identity
|
|
112
|
-
nextCarouselOverlayView?.transform = .identity
|
|
113
|
-
currentOverlayView = nil
|
|
114
|
-
nextOverlayView = nil
|
|
115
|
-
currentCarouselOverlayView = nil
|
|
116
|
-
nextCarouselOverlayView = nil
|
|
117
|
-
|
|
118
|
-
guard let feedVC = feedViewController else { return }
|
|
119
|
-
|
|
120
|
-
feedVC.willMove(toParent: nil)
|
|
121
|
-
feedVC.view.removeFromSuperview()
|
|
122
|
-
feedVC.removeFromParent()
|
|
123
|
-
|
|
124
|
-
self.feedViewController = nil
|
|
125
|
-
}
|
|
126
75
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
private func setupScrollTracking(_ feedVC: ShortKitFeedViewController) {
|
|
130
|
-
guard let scrollView = findScrollView(in: feedVC.view) else {
|
|
76
|
+
guard let sdk = ShortKitBridge.shared?.sdk else {
|
|
131
77
|
return
|
|
132
78
|
}
|
|
133
79
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
}
|
|
80
|
+
// Parse config from the Fabric view prop
|
|
81
|
+
var feedConfig = ShortKitBridge.parseFeedConfig(self.config ?? "{}")
|
|
138
82
|
|
|
139
|
-
|
|
140
|
-
let
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Why: when the scroll settles on a new page, overlay-current still
|
|
149
|
-
// shows the OLD page's metadata. If we update currentPage immediately,
|
|
150
|
-
// delta snaps to 0 and overlay-current becomes visible with stale data.
|
|
151
|
-
//
|
|
152
|
-
// Instead, we store a pending swap closure. JS calls notifyOverlayReady()
|
|
153
|
-
// after processing OVERLAY_ACTIVATE and resetting overlay opacity, which
|
|
154
|
-
// executes the swap deterministically. A fallback timer catches edge cases.
|
|
155
|
-
let nearestPage = Int(round(offset / cellHeight))
|
|
156
|
-
if abs(offset - CGFloat(nearestPage) * cellHeight) < 1.0 {
|
|
157
|
-
if nearestPage != currentPage && pendingSwap == nil {
|
|
158
|
-
let targetPage = nearestPage
|
|
159
|
-
pendingSwap = { [weak self] in
|
|
160
|
-
guard let self else { return }
|
|
161
|
-
self.currentPage = targetPage
|
|
162
|
-
self.pendingSwap = nil
|
|
163
|
-
self.swapFallbackWorkItem?.cancel()
|
|
164
|
-
self.swapFallbackWorkItem = nil
|
|
165
|
-
// Reapply overlay transforms now that currentPage is updated.
|
|
166
|
-
let h = self.bounds.height
|
|
167
|
-
self.currentOverlayView?.transform = .identity
|
|
168
|
-
self.nextOverlayView?.transform = CGAffineTransform(translationX: 0, y: h)
|
|
169
|
-
self.currentCarouselOverlayView?.transform = .identity
|
|
170
|
-
self.nextCarouselOverlayView?.transform = CGAffineTransform(translationX: 0, y: h)
|
|
171
|
-
}
|
|
172
|
-
// Fallback: if JS never signals (e.g. overlay config is 'none'),
|
|
173
|
-
// execute after 500ms to avoid getting stuck.
|
|
174
|
-
let fallback = DispatchWorkItem { [weak self] in
|
|
175
|
-
self?.pendingSwap?()
|
|
176
|
-
}
|
|
177
|
-
swapFallbackWorkItem = fallback
|
|
178
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: fallback)
|
|
83
|
+
// Consume preload handle if available
|
|
84
|
+
if let preloadId = self.preloadId {
|
|
85
|
+
NSLog("[ShortKit FeedView] preloadId prop: %@", preloadId)
|
|
86
|
+
if let preload = ShortKitBridge.shared?.consumePreload(id: preloadId) {
|
|
87
|
+
feedConfig.preload = preload
|
|
88
|
+
NSLog("[ShortKit FeedView] ✅ Preload handle consumed for feedId: %@", preloadId)
|
|
89
|
+
} else {
|
|
90
|
+
NSLog("[ShortKit FeedView] ❌ No preload handle found for feedId: %@", preloadId)
|
|
91
|
+
NSLog("[ShortKit FeedView] Available preload handles: %@", ShortKitBridge.shared?.preloadHandles.keys.joined(separator: ", ") ?? "none")
|
|
179
92
|
}
|
|
180
|
-
} else
|
|
181
|
-
|
|
182
|
-
// so transforms stay aligned for the new gesture.
|
|
183
|
-
pendingSwap?()
|
|
93
|
+
} else {
|
|
94
|
+
NSLog("[ShortKit FeedView] No preloadId prop set")
|
|
184
95
|
}
|
|
185
96
|
|
|
186
|
-
|
|
97
|
+
NSLog("[ShortKit FeedView] feedConfig.preload is %@", feedConfig.preload != nil ? "SET" : "NIL")
|
|
98
|
+
NSLog("[ShortKit FeedView] feedConfig.feedSource: %@", feedConfig.feedSource == .custom ? "custom" : "algorithmic")
|
|
187
99
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
if currentCarouselOverlayView == nil || nextCarouselOverlayView == nil {
|
|
193
|
-
findCarouselOverlayViews()
|
|
194
|
-
}
|
|
100
|
+
NSLog("[ShortKit FeedView] Creating ShortKitFeedViewController with config.preload=%@, config.feedSource=%@",
|
|
101
|
+
feedConfig.preload != nil ? "SET" : "NIL",
|
|
102
|
+
feedConfig.feedSource == .custom ? "custom" : "algorithmic")
|
|
195
103
|
|
|
196
|
-
let
|
|
104
|
+
let feedVC = ShortKitFeedViewController(shortKit: sdk, config: feedConfig, startAtItemId: startAtItemId)
|
|
105
|
+
feedVC.setBridgeManaged()
|
|
197
106
|
|
|
198
|
-
|
|
199
|
-
currentOverlayView?.transform = translateY
|
|
200
|
-
currentCarouselOverlayView?.transform = translateY
|
|
107
|
+
NSLog("[ShortKit FeedView] VC created successfully (bridge-managed)")
|
|
201
108
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
// Forward scroll (or idle): next cell is below
|
|
205
|
-
let nextTransform = CGAffineTransform(translationX: 0, y: cellHeight - delta)
|
|
206
|
-
nextOverlayView?.transform = nextTransform
|
|
207
|
-
nextCarouselOverlayView?.transform = nextTransform
|
|
208
|
-
} else {
|
|
209
|
-
// Backward scroll: next cell is above
|
|
210
|
-
let nextTransform = CGAffineTransform(translationX: 0, y: -cellHeight - delta)
|
|
211
|
-
nextOverlayView?.transform = nextTransform
|
|
212
|
-
nextCarouselOverlayView?.transform = nextTransform
|
|
109
|
+
feedVC.onDismiss = {
|
|
110
|
+
ShortKitBridge.shared?.emitDismiss()
|
|
213
111
|
}
|
|
214
|
-
}
|
|
215
112
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
overlayReadyObserver = NotificationCenter.default.addObserver(
|
|
220
|
-
forName: .shortKitOverlayReady,
|
|
221
|
-
object: nil,
|
|
222
|
-
queue: .main
|
|
223
|
-
) { [weak self] _ in
|
|
224
|
-
self?.pendingSwap?()
|
|
113
|
+
self.feedViewController = feedVC
|
|
114
|
+
if let feedId = self.feedId {
|
|
115
|
+
ShortKitBridge.shared?.registerFeed(id: feedId, viewController: feedVC)
|
|
225
116
|
}
|
|
226
|
-
}
|
|
227
117
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
/// rendered by ShortKitFeed.tsx. We walk up the ancestor chain until we
|
|
234
|
-
/// find the overlays.
|
|
235
|
-
private func findOverlayViews() {
|
|
236
|
-
var ancestor: UIView? = superview
|
|
237
|
-
while let container = ancestor {
|
|
238
|
-
for child in container.subviews {
|
|
239
|
-
// Skip subtrees that contain ourselves
|
|
240
|
-
guard !self.isDescendant(of: child) else { continue }
|
|
241
|
-
|
|
242
|
-
if currentOverlayView == nil {
|
|
243
|
-
currentOverlayView = findView(withNativeID: "overlay-current", in: child)
|
|
244
|
-
}
|
|
245
|
-
if nextOverlayView == nil {
|
|
246
|
-
nextOverlayView = findView(withNativeID: "overlay-next", in: child)
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if currentOverlayView != nil && nextOverlayView != nil { return }
|
|
250
|
-
ancestor = container.superview
|
|
251
|
-
}
|
|
118
|
+
parentVC.addChild(feedVC)
|
|
119
|
+
feedVC.view.frame = bounds
|
|
120
|
+
feedVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
121
|
+
addSubview(feedVC.view)
|
|
122
|
+
feedVC.didMove(toParent: parentVC)
|
|
252
123
|
}
|
|
253
124
|
|
|
254
|
-
///
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if currentCarouselOverlayView == nil {
|
|
262
|
-
currentCarouselOverlayView = findView(withNativeID: "carousel-overlay-current", in: child)
|
|
263
|
-
}
|
|
264
|
-
if nextCarouselOverlayView == nil {
|
|
265
|
-
nextCarouselOverlayView = findView(withNativeID: "carousel-overlay-next", in: child)
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if currentCarouselOverlayView != nil && nextCarouselOverlayView != nil { return }
|
|
269
|
-
ancestor = container.superview
|
|
125
|
+
/// Detach the feedVC from the parent VC hierarchy without destroying it.
|
|
126
|
+
/// Called when the native stack temporarily removes this view from the window
|
|
127
|
+
/// (e.g. pushing a new screen on top). The feedVC and its state are preserved
|
|
128
|
+
/// so they can be re-attached when the view returns to the window.
|
|
129
|
+
private func suspendFeedViewController() {
|
|
130
|
+
if let feedId = self.feedId {
|
|
131
|
+
ShortKitBridge.shared?.unregisterFeed(id: feedId)
|
|
270
132
|
}
|
|
271
|
-
|
|
133
|
+
guard let feedVC = feedViewController else { return }
|
|
272
134
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
let rnNativeId = view.value(forKey: "nativeId") as? String,
|
|
279
|
-
rnNativeId == nativeID {
|
|
280
|
-
return view
|
|
281
|
-
}
|
|
282
|
-
for subview in view.subviews {
|
|
283
|
-
if let found = findView(withNativeID: nativeID, in: subview) {
|
|
284
|
-
return found
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
return nil
|
|
135
|
+
feedVC.deactivate()
|
|
136
|
+
feedVC.willMove(toParent: nil)
|
|
137
|
+
feedVC.view.removeFromSuperview()
|
|
138
|
+
feedVC.removeFromParent()
|
|
139
|
+
// Keep feedViewController reference — re-attached in embedFeedViewControllerIfNeeded
|
|
288
140
|
}
|
|
289
141
|
|
|
290
|
-
///
|
|
291
|
-
private func
|
|
292
|
-
if let
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
for subview in view.subviews {
|
|
296
|
-
if let sv = findScrollView(in: subview) {
|
|
297
|
-
return sv
|
|
298
|
-
}
|
|
142
|
+
/// Full teardown — called from deinit when React unmounts the native view.
|
|
143
|
+
private func destroyFeedViewController() {
|
|
144
|
+
if let feedId = self.feedId {
|
|
145
|
+
ShortKitBridge.shared?.unregisterFeed(id: feedId)
|
|
299
146
|
}
|
|
300
|
-
return
|
|
147
|
+
guard let feedVC = feedViewController else { return }
|
|
148
|
+
|
|
149
|
+
feedVC.deactivate()
|
|
150
|
+
feedVC.willMove(toParent: nil)
|
|
151
|
+
feedVC.view.removeFromSuperview()
|
|
152
|
+
feedVC.removeFromParent()
|
|
153
|
+
|
|
154
|
+
self.feedViewController = nil
|
|
301
155
|
}
|
|
302
156
|
|
|
303
157
|
// MARK: - Helpers
|
|
@@ -23,7 +23,8 @@ RCT_EXPORT_MODULE(ShortKitFeedView)
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
RCT_EXPORT_VIEW_PROPERTY(config, NSString)
|
|
26
|
-
RCT_EXPORT_VIEW_PROPERTY(
|
|
27
|
-
RCT_EXPORT_VIEW_PROPERTY(
|
|
26
|
+
RCT_EXPORT_VIEW_PROPERTY(startAtItemId, NSString)
|
|
27
|
+
RCT_EXPORT_VIEW_PROPERTY(preloadId, NSString)
|
|
28
|
+
RCT_EXPORT_VIEW_PROPERTY(feedId, NSString)
|
|
28
29
|
|
|
29
30
|
@end
|
package/ios/ShortKitModule.mm
CHANGED
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
@implementation ShortKitModule {
|
|
12
12
|
ShortKitBridge *_shortKitBridge;
|
|
13
13
|
BOOL _hasListeners;
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
id _surfacePresenter;
|
|
15
|
+
// Buffer feed ops that arrive before the bridge is initialized.
|
|
16
|
+
// React fires child effects (MainScreen) before parent effects (ShortKitProvider),
|
|
17
|
+
// so setFeedItems can be called before initialize completes.
|
|
18
|
+
NSMutableArray<void(^)(void)> *_pendingBridgeOps;
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
RCT_EXPORT_MODULE(ShortKitModule)
|
|
@@ -33,6 +36,13 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
33
36
|
return self;
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
/// Called automatically by RCTInstance for modules that respond to this selector.
|
|
40
|
+
/// This is the recommended way to receive the surface presenter in the new architecture.
|
|
41
|
+
- (void)setSurfacePresenter:(id)surfacePresenter {
|
|
42
|
+
_surfacePresenter = surfacePresenter;
|
|
43
|
+
[_shortKitBridge updateSurfacePresenter:surfacePresenter];
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
/// Called when the RN bridge tears down (hot reload, app shutdown).
|
|
37
47
|
- (void)invalidate {
|
|
38
48
|
[_shortKitBridge teardown];
|
|
@@ -58,19 +68,21 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
58
68
|
@"onFormatChange",
|
|
59
69
|
@"onPrefetchedAheadCountChanged",
|
|
60
70
|
@"onRemainingContentCountChanged",
|
|
61
|
-
@"onError",
|
|
62
|
-
@"onShareTapped",
|
|
63
71
|
@"onSurveyResponse",
|
|
64
|
-
@"onOverlayConfigure",
|
|
65
|
-
@"onOverlayActivate",
|
|
66
|
-
@"onOverlayReset",
|
|
67
|
-
@"onOverlayTap",
|
|
68
|
-
@"onOverlayDoubleTap",
|
|
69
72
|
@"onContentTapped",
|
|
70
|
-
@"
|
|
71
|
-
@"
|
|
72
|
-
@"
|
|
73
|
-
@"
|
|
73
|
+
@"onDismiss",
|
|
74
|
+
@"onFeedReady",
|
|
75
|
+
@"onRefreshRequested",
|
|
76
|
+
@"onDidFetchContentItems",
|
|
77
|
+
@"onOverlayActiveChanged",
|
|
78
|
+
@"onOverlayPlayerStateChanged",
|
|
79
|
+
@"onOverlayMutedChanged",
|
|
80
|
+
@"onOverlayPlaybackRateChanged",
|
|
81
|
+
@"onOverlayCaptionsEnabledChanged",
|
|
82
|
+
@"onOverlayActiveCueChanged",
|
|
83
|
+
@"onOverlayFeedScrollPhaseChanged",
|
|
84
|
+
@"onOverlayTimeUpdate",
|
|
85
|
+
@"onOverlayFullState",
|
|
74
86
|
];
|
|
75
87
|
}
|
|
76
88
|
|
|
@@ -108,7 +120,7 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
108
120
|
// MARK: - Lifecycle
|
|
109
121
|
|
|
110
122
|
RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
|
|
111
|
-
|
|
123
|
+
hasLoadingView:(BOOL)hasLoadingView
|
|
112
124
|
clientAppName:(NSString *)clientAppName
|
|
113
125
|
clientAppVersion:(NSString *)clientAppVersion
|
|
114
126
|
customDimensions:(NSString *)customDimensions) {
|
|
@@ -116,20 +128,19 @@ RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
|
|
|
116
128
|
[_shortKitBridge teardown];
|
|
117
129
|
|
|
118
130
|
_shortKitBridge = [[ShortKitBridge alloc] initWithApiKey:apiKey
|
|
119
|
-
|
|
131
|
+
hasLoadingView:hasLoadingView
|
|
120
132
|
clientAppName:clientAppName
|
|
121
133
|
clientAppVersion:clientAppVersion
|
|
122
134
|
customDimensions:customDimensions
|
|
123
|
-
delegate:self
|
|
135
|
+
delegate:self
|
|
136
|
+
surfacePresenter:_surfacePresenter];
|
|
124
137
|
|
|
125
|
-
// Replay any feed
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
[_shortKitBridge appendFeedItems:_pendingAppendItems];
|
|
132
|
-
_pendingAppendItems = nil;
|
|
138
|
+
// Replay any feed ops that arrived before the bridge was ready
|
|
139
|
+
if (_pendingBridgeOps) {
|
|
140
|
+
for (void(^op)(void) in _pendingBridgeOps) {
|
|
141
|
+
op();
|
|
142
|
+
}
|
|
143
|
+
_pendingBridgeOps = nil;
|
|
133
144
|
}
|
|
134
145
|
}
|
|
135
146
|
|
|
@@ -206,30 +217,57 @@ RCT_EXPORT_METHOD(setMaxBitrate:(double)bitrate) {
|
|
|
206
217
|
|
|
207
218
|
// MARK: - Custom Feed
|
|
208
219
|
|
|
209
|
-
RCT_EXPORT_METHOD(setFeedItems:(NSString *)items) {
|
|
220
|
+
RCT_EXPORT_METHOD(setFeedItems:(NSString *)feedId items:(NSString *)items) {
|
|
210
221
|
if (_shortKitBridge) {
|
|
211
|
-
[_shortKitBridge setFeedItems:items];
|
|
222
|
+
[_shortKitBridge setFeedItems:feedId items:items];
|
|
212
223
|
} else {
|
|
213
|
-
|
|
224
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
225
|
+
[_pendingBridgeOps addObject:^{
|
|
226
|
+
[self->_shortKitBridge setFeedItems:feedId items:items];
|
|
227
|
+
}];
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
|
|
217
|
-
RCT_EXPORT_METHOD(appendFeedItems:(NSString *)items) {
|
|
231
|
+
RCT_EXPORT_METHOD(appendFeedItems:(NSString *)feedId items:(NSString *)items) {
|
|
218
232
|
if (_shortKitBridge) {
|
|
219
|
-
[_shortKitBridge appendFeedItems:items];
|
|
233
|
+
[_shortKitBridge appendFeedItems:feedId items:items];
|
|
220
234
|
} else {
|
|
221
|
-
|
|
235
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
236
|
+
[_pendingBridgeOps addObject:^{
|
|
237
|
+
[self->_shortKitBridge appendFeedItems:feedId items:items];
|
|
238
|
+
}];
|
|
222
239
|
}
|
|
223
240
|
}
|
|
224
241
|
|
|
225
242
|
RCT_EXPORT_METHOD(fetchContent:(NSInteger)limit
|
|
243
|
+
filterJSON:(NSString *)filterJSON
|
|
226
244
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
227
245
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
228
|
-
[_shortKitBridge fetchContent:limit completion:^(NSString *json) {
|
|
246
|
+
[_shortKitBridge fetchContent:limit filterJSON:filterJSON completion:^(NSString *json) {
|
|
229
247
|
resolve(json);
|
|
230
248
|
}];
|
|
231
249
|
}
|
|
232
250
|
|
|
251
|
+
RCT_EXPORT_METHOD(applyFilter:(NSString *)feedId filterJSON:(NSString *)filterJSON) {
|
|
252
|
+
if (_shortKitBridge) {
|
|
253
|
+
[_shortKitBridge applyFilter:feedId filterJSON:filterJSON];
|
|
254
|
+
} else {
|
|
255
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
256
|
+
[_pendingBridgeOps addObject:^{
|
|
257
|
+
[self->_shortKitBridge applyFilter:feedId filterJSON:filterJSON];
|
|
258
|
+
}];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
RCT_EXPORT_METHOD(preloadFeed:(NSString *)configJSON
|
|
263
|
+
itemsJSON:(NSString *)itemsJSON
|
|
264
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
265
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
266
|
+
[_shortKitBridge preloadFeed:configJSON itemsJSON:itemsJSON completion:^(NSString *uuid) {
|
|
267
|
+
resolve(uuid);
|
|
268
|
+
}];
|
|
269
|
+
}
|
|
270
|
+
|
|
233
271
|
// MARK: - Storyboard / Seek Thumbnails
|
|
234
272
|
|
|
235
273
|
RCT_EXPORT_METHOD(prefetchStoryboard:(NSString *)playbackId) {
|
|
@@ -248,12 +286,6 @@ RCT_EXPORT_METHOD(getStoryboardData:(NSString *)playbackId
|
|
|
248
286
|
}];
|
|
249
287
|
}
|
|
250
288
|
|
|
251
|
-
// MARK: - Overlay Lifecycle
|
|
252
|
-
|
|
253
|
-
RCT_EXPORT_METHOD(notifyOverlayReady) {
|
|
254
|
-
[_shortKitBridge notifyOverlayReady];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
289
|
// MARK: - New Architecture (TurboModule)
|
|
258
290
|
|
|
259
291
|
#ifdef RCT_NEW_ARCH_ENABLED
|
|
@@ -49,7 +49,7 @@ import ShortKitSDK
|
|
|
49
49
|
public override func willMove(toWindow newWindow: UIWindow?) {
|
|
50
50
|
super.willMove(toWindow: newWindow)
|
|
51
51
|
if newWindow == nil {
|
|
52
|
-
|
|
52
|
+
suspendPlayerVC()
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -58,13 +58,30 @@ import ShortKitSDK
|
|
|
58
58
|
playerVC?.view.frame = bounds
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
deinit {
|
|
62
|
+
destroyPlayerVC()
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
// MARK: - VC Containment
|
|
62
66
|
|
|
63
67
|
private func embedPlayerVCIfNeeded() {
|
|
64
|
-
guard playerVC == nil else { return }
|
|
65
|
-
guard let sdk = ShortKitBridge.shared?.sdk else { return }
|
|
66
68
|
guard let parentVC = findParentViewController() else { return }
|
|
67
69
|
|
|
70
|
+
// Re-attach a suspended VC (e.g. after native stack pop)
|
|
71
|
+
if let vc = playerVC {
|
|
72
|
+
parentVC.addChild(vc)
|
|
73
|
+
vc.view.frame = bounds
|
|
74
|
+
addSubview(vc.view)
|
|
75
|
+
vc.didMove(toParent: parentVC)
|
|
76
|
+
if active {
|
|
77
|
+
vc.activate()
|
|
78
|
+
}
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// First mount — create a new VC
|
|
83
|
+
guard let sdk = ShortKitBridge.shared?.sdk else { return }
|
|
84
|
+
|
|
68
85
|
let playerConfig = parsedConfig ?? PlayerConfig()
|
|
69
86
|
|
|
70
87
|
let vc = ShortKitPlayerViewController(shortKit: sdk, config: playerConfig)
|
|
@@ -88,7 +105,21 @@ import ShortKitSDK
|
|
|
88
105
|
}
|
|
89
106
|
}
|
|
90
107
|
|
|
91
|
-
|
|
108
|
+
/// Detach the playerVC from the parent VC hierarchy without destroying it.
|
|
109
|
+
/// Called when the native stack temporarily removes this view from the window
|
|
110
|
+
/// (e.g. pushing a new screen on top). The playerVC and its thumbnail are
|
|
111
|
+
/// preserved so they can be re-attached instantly when the view returns.
|
|
112
|
+
private func suspendPlayerVC() {
|
|
113
|
+
guard let vc = playerVC else { return }
|
|
114
|
+
vc.deactivate()
|
|
115
|
+
vc.willMove(toParent: nil)
|
|
116
|
+
vc.view.removeFromSuperview()
|
|
117
|
+
vc.removeFromParent()
|
|
118
|
+
// Keep playerVC reference — re-attached in embedPlayerVCIfNeeded
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Full teardown — called from deinit when React unmounts the native view.
|
|
122
|
+
private func destroyPlayerVC() {
|
|
92
123
|
guard let vc = playerVC else { return }
|
|
93
124
|
vc.deactivate()
|
|
94
125
|
vc.willMove(toParent: nil)
|
|
@@ -104,7 +135,7 @@ import ShortKitSDK
|
|
|
104
135
|
parsedConfig = Self.parsePlayerConfig(json)
|
|
105
136
|
// Config changes require re-embedding
|
|
106
137
|
if playerVC != nil {
|
|
107
|
-
|
|
138
|
+
destroyPlayerVC()
|
|
108
139
|
embedPlayerVCIfNeeded()
|
|
109
140
|
}
|
|
110
141
|
}
|
|
@@ -148,9 +179,9 @@ import ShortKitSDK
|
|
|
148
179
|
if let overlayObj = obj["overlay"] as? [String: Any],
|
|
149
180
|
overlayObj["type"] as? String == "custom" {
|
|
150
181
|
overlayMode = .custom { @Sendable in
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
return
|
|
182
|
+
let host = ReactOverlayHost()
|
|
183
|
+
host.surfacePresenter = ShortKitBridge.shared?.surfacePresenter
|
|
184
|
+
return host
|
|
154
185
|
}
|
|
155
186
|
} else {
|
|
156
187
|
overlayMode = .none
|
package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h
CHANGED
|
@@ -365,7 +365,7 @@ SWIFT_CLASS("_TtC11ShortKitSDK26ShortKitFeedViewController")
|
|
|
365
365
|
|
|
366
366
|
/// Public single-video player view controller.
|
|
367
367
|
/// Starts in thumbnail-only mode and borrows an AVPlayer from the shared
|
|
368
|
-
/// <code>
|
|
368
|
+
/// <code>PlayerPool</code> tile slots when activated. Call <code>activate()</code> when the tile becomes
|
|
369
369
|
/// visible and <code>deactivate()</code> when it scrolls offscreen.
|
|
370
370
|
SWIFT_CLASS("_TtC11ShortKitSDK28ShortKitPlayerViewController")
|
|
371
371
|
@interface ShortKitPlayerViewController : UIViewController
|