@shortkitsdk/react-native 0.2.6 → 0.2.11
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 +5 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +319 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +559 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +984 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +88 -220
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +12 -3
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +123 -741
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +2 -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 +458 -0
- package/ios/SKFabricSurfaceWrapper.h +18 -0
- package/ios/SKFabricSurfaceWrapper.mm +57 -0
- package/ios/ShortKitBridge.swift +186 -63
- package/ios/ShortKitFeedView.swift +62 -229
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +66 -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 +2380 -522
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +39 -12
- 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 +39 -12
- 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 +2380 -522
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +39 -12
- 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 +39 -12
- 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 -25
- package/src/ShortKitFeed.tsx +110 -41
- package/src/ShortKitLoadingSurface.tsx +24 -0
- package/src/ShortKitOverlaySurface.tsx +205 -0
- package/src/ShortKitPlayer.tsx +6 -7
- package/src/ShortKitProvider.tsx +27 -286
- package/src/index.ts +5 -3
- package/src/serialization.ts +19 -39
- package/src/specs/NativeShortKitModule.ts +58 -46
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +78 -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,88 @@ 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
|
-
|
|
74
|
-
|
|
59
|
+
guard let parentVC = findParentViewController() else {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
75
62
|
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
if let feedId = self.feedId {
|
|
70
|
+
ShortKitBridge.shared?.registerFeed(id: feedId, viewController: feedVC)
|
|
71
|
+
}
|
|
78
72
|
return
|
|
79
73
|
}
|
|
80
74
|
|
|
81
|
-
guard let
|
|
82
|
-
NSLog("[ShortKitFeedView] Could not find parent UIViewController.")
|
|
75
|
+
guard let sdk = ShortKitBridge.shared?.sdk else {
|
|
83
76
|
return
|
|
84
77
|
}
|
|
85
78
|
|
|
86
|
-
|
|
79
|
+
// Parse config from the Fabric view prop
|
|
80
|
+
var feedConfig = ShortKitBridge.parseFeedConfig(self.config ?? "{}")
|
|
81
|
+
|
|
82
|
+
// Consume preload handle if available
|
|
83
|
+
if let preloadId = self.preloadId,
|
|
84
|
+
let preload = ShortKitBridge.shared?.consumePreload(id: preloadId) {
|
|
85
|
+
feedConfig.preload = preload
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let feedVC = ShortKitFeedViewController(shortKit: sdk, config: feedConfig, startAtItemId: startAtItemId)
|
|
89
|
+
|
|
90
|
+
feedVC.onDismiss = {
|
|
91
|
+
ShortKitBridge.shared?.emitDismiss()
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
self.feedViewController = feedVC
|
|
95
|
+
if let feedId = self.feedId {
|
|
96
|
+
ShortKitBridge.shared?.registerFeed(id: feedId, viewController: feedVC)
|
|
97
|
+
}
|
|
88
98
|
|
|
89
99
|
parentVC.addChild(feedVC)
|
|
90
100
|
feedVC.view.frame = bounds
|
|
91
101
|
feedVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
92
102
|
addSubview(feedVC.view)
|
|
93
103
|
feedVC.didMove(toParent: parentVC)
|
|
94
|
-
|
|
95
|
-
setupScrollTracking(feedVC)
|
|
96
|
-
setupOverlayReadyObserver()
|
|
97
104
|
}
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
/// Detach the feedVC from the parent VC hierarchy without destroying it.
|
|
107
|
+
/// Called when the native stack temporarily removes this view from the window
|
|
108
|
+
/// (e.g. pushing a new screen on top). The feedVC and its state are preserved
|
|
109
|
+
/// so they can be re-attached when the view returns to the window.
|
|
110
|
+
private func suspendFeedViewController() {
|
|
111
|
+
if let feedId = self.feedId {
|
|
112
|
+
ShortKitBridge.shared?.unregisterFeed(id: feedId)
|
|
106
113
|
}
|
|
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
114
|
guard let feedVC = feedViewController else { return }
|
|
119
115
|
|
|
120
116
|
feedVC.willMove(toParent: nil)
|
|
121
117
|
feedVC.view.removeFromSuperview()
|
|
122
118
|
feedVC.removeFromParent()
|
|
123
|
-
|
|
124
|
-
self.feedViewController = nil
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// MARK: - Scroll Tracking
|
|
128
|
-
|
|
129
|
-
private func setupScrollTracking(_ feedVC: ShortKitFeedViewController) {
|
|
130
|
-
guard let scrollView = findScrollView(in: feedVC.view) else {
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
scrollObservation = scrollView.observe(\.contentOffset, options: [.new]) { [weak self] sv, _ in
|
|
135
|
-
self?.handleScrollOffset(sv)
|
|
136
|
-
}
|
|
119
|
+
// Keep feedViewController reference — re-attached in embedFeedViewControllerIfNeeded
|
|
137
120
|
}
|
|
138
121
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
let offset = scrollView.contentOffset.y
|
|
144
|
-
|
|
145
|
-
// Detect page change, but DEFER updating currentPage until JS
|
|
146
|
-
// signals that overlay-current has been re-rendered with new content.
|
|
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)
|
|
179
|
-
}
|
|
180
|
-
} else if pendingSwap != nil {
|
|
181
|
-
// User is scrolling again — execute pending swap immediately
|
|
182
|
-
// so transforms stay aligned for the new gesture.
|
|
183
|
-
pendingSwap?()
|
|
122
|
+
/// Full teardown — called from deinit when React unmounts the native view.
|
|
123
|
+
private func destroyFeedViewController() {
|
|
124
|
+
if let feedId = self.feedId {
|
|
125
|
+
ShortKitBridge.shared?.unregisterFeed(id: feedId)
|
|
184
126
|
}
|
|
127
|
+
guard let feedVC = feedViewController else { return }
|
|
185
128
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if currentOverlayView == nil || nextOverlayView == nil {
|
|
190
|
-
findOverlayViews()
|
|
191
|
-
}
|
|
192
|
-
if currentCarouselOverlayView == nil || nextCarouselOverlayView == nil {
|
|
193
|
-
findCarouselOverlayViews()
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
let translateY = CGAffineTransform(translationX: 0, y: -delta)
|
|
197
|
-
|
|
198
|
-
// Current overlays follow the active cell
|
|
199
|
-
currentOverlayView?.transform = translateY
|
|
200
|
-
currentCarouselOverlayView?.transform = translateY
|
|
201
|
-
|
|
202
|
-
// Next overlays: positioned one page ahead in the scroll direction
|
|
203
|
-
if delta >= 0 {
|
|
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
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// MARK: - Overlay Ready Handshake
|
|
217
|
-
|
|
218
|
-
private func setupOverlayReadyObserver() {
|
|
219
|
-
overlayReadyObserver = NotificationCenter.default.addObserver(
|
|
220
|
-
forName: .shortKitOverlayReady,
|
|
221
|
-
object: nil,
|
|
222
|
-
queue: .main
|
|
223
|
-
) { [weak self] _ in
|
|
224
|
-
self?.pendingSwap?()
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/// Find the sibling RN overlay views by nativeID.
|
|
229
|
-
///
|
|
230
|
-
/// In Fabric interop, `ShortKitFeedView` (a Paper-style `RCTViewManager`
|
|
231
|
-
/// view) is wrapped in an intermediate `RCTViewComponentView`. So
|
|
232
|
-
/// `self.superview` is that wrapper — not the React `<View>` container
|
|
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
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/// Find the sibling RN carousel overlay views by nativeID.
|
|
255
|
-
private func findCarouselOverlayViews() {
|
|
256
|
-
var ancestor: UIView? = superview
|
|
257
|
-
while let container = ancestor {
|
|
258
|
-
for child in container.subviews {
|
|
259
|
-
guard !self.isDescendant(of: child) else { continue }
|
|
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
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/// Recursively find a view by its React Native `nativeID` prop.
|
|
274
|
-
/// In Fabric, this is stored on `RCTViewComponentView.nativeId`,
|
|
275
|
-
/// not `accessibilityIdentifier`.
|
|
276
|
-
private func findView(withNativeID nativeID: String, in view: UIView) -> UIView? {
|
|
277
|
-
if view.responds(to: Selector(("nativeId"))),
|
|
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
|
|
288
|
-
}
|
|
129
|
+
feedVC.willMove(toParent: nil)
|
|
130
|
+
feedVC.view.removeFromSuperview()
|
|
131
|
+
feedVC.removeFromParent()
|
|
289
132
|
|
|
290
|
-
|
|
291
|
-
private func findScrollView(in view: UIView) -> UIScrollView? {
|
|
292
|
-
if let sv = view as? UIScrollView {
|
|
293
|
-
return sv
|
|
294
|
-
}
|
|
295
|
-
for subview in view.subviews {
|
|
296
|
-
if let sv = findScrollView(in: subview) {
|
|
297
|
-
return sv
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return nil
|
|
133
|
+
self.feedViewController = nil
|
|
301
134
|
}
|
|
302
135
|
|
|
303
136
|
// 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,19 @@ 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
|
+
@"onRefreshRequested",
|
|
75
|
+
@"onDidFetchContentItems",
|
|
76
|
+
@"onOverlayActiveChanged",
|
|
77
|
+
@"onOverlayPlayerStateChanged",
|
|
78
|
+
@"onOverlayMutedChanged",
|
|
79
|
+
@"onOverlayPlaybackRateChanged",
|
|
80
|
+
@"onOverlayCaptionsEnabledChanged",
|
|
81
|
+
@"onOverlayActiveCueChanged",
|
|
82
|
+
@"onOverlayFeedScrollPhaseChanged",
|
|
83
|
+
@"onOverlayTimeUpdate",
|
|
74
84
|
];
|
|
75
85
|
}
|
|
76
86
|
|
|
@@ -108,7 +118,7 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
108
118
|
// MARK: - Lifecycle
|
|
109
119
|
|
|
110
120
|
RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
|
|
111
|
-
|
|
121
|
+
hasLoadingView:(BOOL)hasLoadingView
|
|
112
122
|
clientAppName:(NSString *)clientAppName
|
|
113
123
|
clientAppVersion:(NSString *)clientAppVersion
|
|
114
124
|
customDimensions:(NSString *)customDimensions) {
|
|
@@ -116,20 +126,19 @@ RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
|
|
|
116
126
|
[_shortKitBridge teardown];
|
|
117
127
|
|
|
118
128
|
_shortKitBridge = [[ShortKitBridge alloc] initWithApiKey:apiKey
|
|
119
|
-
|
|
129
|
+
hasLoadingView:hasLoadingView
|
|
120
130
|
clientAppName:clientAppName
|
|
121
131
|
clientAppVersion:clientAppVersion
|
|
122
132
|
customDimensions:customDimensions
|
|
123
|
-
delegate:self
|
|
133
|
+
delegate:self
|
|
134
|
+
surfacePresenter:_surfacePresenter];
|
|
124
135
|
|
|
125
|
-
// Replay any feed
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
[_shortKitBridge appendFeedItems:_pendingAppendItems];
|
|
132
|
-
_pendingAppendItems = nil;
|
|
136
|
+
// Replay any feed ops that arrived before the bridge was ready
|
|
137
|
+
if (_pendingBridgeOps) {
|
|
138
|
+
for (void(^op)(void) in _pendingBridgeOps) {
|
|
139
|
+
op();
|
|
140
|
+
}
|
|
141
|
+
_pendingBridgeOps = nil;
|
|
133
142
|
}
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -206,30 +215,56 @@ RCT_EXPORT_METHOD(setMaxBitrate:(double)bitrate) {
|
|
|
206
215
|
|
|
207
216
|
// MARK: - Custom Feed
|
|
208
217
|
|
|
209
|
-
RCT_EXPORT_METHOD(setFeedItems:(NSString *)items) {
|
|
218
|
+
RCT_EXPORT_METHOD(setFeedItems:(NSString *)feedId items:(NSString *)items) {
|
|
210
219
|
if (_shortKitBridge) {
|
|
211
|
-
[_shortKitBridge setFeedItems:items];
|
|
220
|
+
[_shortKitBridge setFeedItems:feedId items:items];
|
|
212
221
|
} else {
|
|
213
|
-
|
|
222
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
223
|
+
[_pendingBridgeOps addObject:^{
|
|
224
|
+
[self->_shortKitBridge setFeedItems:feedId items:items];
|
|
225
|
+
}];
|
|
214
226
|
}
|
|
215
227
|
}
|
|
216
228
|
|
|
217
|
-
RCT_EXPORT_METHOD(appendFeedItems:(NSString *)items) {
|
|
229
|
+
RCT_EXPORT_METHOD(appendFeedItems:(NSString *)feedId items:(NSString *)items) {
|
|
218
230
|
if (_shortKitBridge) {
|
|
219
|
-
[_shortKitBridge appendFeedItems:items];
|
|
231
|
+
[_shortKitBridge appendFeedItems:feedId items:items];
|
|
220
232
|
} else {
|
|
221
|
-
|
|
233
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
234
|
+
[_pendingBridgeOps addObject:^{
|
|
235
|
+
[self->_shortKitBridge appendFeedItems:feedId items:items];
|
|
236
|
+
}];
|
|
222
237
|
}
|
|
223
238
|
}
|
|
224
239
|
|
|
225
240
|
RCT_EXPORT_METHOD(fetchContent:(NSInteger)limit
|
|
241
|
+
filterJSON:(NSString *)filterJSON
|
|
226
242
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
227
243
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
228
|
-
[_shortKitBridge fetchContent:limit completion:^(NSString *json) {
|
|
244
|
+
[_shortKitBridge fetchContent:limit filterJSON:filterJSON completion:^(NSString *json) {
|
|
229
245
|
resolve(json);
|
|
230
246
|
}];
|
|
231
247
|
}
|
|
232
248
|
|
|
249
|
+
RCT_EXPORT_METHOD(applyFilter:(NSString *)feedId filterJSON:(NSString *)filterJSON) {
|
|
250
|
+
if (_shortKitBridge) {
|
|
251
|
+
[_shortKitBridge applyFilter:feedId filterJSON:filterJSON];
|
|
252
|
+
} else {
|
|
253
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
254
|
+
[_pendingBridgeOps addObject:^{
|
|
255
|
+
[self->_shortKitBridge applyFilter:feedId filterJSON:filterJSON];
|
|
256
|
+
}];
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
RCT_EXPORT_METHOD(preloadFeed:(NSString *)configJSON
|
|
261
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
262
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
263
|
+
[_shortKitBridge preloadFeed:configJSON completion:^(NSString *uuid) {
|
|
264
|
+
resolve(uuid);
|
|
265
|
+
}];
|
|
266
|
+
}
|
|
267
|
+
|
|
233
268
|
// MARK: - Storyboard / Seek Thumbnails
|
|
234
269
|
|
|
235
270
|
RCT_EXPORT_METHOD(prefetchStoryboard:(NSString *)playbackId) {
|
|
@@ -248,12 +283,6 @@ RCT_EXPORT_METHOD(getStoryboardData:(NSString *)playbackId
|
|
|
248
283
|
}];
|
|
249
284
|
}
|
|
250
285
|
|
|
251
|
-
// MARK: - Overlay Lifecycle
|
|
252
|
-
|
|
253
|
-
RCT_EXPORT_METHOD(notifyOverlayReady) {
|
|
254
|
-
[_shortKitBridge notifyOverlayReady];
|
|
255
|
-
}
|
|
256
|
-
|
|
257
286
|
// MARK: - New Architecture (TurboModule)
|
|
258
287
|
|
|
259
288
|
#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
|