@shortkitsdk/react-native 0.2.5 → 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 +126 -706
- 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 +266 -65
- package/ios/ShortKitFeedView.swift +63 -207
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +86 -32
- 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 +2 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3998 -962
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +85 -24
- 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 +85 -24
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +2 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3998 -962
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +85 -24
- 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 +85 -24
- 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 +11 -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 +65 -250
- package/src/index.ts +9 -4
- package/src/serialization.ts +22 -42
- package/src/specs/NativeShortKitModule.ts +67 -53
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +104 -19
- package/src/useShortKit.ts +1 -3
- package/src/useShortKitPlayer.ts +7 -8
- 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 -54
- package/ios/ShortKitOverlayBridge.swift +0 -113
- package/src/CarouselOverlayManager.tsx +0 -71
- package/src/OverlayManager.tsx +0 -87
- package/src/useShortKitCarousel.ts +0 -29
|
@@ -4,42 +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
|
-
/// Deferred page update to avoid flashing stale metadata.
|
|
41
|
-
private var pageUpdateWorkItem: DispatchWorkItem?
|
|
42
|
-
|
|
43
29
|
// MARK: - Lifecycle
|
|
44
30
|
|
|
45
31
|
public override func didMoveToWindow() {
|
|
@@ -54,7 +40,7 @@ import ShortKitSDK
|
|
|
54
40
|
super.willMove(toWindow: newWindow)
|
|
55
41
|
|
|
56
42
|
if newWindow == nil {
|
|
57
|
-
|
|
43
|
+
suspendFeedViewController()
|
|
58
44
|
}
|
|
59
45
|
}
|
|
60
46
|
|
|
@@ -63,218 +49,88 @@ import ShortKitSDK
|
|
|
63
49
|
feedViewController?.view.frame = bounds
|
|
64
50
|
}
|
|
65
51
|
|
|
52
|
+
deinit {
|
|
53
|
+
destroyFeedViewController()
|
|
54
|
+
}
|
|
55
|
+
|
|
66
56
|
// MARK: - VC Containment
|
|
67
57
|
|
|
68
58
|
private func embedFeedViewControllerIfNeeded() {
|
|
69
|
-
|
|
70
|
-
|
|
59
|
+
guard let parentVC = findParentViewController() else {
|
|
60
|
+
return
|
|
61
|
+
}
|
|
71
62
|
|
|
72
|
-
|
|
73
|
-
|
|
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
|
+
}
|
|
74
72
|
return
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
guard let
|
|
78
|
-
NSLog("[ShortKitFeedView] Could not find parent UIViewController.")
|
|
75
|
+
guard let sdk = ShortKitBridge.shared?.sdk else {
|
|
79
76
|
return
|
|
80
77
|
}
|
|
81
78
|
|
|
82
|
-
|
|
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
|
+
|
|
83
94
|
self.feedViewController = feedVC
|
|
95
|
+
if let feedId = self.feedId {
|
|
96
|
+
ShortKitBridge.shared?.registerFeed(id: feedId, viewController: feedVC)
|
|
97
|
+
}
|
|
84
98
|
|
|
85
99
|
parentVC.addChild(feedVC)
|
|
86
100
|
feedVC.view.frame = bounds
|
|
87
101
|
feedVC.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
88
102
|
addSubview(feedVC.view)
|
|
89
103
|
feedVC.didMove(toParent: parentVC)
|
|
90
|
-
|
|
91
|
-
setupScrollTracking(feedVC)
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
nextCarouselOverlayView?.transform = .identity
|
|
103
|
-
currentOverlayView = nil
|
|
104
|
-
nextOverlayView = nil
|
|
105
|
-
currentCarouselOverlayView = nil
|
|
106
|
-
nextCarouselOverlayView = nil
|
|
107
|
-
|
|
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)
|
|
113
|
+
}
|
|
108
114
|
guard let feedVC = feedViewController else { return }
|
|
109
115
|
|
|
110
116
|
feedVC.willMove(toParent: nil)
|
|
111
117
|
feedVC.view.removeFromSuperview()
|
|
112
118
|
feedVC.removeFromParent()
|
|
113
|
-
|
|
114
|
-
self.feedViewController = nil
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// MARK: - Scroll Tracking
|
|
118
|
-
|
|
119
|
-
private func setupScrollTracking(_ feedVC: ShortKitFeedViewController) {
|
|
120
|
-
guard let scrollView = findScrollView(in: feedVC.view) else {
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
scrollObservation = scrollView.observe(\.contentOffset, options: [.new]) { [weak self] sv, _ in
|
|
125
|
-
self?.handleScrollOffset(sv)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private func handleScrollOffset(_ scrollView: UIScrollView) {
|
|
130
|
-
let cellHeight = scrollView.bounds.height
|
|
131
|
-
guard cellHeight > 0 else { return }
|
|
132
|
-
|
|
133
|
-
let offset = scrollView.contentOffset.y
|
|
134
|
-
|
|
135
|
-
// Detect page change, but DEFER updating currentPage.
|
|
136
|
-
//
|
|
137
|
-
// Why: when the scroll settles on a new page, overlay-current still
|
|
138
|
-
// shows the OLD page's metadata (React hasn't processed OVERLAY_ACTIVATE
|
|
139
|
-
// yet). If we update currentPage immediately, delta snaps to 0 and
|
|
140
|
-
// overlay-current becomes visible with stale data.
|
|
141
|
-
//
|
|
142
|
-
// By deferring ~80ms, overlay-next (which already shows the correct
|
|
143
|
-
// data via NextOverlayProvider) stays visible at y=0 while React
|
|
144
|
-
// processes the state update. After the delay, overlay-current has
|
|
145
|
-
// been re-rendered with correct data and takes over seamlessly.
|
|
146
|
-
let nearestPage = Int(round(offset / cellHeight))
|
|
147
|
-
if abs(offset - CGFloat(nearestPage) * cellHeight) < 1.0 {
|
|
148
|
-
if nearestPage != currentPage && pageUpdateWorkItem == nil {
|
|
149
|
-
let targetPage = nearestPage
|
|
150
|
-
let workItem = DispatchWorkItem { [weak self] in
|
|
151
|
-
guard let self else { return }
|
|
152
|
-
self.currentPage = targetPage
|
|
153
|
-
self.pageUpdateWorkItem = nil
|
|
154
|
-
// Reapply overlay transforms now that currentPage is updated.
|
|
155
|
-
// Without this, overlay-next (static NextOverlayProvider state)
|
|
156
|
-
// stays visible at y=0 while overlay-current (live state) stays
|
|
157
|
-
// hidden — no scroll event fires to trigger handleScrollOffset.
|
|
158
|
-
let h = self.bounds.height
|
|
159
|
-
self.currentOverlayView?.transform = .identity
|
|
160
|
-
self.nextOverlayView?.transform = CGAffineTransform(translationX: 0, y: h)
|
|
161
|
-
self.currentCarouselOverlayView?.transform = .identity
|
|
162
|
-
self.nextCarouselOverlayView?.transform = CGAffineTransform(translationX: 0, y: h)
|
|
163
|
-
}
|
|
164
|
-
pageUpdateWorkItem = workItem
|
|
165
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + 0.08, execute: workItem)
|
|
166
|
-
}
|
|
167
|
-
} else if let workItem = pageUpdateWorkItem {
|
|
168
|
-
// User is scrolling again — apply pending update immediately
|
|
169
|
-
// so transforms stay aligned for the new gesture.
|
|
170
|
-
workItem.cancel()
|
|
171
|
-
pageUpdateWorkItem = nil
|
|
172
|
-
currentPage = nearestPage
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let delta = offset - CGFloat(currentPage) * cellHeight
|
|
176
|
-
|
|
177
|
-
// Find the overlay views if not cached
|
|
178
|
-
if currentOverlayView == nil || nextOverlayView == nil {
|
|
179
|
-
findOverlayViews()
|
|
180
|
-
}
|
|
181
|
-
if currentCarouselOverlayView == nil || nextCarouselOverlayView == nil {
|
|
182
|
-
findCarouselOverlayViews()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let translateY = CGAffineTransform(translationX: 0, y: -delta)
|
|
186
|
-
|
|
187
|
-
// Current overlays follow the active cell
|
|
188
|
-
currentOverlayView?.transform = translateY
|
|
189
|
-
currentCarouselOverlayView?.transform = translateY
|
|
190
|
-
|
|
191
|
-
// Next overlays: positioned one page ahead in the scroll direction
|
|
192
|
-
if delta >= 0 {
|
|
193
|
-
// Forward scroll (or idle): next cell is below
|
|
194
|
-
let nextTransform = CGAffineTransform(translationX: 0, y: cellHeight - delta)
|
|
195
|
-
nextOverlayView?.transform = nextTransform
|
|
196
|
-
nextCarouselOverlayView?.transform = nextTransform
|
|
197
|
-
} else {
|
|
198
|
-
// Backward scroll: next cell is above
|
|
199
|
-
let nextTransform = CGAffineTransform(translationX: 0, y: -cellHeight - delta)
|
|
200
|
-
nextOverlayView?.transform = nextTransform
|
|
201
|
-
nextCarouselOverlayView?.transform = nextTransform
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/// Find the sibling RN overlay views by nativeID.
|
|
206
|
-
///
|
|
207
|
-
/// In Fabric interop, `ShortKitFeedView` (a Paper-style `RCTViewManager`
|
|
208
|
-
/// view) is wrapped in an intermediate `RCTViewComponentView`. So
|
|
209
|
-
/// `self.superview` is that wrapper — not the React `<View>` container
|
|
210
|
-
/// rendered by ShortKitFeed.tsx. We walk up the ancestor chain until we
|
|
211
|
-
/// find the overlays.
|
|
212
|
-
private func findOverlayViews() {
|
|
213
|
-
var ancestor: UIView? = superview
|
|
214
|
-
while let container = ancestor {
|
|
215
|
-
for child in container.subviews {
|
|
216
|
-
// Skip subtrees that contain ourselves
|
|
217
|
-
guard !self.isDescendant(of: child) else { continue }
|
|
218
|
-
|
|
219
|
-
if currentOverlayView == nil {
|
|
220
|
-
currentOverlayView = findView(withNativeID: "overlay-current", in: child)
|
|
221
|
-
}
|
|
222
|
-
if nextOverlayView == nil {
|
|
223
|
-
nextOverlayView = findView(withNativeID: "overlay-next", in: child)
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
if currentOverlayView != nil && nextOverlayView != nil { return }
|
|
227
|
-
ancestor = container.superview
|
|
228
|
-
}
|
|
119
|
+
// Keep feedViewController reference — re-attached in embedFeedViewControllerIfNeeded
|
|
229
120
|
}
|
|
230
121
|
|
|
231
|
-
///
|
|
232
|
-
private func
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
for child in container.subviews {
|
|
236
|
-
guard !self.isDescendant(of: child) else { continue }
|
|
237
|
-
|
|
238
|
-
if currentCarouselOverlayView == nil {
|
|
239
|
-
currentCarouselOverlayView = findView(withNativeID: "carousel-overlay-current", in: child)
|
|
240
|
-
}
|
|
241
|
-
if nextCarouselOverlayView == nil {
|
|
242
|
-
nextCarouselOverlayView = findView(withNativeID: "carousel-overlay-next", in: child)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if currentCarouselOverlayView != nil && nextCarouselOverlayView != nil { return }
|
|
246
|
-
ancestor = container.superview
|
|
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)
|
|
247
126
|
}
|
|
248
|
-
|
|
127
|
+
guard let feedVC = feedViewController else { return }
|
|
249
128
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
private func findView(withNativeID nativeID: String, in view: UIView) -> UIView? {
|
|
254
|
-
if view.responds(to: Selector(("nativeId"))),
|
|
255
|
-
let rnNativeId = view.value(forKey: "nativeId") as? String,
|
|
256
|
-
rnNativeId == nativeID {
|
|
257
|
-
return view
|
|
258
|
-
}
|
|
259
|
-
for subview in view.subviews {
|
|
260
|
-
if let found = findView(withNativeID: nativeID, in: subview) {
|
|
261
|
-
return found
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return nil
|
|
265
|
-
}
|
|
129
|
+
feedVC.willMove(toParent: nil)
|
|
130
|
+
feedVC.view.removeFromSuperview()
|
|
131
|
+
feedVC.removeFromParent()
|
|
266
132
|
|
|
267
|
-
|
|
268
|
-
private func findScrollView(in view: UIView) -> UIScrollView? {
|
|
269
|
-
if let sv = view as? UIScrollView {
|
|
270
|
-
return sv
|
|
271
|
-
}
|
|
272
|
-
for subview in view.subviews {
|
|
273
|
-
if let sv = findScrollView(in: subview) {
|
|
274
|
-
return sv
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return nil
|
|
133
|
+
self.feedViewController = nil
|
|
278
134
|
}
|
|
279
135
|
|
|
280
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];
|
|
@@ -54,20 +64,23 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
54
64
|
@"onActiveCueChanged",
|
|
55
65
|
@"onDidLoop",
|
|
56
66
|
@"onFeedTransition",
|
|
67
|
+
@"onFeedScrollPhase",
|
|
57
68
|
@"onFormatChange",
|
|
58
69
|
@"onPrefetchedAheadCountChanged",
|
|
59
70
|
@"onRemainingContentCountChanged",
|
|
60
|
-
@"onError",
|
|
61
|
-
@"onShareTapped",
|
|
62
71
|
@"onSurveyResponse",
|
|
63
|
-
@"onOverlayConfigure",
|
|
64
|
-
@"onOverlayActivate",
|
|
65
|
-
@"onOverlayReset",
|
|
66
|
-
@"onOverlayFadeOut",
|
|
67
|
-
@"onOverlayRestore",
|
|
68
|
-
@"onOverlayTap",
|
|
69
|
-
@"onOverlayDoubleTap",
|
|
70
72
|
@"onContentTapped",
|
|
73
|
+
@"onDismiss",
|
|
74
|
+
@"onRefreshRequested",
|
|
75
|
+
@"onDidFetchContentItems",
|
|
76
|
+
@"onOverlayActiveChanged",
|
|
77
|
+
@"onOverlayPlayerStateChanged",
|
|
78
|
+
@"onOverlayMutedChanged",
|
|
79
|
+
@"onOverlayPlaybackRateChanged",
|
|
80
|
+
@"onOverlayCaptionsEnabledChanged",
|
|
81
|
+
@"onOverlayActiveCueChanged",
|
|
82
|
+
@"onOverlayFeedScrollPhaseChanged",
|
|
83
|
+
@"onOverlayTimeUpdate",
|
|
71
84
|
];
|
|
72
85
|
}
|
|
73
86
|
|
|
@@ -105,8 +118,7 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
105
118
|
// MARK: - Lifecycle
|
|
106
119
|
|
|
107
120
|
RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
|
|
108
|
-
|
|
109
|
-
embedId:(NSString *)embedId
|
|
121
|
+
hasLoadingView:(BOOL)hasLoadingView
|
|
110
122
|
clientAppName:(NSString *)clientAppName
|
|
111
123
|
clientAppVersion:(NSString *)clientAppVersion
|
|
112
124
|
customDimensions:(NSString *)customDimensions) {
|
|
@@ -114,21 +126,19 @@ RCT_EXPORT_METHOD(initialize:(NSString *)apiKey
|
|
|
114
126
|
[_shortKitBridge teardown];
|
|
115
127
|
|
|
116
128
|
_shortKitBridge = [[ShortKitBridge alloc] initWithApiKey:apiKey
|
|
117
|
-
|
|
118
|
-
embedId:embedId
|
|
129
|
+
hasLoadingView:hasLoadingView
|
|
119
130
|
clientAppName:clientAppName
|
|
120
131
|
clientAppVersion:clientAppVersion
|
|
121
132
|
customDimensions:customDimensions
|
|
122
|
-
delegate:self
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
_pendingAppendItems = nil;
|
|
133
|
+
delegate:self
|
|
134
|
+
surfacePresenter:_surfacePresenter];
|
|
135
|
+
|
|
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;
|
|
132
142
|
}
|
|
133
143
|
}
|
|
134
144
|
|
|
@@ -205,30 +215,74 @@ RCT_EXPORT_METHOD(setMaxBitrate:(double)bitrate) {
|
|
|
205
215
|
|
|
206
216
|
// MARK: - Custom Feed
|
|
207
217
|
|
|
208
|
-
RCT_EXPORT_METHOD(setFeedItems:(NSString *)items) {
|
|
218
|
+
RCT_EXPORT_METHOD(setFeedItems:(NSString *)feedId items:(NSString *)items) {
|
|
209
219
|
if (_shortKitBridge) {
|
|
210
|
-
[_shortKitBridge setFeedItems:items];
|
|
220
|
+
[_shortKitBridge setFeedItems:feedId items:items];
|
|
211
221
|
} else {
|
|
212
|
-
|
|
222
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
223
|
+
[_pendingBridgeOps addObject:^{
|
|
224
|
+
[self->_shortKitBridge setFeedItems:feedId items:items];
|
|
225
|
+
}];
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
|
|
216
|
-
RCT_EXPORT_METHOD(appendFeedItems:(NSString *)items) {
|
|
229
|
+
RCT_EXPORT_METHOD(appendFeedItems:(NSString *)feedId items:(NSString *)items) {
|
|
217
230
|
if (_shortKitBridge) {
|
|
218
|
-
[_shortKitBridge appendFeedItems:items];
|
|
231
|
+
[_shortKitBridge appendFeedItems:feedId items:items];
|
|
219
232
|
} else {
|
|
220
|
-
|
|
233
|
+
if (!_pendingBridgeOps) _pendingBridgeOps = [NSMutableArray new];
|
|
234
|
+
[_pendingBridgeOps addObject:^{
|
|
235
|
+
[self->_shortKitBridge appendFeedItems:feedId items:items];
|
|
236
|
+
}];
|
|
221
237
|
}
|
|
222
238
|
}
|
|
223
239
|
|
|
224
240
|
RCT_EXPORT_METHOD(fetchContent:(NSInteger)limit
|
|
241
|
+
filterJSON:(NSString *)filterJSON
|
|
225
242
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
226
243
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
227
|
-
[_shortKitBridge fetchContent:limit completion:^(NSString *json) {
|
|
244
|
+
[_shortKitBridge fetchContent:limit filterJSON:filterJSON completion:^(NSString *json) {
|
|
228
245
|
resolve(json);
|
|
229
246
|
}];
|
|
230
247
|
}
|
|
231
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
|
+
|
|
268
|
+
// MARK: - Storyboard / Seek Thumbnails
|
|
269
|
+
|
|
270
|
+
RCT_EXPORT_METHOD(prefetchStoryboard:(NSString *)playbackId) {
|
|
271
|
+
[_shortKitBridge prefetchStoryboard:playbackId];
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
RCT_EXPORT_METHOD(getStoryboardData:(NSString *)playbackId
|
|
275
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
276
|
+
reject:(RCTPromiseRejectBlock)reject) {
|
|
277
|
+
[_shortKitBridge getStoryboardData:playbackId completion:^(NSString *json) {
|
|
278
|
+
if (json) {
|
|
279
|
+
resolve(json);
|
|
280
|
+
} else {
|
|
281
|
+
resolve([NSNull null]);
|
|
282
|
+
}
|
|
283
|
+
}];
|
|
284
|
+
}
|
|
285
|
+
|
|
232
286
|
// MARK: - New Architecture (TurboModule)
|
|
233
287
|
|
|
234
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
|
@@ -337,6 +337,7 @@ SWIFT_CLASS("_TtC11ShortKitSDK26ShortKitFeedViewController")
|
|
|
337
337
|
@interface ShortKitFeedViewController : UIViewController
|
|
338
338
|
- (nullable instancetype)initWithCoder:(NSCoder * _Nonnull)coder SWIFT_UNAVAILABLE;
|
|
339
339
|
- (void)viewDidLoad;
|
|
340
|
+
- (void)viewDidLayoutSubviews;
|
|
340
341
|
- (void)viewWillAppear:(BOOL)animated;
|
|
341
342
|
- (void)viewDidAppear:(BOOL)animated;
|
|
342
343
|
- (void)viewWillDisappear:(BOOL)animated;
|
|
@@ -364,7 +365,7 @@ SWIFT_CLASS("_TtC11ShortKitSDK26ShortKitFeedViewController")
|
|
|
364
365
|
|
|
365
366
|
/// Public single-video player view controller.
|
|
366
367
|
/// Starts in thumbnail-only mode and borrows an AVPlayer from the shared
|
|
367
|
-
/// <code>
|
|
368
|
+
/// <code>PlayerPool</code> tile slots when activated. Call <code>activate()</code> when the tile becomes
|
|
368
369
|
/// visible and <code>deactivate()</code> when it scrolls offscreen.
|
|
369
370
|
SWIFT_CLASS("_TtC11ShortKitSDK28ShortKitPlayerViewController")
|
|
370
371
|
@interface ShortKitPlayerViewController : UIViewController
|