@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
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import UIKit
|
|
2
|
-
import ShortKitSDK
|
|
3
|
-
|
|
4
|
-
/// A UIView that conforms to `CarouselOverlay` and renders carousel images
|
|
5
|
-
/// natively inside the feed cell using a horizontal paging UIScrollView.
|
|
6
|
-
///
|
|
7
|
-
/// This matches the Swift SDK's `DefaultCarouselView` architecture: images
|
|
8
|
-
/// are rendered INSIDE the cell (part of the feed scroll view), so vertical
|
|
9
|
-
/// feed scrolling and horizontal image swiping work naturally through UIKit's
|
|
10
|
-
/// gesture conflict resolution.
|
|
11
|
-
///
|
|
12
|
-
/// The RN side only renders metadata (title, description, page dots, buttons)
|
|
13
|
-
/// on top via the `CarouselOverlayManager`.
|
|
14
|
-
@objc public class ShortKitCarouselOverlayBridge: UIView, @unchecked Sendable, CarouselOverlay {
|
|
15
|
-
|
|
16
|
-
// MARK: - Bridge Reference
|
|
17
|
-
|
|
18
|
-
weak var bridge: ShortKitBridge?
|
|
19
|
-
|
|
20
|
-
// MARK: - CarouselOverlay
|
|
21
|
-
|
|
22
|
-
public var cachedImage: ((String) -> UIImage?)?
|
|
23
|
-
|
|
24
|
-
// MARK: - State
|
|
25
|
-
|
|
26
|
-
private var currentItem: ImageCarouselItem?
|
|
27
|
-
private var loadTasks: [Task<Void, Never>] = []
|
|
28
|
-
private var currentPage: Int = 0
|
|
29
|
-
private var autoScrollTimer: Timer?
|
|
30
|
-
|
|
31
|
-
// MARK: - UI
|
|
32
|
-
|
|
33
|
-
private let scrollView = UIScrollView()
|
|
34
|
-
private let pageControl = UIPageControl()
|
|
35
|
-
private var imageViews: [UIImageView] = []
|
|
36
|
-
|
|
37
|
-
// MARK: - Init
|
|
38
|
-
|
|
39
|
-
override init(frame: CGRect) {
|
|
40
|
-
super.init(frame: frame)
|
|
41
|
-
backgroundColor = .black
|
|
42
|
-
isUserInteractionEnabled = true
|
|
43
|
-
setupScrollView()
|
|
44
|
-
setupPageControl()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
required init?(coder: NSCoder) {
|
|
48
|
-
fatalError("init(coder:) is not supported")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
private func setupScrollView() {
|
|
52
|
-
scrollView.isPagingEnabled = true
|
|
53
|
-
scrollView.showsHorizontalScrollIndicator = false
|
|
54
|
-
scrollView.bounces = false
|
|
55
|
-
scrollView.delegate = self
|
|
56
|
-
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
57
|
-
addSubview(scrollView)
|
|
58
|
-
NSLayoutConstraint.activate([
|
|
59
|
-
scrollView.topAnchor.constraint(equalTo: topAnchor),
|
|
60
|
-
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
61
|
-
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
62
|
-
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
63
|
-
])
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private func setupPageControl() {
|
|
67
|
-
pageControl.isUserInteractionEnabled = false
|
|
68
|
-
pageControl.translatesAutoresizingMaskIntoConstraints = false
|
|
69
|
-
addSubview(pageControl)
|
|
70
|
-
NSLayoutConstraint.activate([
|
|
71
|
-
pageControl.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
72
|
-
pageControl.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -40),
|
|
73
|
-
])
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// MARK: - Layout
|
|
77
|
-
|
|
78
|
-
public override func layoutSubviews() {
|
|
79
|
-
super.layoutSubviews()
|
|
80
|
-
let w = bounds.width
|
|
81
|
-
let h = bounds.height
|
|
82
|
-
guard w > 0, h > 0 else { return }
|
|
83
|
-
scrollView.contentSize = CGSize(width: w * CGFloat(imageViews.count), height: h)
|
|
84
|
-
for (i, iv) in imageViews.enumerated() {
|
|
85
|
-
iv.frame = CGRect(x: w * CGFloat(i), y: 0, width: w, height: h)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// MARK: - CarouselOverlay
|
|
90
|
-
|
|
91
|
-
public func configure(with item: ImageCarouselItem) {
|
|
92
|
-
NSLog("[CarouselBridge] configure id=\(item.id) images=\(item.images.count)")
|
|
93
|
-
currentItem = item
|
|
94
|
-
|
|
95
|
-
// Cancel previous loads & auto-scroll
|
|
96
|
-
cancelLoads()
|
|
97
|
-
autoScrollTimer?.invalidate()
|
|
98
|
-
autoScrollTimer = nil
|
|
99
|
-
|
|
100
|
-
// Remove old image views
|
|
101
|
-
for iv in imageViews { iv.removeFromSuperview() }
|
|
102
|
-
imageViews.removeAll()
|
|
103
|
-
|
|
104
|
-
// Create image views and load images
|
|
105
|
-
for image in item.images {
|
|
106
|
-
let iv = UIImageView()
|
|
107
|
-
iv.contentMode = .scaleAspectFill
|
|
108
|
-
iv.clipsToBounds = true
|
|
109
|
-
iv.backgroundColor = .black
|
|
110
|
-
scrollView.addSubview(iv)
|
|
111
|
-
imageViews.append(iv)
|
|
112
|
-
|
|
113
|
-
if let cached = cachedImage?(image.url) {
|
|
114
|
-
iv.image = cached
|
|
115
|
-
} else {
|
|
116
|
-
let task = Task { [weak iv] in
|
|
117
|
-
guard let url = URL(string: image.url),
|
|
118
|
-
let (data, _) = try? await URLSession.shared.data(from: url),
|
|
119
|
-
!Task.isCancelled,
|
|
120
|
-
let uiImage = UIImage(data: data) else { return }
|
|
121
|
-
await MainActor.run { iv?.image = uiImage }
|
|
122
|
-
}
|
|
123
|
-
loadTasks.append(task)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Reset scroll position and page
|
|
128
|
-
currentPage = 0
|
|
129
|
-
scrollView.contentOffset = .zero
|
|
130
|
-
setNeedsLayout()
|
|
131
|
-
|
|
132
|
-
// Update page control
|
|
133
|
-
pageControl.numberOfPages = item.images.count
|
|
134
|
-
pageControl.currentPage = 0
|
|
135
|
-
pageControl.isHidden = item.images.count <= 1
|
|
136
|
-
|
|
137
|
-
// Start auto-scroll if configured
|
|
138
|
-
let interval = item.autoScrollInterval ?? 0
|
|
139
|
-
if interval > 0 && item.images.count > 1 {
|
|
140
|
-
startAutoScroll(interval: interval)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Emit configure to JS so the metadata overlay updates
|
|
144
|
-
bridge?.emitCarouselOverlayEvent("onCarouselOverlayConfigure", item: item)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
public func resetState() {
|
|
148
|
-
NSLog("[CarouselBridge] resetState")
|
|
149
|
-
cancelLoads()
|
|
150
|
-
autoScrollTimer?.invalidate()
|
|
151
|
-
autoScrollTimer = nil
|
|
152
|
-
for iv in imageViews { iv.removeFromSuperview() }
|
|
153
|
-
imageViews.removeAll()
|
|
154
|
-
scrollView.contentOffset = .zero
|
|
155
|
-
currentPage = 0
|
|
156
|
-
currentItem = nil
|
|
157
|
-
bridge?.emitCarouselOverlayEvent("onCarouselOverlayReset", body: [:])
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public func fadeOutForTransition() {
|
|
161
|
-
// No-op — native images are inside the cell, transitions are handled
|
|
162
|
-
// by the feed's collection view.
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
public func restoreFromTransition() {
|
|
166
|
-
// No-op
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// MARK: - Auto-Scroll
|
|
170
|
-
|
|
171
|
-
private func startAutoScroll(interval: Double) {
|
|
172
|
-
autoScrollTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
|
|
173
|
-
guard let self, !self.imageViews.isEmpty else { return }
|
|
174
|
-
let nextPage = (self.currentPage + 1) % self.imageViews.count
|
|
175
|
-
let offset = CGFloat(nextPage) * self.scrollView.bounds.width
|
|
176
|
-
self.scrollView.setContentOffset(CGPoint(x: offset, y: 0), animated: true)
|
|
177
|
-
self.currentPage = nextPage
|
|
178
|
-
self.pageControl.currentPage = nextPage
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// MARK: - Helpers
|
|
183
|
-
|
|
184
|
-
private func cancelLoads() {
|
|
185
|
-
for task in loadTasks { task.cancel() }
|
|
186
|
-
loadTasks.removeAll()
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// MARK: - UIScrollViewDelegate
|
|
192
|
-
|
|
193
|
-
extension ShortKitCarouselOverlayBridge: UIScrollViewDelegate {
|
|
194
|
-
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
195
|
-
guard scrollView.bounds.width > 0 else { return }
|
|
196
|
-
let page = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
|
|
197
|
-
currentPage = page
|
|
198
|
-
pageControl.currentPage = page
|
|
199
|
-
|
|
200
|
-
// Restart auto-scroll after user interaction
|
|
201
|
-
if let interval = currentItem?.autoScrollInterval, interval > 0, imageViews.count > 1 {
|
|
202
|
-
autoScrollTimer?.invalidate()
|
|
203
|
-
startAutoScroll(interval: interval)
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
|
208
|
-
guard scrollView.bounds.width > 0 else { return }
|
|
209
|
-
let page = Int(round(scrollView.contentOffset.x / scrollView.bounds.width))
|
|
210
|
-
currentPage = page
|
|
211
|
-
pageControl.currentPage = page
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
215
|
-
// Pause auto-scroll during user interaction
|
|
216
|
-
autoScrollTimer?.invalidate()
|
|
217
|
-
autoScrollTimer = nil
|
|
218
|
-
}
|
|
219
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import UIKit
|
|
2
|
-
import ShortKitSDK
|
|
3
|
-
|
|
4
|
-
/// A transparent UIView that conforms to `FeedOverlay` and bridges overlay
|
|
5
|
-
/// lifecycle calls to JS events via `ShortKitBridge`.
|
|
6
|
-
///
|
|
7
|
-
/// The actual overlay UI is rendered by React Native on the JS side through
|
|
8
|
-
/// the `OverlayManager` component. This view simply relays the SDK lifecycle
|
|
9
|
-
/// events so the JS overlay knows when to configure, activate, reset, etc.
|
|
10
|
-
@objc public class ShortKitOverlayBridge: UIView, @unchecked Sendable, FeedOverlay {
|
|
11
|
-
|
|
12
|
-
// MARK: - Bridge Reference
|
|
13
|
-
|
|
14
|
-
/// Weak reference to the bridge, set by the factory closure in `parseFeedConfig`.
|
|
15
|
-
weak var bridge: ShortKitBridge?
|
|
16
|
-
|
|
17
|
-
// MARK: - State
|
|
18
|
-
|
|
19
|
-
/// Stores the last configured ContentItem so we can pass it with lifecycle
|
|
20
|
-
/// events that don't receive the item as a parameter.
|
|
21
|
-
private var currentItem: ContentItem?
|
|
22
|
-
|
|
23
|
-
/// Deferred configure emission — cancelled if `activatePlayback()` fires
|
|
24
|
-
/// on the same run-loop iteration (meaning this was a handleSwipe
|
|
25
|
-
/// re-configure of the active cell, not a prefetch for the next cell).
|
|
26
|
-
private var pendingConfigureWorkItem: DispatchWorkItem?
|
|
27
|
-
|
|
28
|
-
// MARK: - Init
|
|
29
|
-
|
|
30
|
-
override init(frame: CGRect) {
|
|
31
|
-
super.init(frame: frame)
|
|
32
|
-
backgroundColor = .clear
|
|
33
|
-
isUserInteractionEnabled = true
|
|
34
|
-
setupGestures()
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
required init?(coder: NSCoder) {
|
|
38
|
-
fatalError("init(coder:) is not supported")
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// MARK: - Gestures
|
|
42
|
-
|
|
43
|
-
private func setupGestures() {
|
|
44
|
-
let doubleTap = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap(_:)))
|
|
45
|
-
doubleTap.numberOfTapsRequired = 2
|
|
46
|
-
addGestureRecognizer(doubleTap)
|
|
47
|
-
|
|
48
|
-
let singleTap = UITapGestureRecognizer(target: self, action: #selector(handleSingleTap))
|
|
49
|
-
singleTap.require(toFail: doubleTap)
|
|
50
|
-
addGestureRecognizer(singleTap)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@objc private func handleSingleTap() {
|
|
54
|
-
bridge?.emitOverlayEvent("onOverlayTap", body: [:])
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@objc private func handleDoubleTap(_ gesture: UITapGestureRecognizer) {
|
|
58
|
-
let location = gesture.location(in: self)
|
|
59
|
-
bridge?.emitOverlayEvent("onOverlayDoubleTap", body: [
|
|
60
|
-
"x": location.x,
|
|
61
|
-
"y": location.y,
|
|
62
|
-
])
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// MARK: - FeedOverlay
|
|
66
|
-
|
|
67
|
-
public func attach(player: ShortKitPlayer) {
|
|
68
|
-
// No-op on the native side. The JS overlay subscribes to player
|
|
69
|
-
// state via the TurboModule's Combine publishers.
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
public func configure(with item: ContentItem) {
|
|
73
|
-
currentItem = item
|
|
74
|
-
|
|
75
|
-
// Defer the configure event by one run-loop tick. If activatePlayback()
|
|
76
|
-
// fires before then (handleSwipe sequence: configure → reset → activate),
|
|
77
|
-
// the event is cancelled — preventing nextItem from being overwritten
|
|
78
|
-
// with the current cell's data.
|
|
79
|
-
pendingConfigureWorkItem?.cancel()
|
|
80
|
-
let workItem = DispatchWorkItem { [weak self] in
|
|
81
|
-
guard let self, let item = self.currentItem else { return }
|
|
82
|
-
self.bridge?.emitOverlayEvent("onOverlayConfigure", item: item)
|
|
83
|
-
self.pendingConfigureWorkItem = nil
|
|
84
|
-
}
|
|
85
|
-
pendingConfigureWorkItem = workItem
|
|
86
|
-
DispatchQueue.main.async(execute: workItem)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public func resetPlaybackProgress() {
|
|
90
|
-
guard let item = currentItem else { return }
|
|
91
|
-
bridge?.emitOverlayEvent("onOverlayReset", item: item)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public func activatePlayback() {
|
|
95
|
-
// Cancel the pending configure — it was for the current cell (part of
|
|
96
|
-
// handleSwipe), not a prefetch for the next cell.
|
|
97
|
-
pendingConfigureWorkItem?.cancel()
|
|
98
|
-
pendingConfigureWorkItem = nil
|
|
99
|
-
|
|
100
|
-
guard let item = currentItem else { return }
|
|
101
|
-
bridge?.emitOverlayEvent("onOverlayActivate", item: item)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
public func fadeOutForTransition() {
|
|
105
|
-
// No-op: JS overlays handle fading via feedScrollPhase
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
public func restoreFromTransition() {
|
|
109
|
-
// No-op: JS overlays handle fading via feedScrollPhase
|
|
110
|
-
}
|
|
111
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useMemo } from 'react';
|
|
2
|
-
import { View, StyleSheet } from 'react-native';
|
|
3
|
-
import type { CarouselOverlayConfig } from './types';
|
|
4
|
-
import { ShortKitContext } from './ShortKitContext';
|
|
5
|
-
import type { ShortKitContextValue } from './ShortKitContext';
|
|
6
|
-
|
|
7
|
-
interface CarouselOverlayManagerProps {
|
|
8
|
-
carouselOverlay: CarouselOverlayConfig;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Internal component that renders TWO instances of the developer's custom
|
|
13
|
-
* carousel overlay component — one for the current cell and one for the next.
|
|
14
|
-
*
|
|
15
|
-
* Works identically to `OverlayManager` but for image carousel cells.
|
|
16
|
-
* The native side finds these views by their `nativeID` and applies
|
|
17
|
-
* scroll-tracking transforms so each overlay moves with its respective
|
|
18
|
-
* carousel cell during swipe transitions.
|
|
19
|
-
*/
|
|
20
|
-
export function CarouselOverlayManager({ carouselOverlay }: CarouselOverlayManagerProps) {
|
|
21
|
-
if (
|
|
22
|
-
carouselOverlay === 'none' ||
|
|
23
|
-
typeof carouselOverlay === 'string' ||
|
|
24
|
-
carouselOverlay.type !== 'custom' ||
|
|
25
|
-
!('component' in carouselOverlay)
|
|
26
|
-
) {
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const CarouselComponent = carouselOverlay.component;
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<>
|
|
34
|
-
<View style={StyleSheet.absoluteFill} nativeID="carousel-overlay-current" pointerEvents="box-none">
|
|
35
|
-
<CarouselComponent />
|
|
36
|
-
</View>
|
|
37
|
-
<View style={StyleSheet.absoluteFill} nativeID="carousel-overlay-next" pointerEvents="box-none">
|
|
38
|
-
<NextCarouselOverlayProvider>
|
|
39
|
-
<CarouselComponent />
|
|
40
|
-
</NextCarouselOverlayProvider>
|
|
41
|
-
</View>
|
|
42
|
-
</>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Wraps children with a modified ShortKitContext where `currentCarouselItem`
|
|
48
|
-
* is set to the provider's `nextCarouselItem`.
|
|
49
|
-
*/
|
|
50
|
-
function NextCarouselOverlayProvider({ children }: { children: React.ReactNode }) {
|
|
51
|
-
const context = useContext(ShortKitContext);
|
|
52
|
-
|
|
53
|
-
const nextValue: ShortKitContextValue | null = useMemo(() => {
|
|
54
|
-
if (!context) return null;
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
...context,
|
|
58
|
-
currentCarouselItem: context.nextCarouselItem,
|
|
59
|
-
isCarouselActive: context.nextCarouselItem != null,
|
|
60
|
-
};
|
|
61
|
-
}, [context]);
|
|
62
|
-
|
|
63
|
-
if (!nextValue) return <>{children}</>;
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<ShortKitContext.Provider value={nextValue}>
|
|
67
|
-
{children}
|
|
68
|
-
</ShortKitContext.Provider>
|
|
69
|
-
);
|
|
70
|
-
}
|
package/src/OverlayManager.tsx
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useMemo } from 'react';
|
|
2
|
-
import { View, StyleSheet } from 'react-native';
|
|
3
|
-
import type { OverlayConfig, PlayerState } from './types';
|
|
4
|
-
import { ShortKitContext } from './ShortKitContext';
|
|
5
|
-
import type { ShortKitContextValue } from './ShortKitContext';
|
|
6
|
-
|
|
7
|
-
interface OverlayManagerProps {
|
|
8
|
-
overlay: OverlayConfig;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Internal component that renders TWO instances of the developer's custom
|
|
13
|
-
* overlay component — one for the current cell and one for the next cell.
|
|
14
|
-
*
|
|
15
|
-
* On the native side, `ShortKitFeedView` finds these views by their
|
|
16
|
-
* `nativeID` and applies scroll-tracking transforms so each overlay moves
|
|
17
|
-
* with its respective cell during swipe transitions — matching the native
|
|
18
|
-
* iOS/Android SDK behavior where each cell has its own overlay instance.
|
|
19
|
-
*
|
|
20
|
-
* The "current" overlay uses the actual provider context.
|
|
21
|
-
* The "next" overlay uses a context override where `currentItem = nextItem`
|
|
22
|
-
* with initial playback state, so it renders the upcoming video's overlay
|
|
23
|
-
* content before the user scrolls to it.
|
|
24
|
-
*/
|
|
25
|
-
export function OverlayManager({ overlay }: OverlayManagerProps) {
|
|
26
|
-
if (
|
|
27
|
-
overlay === 'none' ||
|
|
28
|
-
typeof overlay === 'string' ||
|
|
29
|
-
overlay.type !== 'custom' ||
|
|
30
|
-
!('component' in overlay)
|
|
31
|
-
) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const OverlayComponent = overlay.component;
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<>
|
|
39
|
-
<View style={StyleSheet.absoluteFill} nativeID="overlay-current" pointerEvents="box-none">
|
|
40
|
-
<OverlayComponent />
|
|
41
|
-
</View>
|
|
42
|
-
<View style={StyleSheet.absoluteFill} nativeID="overlay-next" pointerEvents="box-none">
|
|
43
|
-
<NextOverlayProvider>
|
|
44
|
-
<OverlayComponent />
|
|
45
|
-
</NextOverlayProvider>
|
|
46
|
-
</View>
|
|
47
|
-
</>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Wraps children with a modified ShortKitContext where `currentItem` is
|
|
53
|
-
* set to the provider's `nextItem` (the item configured for the upcoming
|
|
54
|
-
* cell). Other state reflects an "about to play" initial state so the
|
|
55
|
-
* overlay renders correctly before playback starts on that cell.
|
|
56
|
-
*/
|
|
57
|
-
function NextOverlayProvider({ children }: { children: React.ReactNode }) {
|
|
58
|
-
const context = useContext(ShortKitContext);
|
|
59
|
-
|
|
60
|
-
const nextValue: ShortKitContextValue | null = useMemo(() => {
|
|
61
|
-
if (!context) return null;
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
...context,
|
|
65
|
-
currentItem: context.nextItem,
|
|
66
|
-
isActive: context.nextItem != null,
|
|
67
|
-
time: {
|
|
68
|
-
current: 0,
|
|
69
|
-
duration: context.nextItem?.duration ?? 0,
|
|
70
|
-
buffered: 0,
|
|
71
|
-
},
|
|
72
|
-
playerState: 'idle' as PlayerState,
|
|
73
|
-
feedScrollPhase: null,
|
|
74
|
-
activeCue: null,
|
|
75
|
-
lastOverlayTap: 0,
|
|
76
|
-
lastOverlayDoubleTap: null,
|
|
77
|
-
};
|
|
78
|
-
}, [context]);
|
|
79
|
-
|
|
80
|
-
if (!nextValue) return <>{children}</>;
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<ShortKitContext.Provider value={nextValue}>
|
|
84
|
-
{children}
|
|
85
|
-
</ShortKitContext.Provider>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useContext } from 'react';
|
|
2
|
-
import { ShortKitContext } from './ShortKitContext';
|
|
3
|
-
import type { ImageCarouselItem } from './types';
|
|
4
|
-
|
|
5
|
-
export interface ShortKitCarouselState {
|
|
6
|
-
currentCarouselItem: ImageCarouselItem | null;
|
|
7
|
-
isCarouselActive: boolean;
|
|
8
|
-
currentCarouselPage: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Hook to access carousel overlay state from the nearest ShortKitProvider.
|
|
13
|
-
*
|
|
14
|
-
* Use this inside a custom carousel overlay component to get the current
|
|
15
|
-
* `ImageCarouselItem` data (images, title, description, etc.).
|
|
16
|
-
*
|
|
17
|
-
* Must be used within a `<ShortKitProvider>`.
|
|
18
|
-
*/
|
|
19
|
-
export function useShortKitCarousel(): ShortKitCarouselState {
|
|
20
|
-
const context = useContext(ShortKitContext);
|
|
21
|
-
if (!context) {
|
|
22
|
-
throw new Error('useShortKitCarousel must be used within a ShortKitProvider');
|
|
23
|
-
}
|
|
24
|
-
return {
|
|
25
|
-
currentCarouselItem: context.currentCarouselItem,
|
|
26
|
-
isCarouselActive: context.isCarouselActive,
|
|
27
|
-
currentCarouselPage: context.currentCarouselPage,
|
|
28
|
-
};
|
|
29
|
-
}
|