@shortkitsdk/react-native 0.2.31 → 0.2.33
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/android/libs/shortkit-release.aar +0 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +26 -5
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +19 -5
- package/ios/FeedMaskHostView.swift +190 -0
- package/ios/ShortKitBridge.swift +111 -3
- package/ios/ShortKitModule.mm +5 -1
- package/ios/ShortKitPlayerNativeView.swift +31 -0
- package/ios/ShortKitPlayerNativeViewManager.mm +1 -0
- package/ios/ShortKitSDK.xcframework/Info.plist +5 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3060 -259
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +84 -7
- 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 +84 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/_CodeSignature/CodeResources +9 -9
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Info.plist +2 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3060 -259
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +84 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +84 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +3060 -259
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +84 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.swiftinterface +84 -7
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/_CodeSignature/CodeResources +17 -17
- package/ios/ShortKitWidgetNativeView.swift +153 -6
- package/ios/ShortKitWidgetNativeViewManager.mm +2 -0
- package/package.json +1 -1
- package/src/ShortKitCommands.ts +12 -2
- package/src/ShortKitContext.ts +7 -1
- package/src/ShortKitFeedMaskSurface.tsx +132 -0
- package/src/ShortKitPlayer.tsx +15 -2
- package/src/ShortKitWidget.tsx +16 -2
- package/src/index.ts +8 -1
- package/src/serialization.ts +15 -0
- package/src/specs/NativeShortKitModule.ts +2 -1
- package/src/specs/ShortKitPlayerViewNativeComponent.ts +7 -0
- package/src/specs/ShortKitWidgetViewNativeComponent.ts +15 -0
- package/src/types.ts +99 -0
|
Binary file
|
|
@@ -59,6 +59,8 @@ class ShortKitBridge(
|
|
|
59
59
|
clientAppVersion: String?,
|
|
60
60
|
customDimensionsJSON: String?,
|
|
61
61
|
debugPanel: Boolean,
|
|
62
|
+
serverTracingEnabled: Boolean,
|
|
63
|
+
consoleTracingEnabled: Boolean,
|
|
62
64
|
private val emitEvent: (String, WritableMap) -> Unit
|
|
63
65
|
) {
|
|
64
66
|
|
|
@@ -516,7 +518,9 @@ class ShortKitBridge(
|
|
|
516
518
|
clientAppName = clientAppName,
|
|
517
519
|
clientAppVersion = clientAppVersion,
|
|
518
520
|
customDimensions = dims,
|
|
519
|
-
debugPanelEnabled = debugPanel
|
|
521
|
+
debugPanelEnabled = debugPanel,
|
|
522
|
+
serverTracingEnabled = serverTracingEnabled,
|
|
523
|
+
consoleTracingEnabled = consoleTracingEnabled
|
|
520
524
|
)
|
|
521
525
|
this.shortKit = sdk
|
|
522
526
|
shared = this
|
|
@@ -778,22 +782,22 @@ class ShortKitBridge(
|
|
|
778
782
|
// Custom feed operations
|
|
779
783
|
// ------------------------------------------------------------------
|
|
780
784
|
|
|
781
|
-
fun setFeedItems(feedId: String, itemsJSON: String) {
|
|
785
|
+
fun setFeedItems(feedId: String, itemsJSON: String, startAtId: String?) {
|
|
782
786
|
val fragment = feedFragment(feedId)
|
|
783
787
|
if (fragment != null) {
|
|
784
788
|
val parsed = parseFeedInputs(itemsJSON) ?: return
|
|
785
789
|
Handler(Looper.getMainLooper()).post {
|
|
786
|
-
fragment.setFeedItems(parsed)
|
|
790
|
+
fragment.setFeedItems(parsed, startAtId)
|
|
787
791
|
}
|
|
788
792
|
} else {
|
|
789
793
|
synchronized(pendingOpsLock) {
|
|
790
794
|
pendingOps.getOrPut(feedId) { mutableListOf() }.add { frag ->
|
|
791
795
|
val parsed = parseFeedInputs(itemsJSON) ?: return@add
|
|
792
796
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
|
793
|
-
frag.setFeedItems(parsed)
|
|
797
|
+
frag.setFeedItems(parsed, startAtId)
|
|
794
798
|
} else {
|
|
795
799
|
Handler(Looper.getMainLooper()).post {
|
|
796
|
-
frag.setFeedItems(parsed)
|
|
800
|
+
frag.setFeedItems(parsed, startAtId)
|
|
797
801
|
}
|
|
798
802
|
}
|
|
799
803
|
}
|
|
@@ -801,6 +805,23 @@ class ShortKitBridge(
|
|
|
801
805
|
}
|
|
802
806
|
}
|
|
803
807
|
|
|
808
|
+
fun scrollFeedToItem(feedId: String, id: String, animated: Boolean) {
|
|
809
|
+
val fragment = feedFragment(feedId)
|
|
810
|
+
if (fragment != null) {
|
|
811
|
+
Handler(Looper.getMainLooper()).post {
|
|
812
|
+
fragment.scrollToItem(id, animated)
|
|
813
|
+
}
|
|
814
|
+
} else {
|
|
815
|
+
synchronized(pendingOpsLock) {
|
|
816
|
+
pendingOps.getOrPut(feedId) { mutableListOf() }.add { frag ->
|
|
817
|
+
Handler(Looper.getMainLooper()).post {
|
|
818
|
+
frag.scrollToItem(id, animated)
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
804
825
|
fun appendFeedItems(feedId: String, itemsJSON: String) {
|
|
805
826
|
val fragment = feedFragment(feedId)
|
|
806
827
|
if (fragment != null) {
|
|
@@ -72,7 +72,9 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
72
72
|
clientAppName: String?,
|
|
73
73
|
clientAppVersion: String?,
|
|
74
74
|
customDimensions: String?,
|
|
75
|
-
debugPanel: Boolean
|
|
75
|
+
debugPanel: Boolean?,
|
|
76
|
+
serverTracingEnabled: Boolean?,
|
|
77
|
+
consoleTracingEnabled: Boolean?
|
|
76
78
|
) {
|
|
77
79
|
bridge?.teardown()
|
|
78
80
|
bridge = null
|
|
@@ -88,6 +90,8 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
88
90
|
clientAppVersion = clientAppVersion,
|
|
89
91
|
customDimensionsJSON = customDimensions,
|
|
90
92
|
debugPanel = debugPanel ?: false,
|
|
93
|
+
serverTracingEnabled = serverTracingEnabled ?: false,
|
|
94
|
+
consoleTracingEnabled = consoleTracingEnabled ?: false,
|
|
91
95
|
emitEvent = { name, body -> sendEvent(name, body) }
|
|
92
96
|
)
|
|
93
97
|
|
|
@@ -191,12 +195,22 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
191
195
|
// -----------------------------------------------------------------
|
|
192
196
|
|
|
193
197
|
@ReactMethod
|
|
194
|
-
override fun setFeedItems(feedId: String, items: String) {
|
|
198
|
+
override fun setFeedItems(feedId: String, items: String, startAtId: String?) {
|
|
195
199
|
val b = bridge
|
|
196
200
|
if (b != null) {
|
|
197
|
-
b.setFeedItems(feedId, items)
|
|
201
|
+
b.setFeedItems(feedId, items, startAtId)
|
|
198
202
|
} else {
|
|
199
|
-
bufferOp { bridge?.setFeedItems(feedId, items) }
|
|
203
|
+
bufferOp { bridge?.setFeedItems(feedId, items, startAtId) }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@ReactMethod
|
|
208
|
+
override fun scrollFeedToItem(feedId: String, id: String, animated: Boolean) {
|
|
209
|
+
val b = bridge
|
|
210
|
+
if (b != null) {
|
|
211
|
+
b.scrollFeedToItem(feedId, id, animated)
|
|
212
|
+
} else {
|
|
213
|
+
bufferOp { bridge?.scrollFeedToItem(feedId, id, animated) }
|
|
200
214
|
}
|
|
201
215
|
}
|
|
202
216
|
|
|
@@ -274,7 +288,7 @@ class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
|
274
288
|
// -----------------------------------------------------------------
|
|
275
289
|
|
|
276
290
|
@ReactMethod
|
|
277
|
-
override fun downloadVideo(itemId: String, mode: String, promise: Promise) {
|
|
291
|
+
override fun downloadVideo(itemId: String, mode: String, overlayMode: String, promise: Promise) {
|
|
278
292
|
// TODO: PR 2 — wire to ShortKit.activeInstance.get().downloadVideo
|
|
279
293
|
promise.reject("UNSUPPORTED", "downloadVideo not yet implemented on Android")
|
|
280
294
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import ShortKitSDK
|
|
3
|
+
|
|
4
|
+
/// `UIView` that conforms to `FeedMask` and bridges to a host-supplied
|
|
5
|
+
/// React component. Acts as the integration point between the SDK's
|
|
6
|
+
/// UIKit `FeedMask` API and the RN component tree.
|
|
7
|
+
///
|
|
8
|
+
/// **Architecture: native feed region + RN-rendered chrome container.**
|
|
9
|
+
/// Mirrors the layout of `swift_sdk/.../DailyMailFeedMask` exactly:
|
|
10
|
+
///
|
|
11
|
+
/// ┌──────────────────────────────┐
|
|
12
|
+
/// │ │
|
|
13
|
+
/// │ `feedRegion` (native) │ ← SDK embeds the feed VC here.
|
|
14
|
+
/// │ │
|
|
15
|
+
/// ├──────────────────────────────┤
|
|
16
|
+
/// │ `chromeContainer` (RN) │ ← Host's JS mask renders here.
|
|
17
|
+
/// └──────────────────────────────┘
|
|
18
|
+
///
|
|
19
|
+
/// `feedRegion` is a plain `UIView` instance property pinned to the
|
|
20
|
+
/// top of the mask. The SDK's `FeedMaskContainerViewController.embedFeed`
|
|
21
|
+
/// adds the feed VC's view to it via AutoLayout, identical to the
|
|
22
|
+
/// native DM demo. Nothing covers the feed, so SDK touch routing,
|
|
23
|
+
/// pull-to-dismiss, and the seamless modal-zoom transition all work
|
|
24
|
+
/// out of the box.
|
|
25
|
+
///
|
|
26
|
+
/// `chromeContainer` is the lower sibling. We mount an
|
|
27
|
+
/// `SKFabricSurfaceWrapper`-backed RN root inside it rendering the JS
|
|
28
|
+
/// surface module `ShortKitFeedMask_<name>`. The host's component
|
|
29
|
+
/// renders into chromeContainer's bounds — pure JS, no native bridge
|
|
30
|
+
/// needed beyond this file.
|
|
31
|
+
@objc public class FeedMaskHostView: UIView, @unchecked Sendable, FeedMask {
|
|
32
|
+
|
|
33
|
+
// MARK: - FeedMask conformance — public
|
|
34
|
+
|
|
35
|
+
/// Native UIView the SDK embeds the feed VC into. Pinned to the
|
|
36
|
+
/// top of the mask above `chromeContainer`.
|
|
37
|
+
public let feedRegion = UIView()
|
|
38
|
+
|
|
39
|
+
public func attach(player: ShortKitPlayer) {
|
|
40
|
+
self.attachedPlayer = player
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public func configure(with item: ContentItem) {
|
|
44
|
+
pendingItem = item
|
|
45
|
+
guard let surf = surface, let json = Self.serializeItem(item) else { return }
|
|
46
|
+
surf.setProperties(["item": json])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public func activatePlayback() {
|
|
50
|
+
// v1: stub. Reserved for a future "playback active" event prop
|
|
51
|
+
// pushed to the JS surface if a host wires something up to it.
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// MARK: - Configuration (set by the bridge factory closure)
|
|
55
|
+
|
|
56
|
+
/// Surface presenter for instantiating `RCTFabricSurface`.
|
|
57
|
+
var surfacePresenter: AnyObject?
|
|
58
|
+
|
|
59
|
+
/// JS module name for the Fabric surface, e.g.
|
|
60
|
+
/// `ShortKitFeedMask_viewStory`. The JS side's
|
|
61
|
+
/// `ShortKitFeedMaskSurface` registers under this name on
|
|
62
|
+
/// `<ShortKitPlayer>`/`<ShortKitWidget>` mount.
|
|
63
|
+
var feedMaskModuleName: String = "ShortKitFeedMask"
|
|
64
|
+
|
|
65
|
+
/// Height of the chrome container in points. v1: fixed default of
|
|
66
|
+
/// 96pt (matches the DM "View Story" pill demo). Future: expose as
|
|
67
|
+
/// part of the JS `feedMask` config so hosts can override per-mask.
|
|
68
|
+
var chromeHeight: CGFloat = 96
|
|
69
|
+
|
|
70
|
+
// MARK: - Internal state
|
|
71
|
+
|
|
72
|
+
private var surface: SKFabricSurfaceWrapper?
|
|
73
|
+
private var surfaceView: UIView?
|
|
74
|
+
private var pendingItem: ContentItem?
|
|
75
|
+
private weak var attachedPlayer: ShortKitPlayer?
|
|
76
|
+
|
|
77
|
+
/// Native chrome container — sibling of `feedRegion`, pinned to
|
|
78
|
+
/// the bottom of the mask. The RN root mounts INSIDE this
|
|
79
|
+
/// container, full-bleed within the chrome strip only.
|
|
80
|
+
private let chromeContainer: UIView = {
|
|
81
|
+
let v = UIView()
|
|
82
|
+
v.translatesAutoresizingMaskIntoConstraints = false
|
|
83
|
+
v.backgroundColor = .black
|
|
84
|
+
return v
|
|
85
|
+
}()
|
|
86
|
+
|
|
87
|
+
// MARK: - Init
|
|
88
|
+
|
|
89
|
+
public override init(frame: CGRect) {
|
|
90
|
+
super.init(frame: frame)
|
|
91
|
+
backgroundColor = .black
|
|
92
|
+
|
|
93
|
+
feedRegion.translatesAutoresizingMaskIntoConstraints = false
|
|
94
|
+
addSubview(feedRegion)
|
|
95
|
+
addSubview(chromeContainer)
|
|
96
|
+
|
|
97
|
+
NSLayoutConstraint.activate([
|
|
98
|
+
feedRegion.topAnchor.constraint(equalTo: topAnchor),
|
|
99
|
+
feedRegion.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
100
|
+
feedRegion.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
101
|
+
feedRegion.bottomAnchor.constraint(equalTo: chromeContainer.topAnchor),
|
|
102
|
+
|
|
103
|
+
chromeContainer.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
104
|
+
chromeContainer.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
105
|
+
chromeContainer.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
106
|
+
chromeContainer.heightAnchor.constraint(equalToConstant: chromeHeight),
|
|
107
|
+
])
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@available(*, unavailable)
|
|
111
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }
|
|
112
|
+
|
|
113
|
+
deinit {
|
|
114
|
+
surface?.stop()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// MARK: - Lifecycle
|
|
118
|
+
|
|
119
|
+
public override func didMoveToWindow() {
|
|
120
|
+
super.didMoveToWindow()
|
|
121
|
+
if window != nil, surface == nil {
|
|
122
|
+
installSurface()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private func installSurface() {
|
|
127
|
+
guard let presenter = surfacePresenter else { return }
|
|
128
|
+
|
|
129
|
+
let initialProps: [String: Any]
|
|
130
|
+
if let item = pendingItem, let json = Self.serializeItem(item) {
|
|
131
|
+
initialProps = ["item": json]
|
|
132
|
+
} else {
|
|
133
|
+
initialProps = [:]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
guard let surf = SKFabricSurfaceWrapper(
|
|
137
|
+
presenter: presenter,
|
|
138
|
+
moduleName: feedMaskModuleName,
|
|
139
|
+
initialProperties: initialProps
|
|
140
|
+
) else { return }
|
|
141
|
+
surf.start()
|
|
142
|
+
|
|
143
|
+
let view = surf.view
|
|
144
|
+
view.translatesAutoresizingMaskIntoConstraints = false
|
|
145
|
+
chromeContainer.addSubview(view)
|
|
146
|
+
NSLayoutConstraint.activate([
|
|
147
|
+
view.topAnchor.constraint(equalTo: chromeContainer.topAnchor),
|
|
148
|
+
view.leadingAnchor.constraint(equalTo: chromeContainer.leadingAnchor),
|
|
149
|
+
view.trailingAnchor.constraint(equalTo: chromeContainer.trailingAnchor),
|
|
150
|
+
view.bottomAnchor.constraint(equalTo: chromeContainer.bottomAnchor),
|
|
151
|
+
])
|
|
152
|
+
surfaceView = view
|
|
153
|
+
surface = surf
|
|
154
|
+
|
|
155
|
+
let size = CGSize(width: bounds.width, height: chromeHeight)
|
|
156
|
+
if size.width > 0, size.height > 0 {
|
|
157
|
+
surf.setMinimumSize(size)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public override func layoutSubviews() {
|
|
162
|
+
super.layoutSubviews()
|
|
163
|
+
let size = CGSize(width: bounds.width, height: chromeHeight)
|
|
164
|
+
guard size.width > 0, size.height > 0 else { return }
|
|
165
|
+
surface?.setMinimumSize(size)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// MARK: - Helpers
|
|
169
|
+
|
|
170
|
+
/// JSON-serialize a `ContentItem` for the JS surface's `item` prop.
|
|
171
|
+
/// Mirrors the JS-side `deserializeContentItem` shape.
|
|
172
|
+
private static func serializeItem(_ item: ContentItem) -> String? {
|
|
173
|
+
var dict: [String: Any] = [
|
|
174
|
+
"id": item.id,
|
|
175
|
+
"title": item.title,
|
|
176
|
+
"duration": item.duration,
|
|
177
|
+
"streamingUrl": item.streamingUrl,
|
|
178
|
+
"thumbnailUrl": item.thumbnailUrl,
|
|
179
|
+
"captionTracks": [],
|
|
180
|
+
]
|
|
181
|
+
if let pid = item.playbackId { dict["playbackId"] = pid }
|
|
182
|
+
if let desc = item.description { dict["description"] = desc }
|
|
183
|
+
if let author = item.author { dict["author"] = author }
|
|
184
|
+
if let articleUrl = item.articleUrl { dict["articleUrl"] = articleUrl }
|
|
185
|
+
if let fallbackUrl = item.fallbackUrl { dict["fallbackUrl"] = fallbackUrl }
|
|
186
|
+
guard let data = try? JSONSerialization.data(withJSONObject: dict),
|
|
187
|
+
let json = String(data: data, encoding: .utf8) else { return nil }
|
|
188
|
+
return json
|
|
189
|
+
}
|
|
190
|
+
}
|
package/ios/ShortKitBridge.swift
CHANGED
|
@@ -903,6 +903,40 @@ import ShortKitSDK
|
|
|
903
903
|
)
|
|
904
904
|
}
|
|
905
905
|
|
|
906
|
+
/// Parse a double-stringified feed-mask JSON value into a
|
|
907
|
+
/// `FeedMaskMode`. The JS side serializes `config.feedMask` as
|
|
908
|
+
/// either `"\"none\""` or `{"type":"custom","name":"<name>"}` —
|
|
909
|
+
/// both shapes get a JSON-encoded string layer because they're
|
|
910
|
+
/// embedded inside the larger config string.
|
|
911
|
+
///
|
|
912
|
+
/// On `.custom`, returns a factory closure that constructs a fresh
|
|
913
|
+
/// `FeedMaskHostView` per invocation. The host then wires up its
|
|
914
|
+
/// surface presenter + module name from the shared `ShortKitBridge`.
|
|
915
|
+
/// Multiple expansions create independent mask instances —
|
|
916
|
+
/// matches `ReactOverlayHost`'s closure pattern.
|
|
917
|
+
static func parseFeedMask(_ json: String?) -> FeedMaskMode {
|
|
918
|
+
guard let json,
|
|
919
|
+
let data = json.data(using: .utf8),
|
|
920
|
+
let parsed = try? JSONSerialization.jsonObject(with: data) else {
|
|
921
|
+
return .none
|
|
922
|
+
}
|
|
923
|
+
if let str = parsed as? String, str == "none" {
|
|
924
|
+
return .none
|
|
925
|
+
}
|
|
926
|
+
if let obj = parsed as? [String: Any],
|
|
927
|
+
let type = obj["type"] as? String,
|
|
928
|
+
type == "custom" {
|
|
929
|
+
let name = obj["name"] as? String ?? "Default"
|
|
930
|
+
return .custom { @Sendable @MainActor in
|
|
931
|
+
let host = FeedMaskHostView()
|
|
932
|
+
host.surfacePresenter = ShortKitBridge.shared?.surfacePresenter
|
|
933
|
+
host.feedMaskModuleName = "ShortKitFeedMask_\(name)"
|
|
934
|
+
return host
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return .none
|
|
938
|
+
}
|
|
939
|
+
|
|
906
940
|
/// Parse a double-stringified overlay JSON into a `VideoOverlayMode`.
|
|
907
941
|
///
|
|
908
942
|
/// Examples:
|
|
@@ -1164,13 +1198,31 @@ extension ShortKitBridge: ShortKitDownloadDelegate {
|
|
|
1164
1198
|
emitOnMain("onDownloadStarted", body: ["itemId": item.id])
|
|
1165
1199
|
}
|
|
1166
1200
|
|
|
1201
|
+
// Legacy phase-less progress overload. Intentionally a no-op on the
|
|
1202
|
+
// bridge: `DownloadManager.emitProgress` invokes both this method and
|
|
1203
|
+
// the phase-aware overload below for backwards compatibility with
|
|
1204
|
+
// native Swift delegates that only implement the legacy method, but
|
|
1205
|
+
// RN consumers want a single phase-aware emit per progress tick.
|
|
1206
|
+
// Suppressing the legacy emit here avoids double-firing
|
|
1207
|
+
// `onDownloadProgress` (and prevents the `phase` field from being
|
|
1208
|
+
// dropped when this overload would otherwise win the throttle gate).
|
|
1167
1209
|
public func shortKit(_ shortKit: ShortKit, didUpdateDownloadProgress item: ContentItem, progress: Double) {
|
|
1210
|
+
// No-op — see comment above.
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
public func shortKit(
|
|
1214
|
+
_ shortKit: ShortKit,
|
|
1215
|
+
didUpdateDownloadProgress item: ContentItem,
|
|
1216
|
+
progress: Double,
|
|
1217
|
+
phase: DownloadPhase
|
|
1218
|
+
) {
|
|
1168
1219
|
let now = CACurrentMediaTime()
|
|
1169
1220
|
guard now - lastDownloadProgressEmitTime >= 0.1 else { return }
|
|
1170
1221
|
lastDownloadProgressEmitTime = now
|
|
1171
1222
|
emitOnMain("onDownloadProgress", body: [
|
|
1172
1223
|
"itemId": item.id,
|
|
1173
1224
|
"progress": progress,
|
|
1225
|
+
"phase": phase.rawValue,
|
|
1174
1226
|
])
|
|
1175
1227
|
}
|
|
1176
1228
|
|
|
@@ -1194,6 +1246,10 @@ extension ShortKitBridge: ShortKitDownloadDelegate {
|
|
|
1194
1246
|
message = "HTTP error: \(statusCode)"
|
|
1195
1247
|
case .cancelled:
|
|
1196
1248
|
message = "Download cancelled"
|
|
1249
|
+
case .exportFailed(let underlying):
|
|
1250
|
+
message = "Overlay export failed: \(underlying.localizedDescription)"
|
|
1251
|
+
case .overlayNotVisibleForSnapshot:
|
|
1252
|
+
message = "Overlay not visible for snapshot"
|
|
1197
1253
|
}
|
|
1198
1254
|
emitOnMain("onDownloadFailed", body: [
|
|
1199
1255
|
"itemId": item.id,
|
|
@@ -1205,9 +1261,16 @@ extension ShortKitBridge: ShortKitDownloadDelegate {
|
|
|
1205
1261
|
// MARK: - Downloads
|
|
1206
1262
|
|
|
1207
1263
|
extension ShortKitBridge {
|
|
1208
|
-
@objc public func downloadVideo(
|
|
1264
|
+
@objc public func downloadVideo(
|
|
1265
|
+
_ itemId: String,
|
|
1266
|
+
mode: String,
|
|
1267
|
+
overlayMode: String,
|
|
1268
|
+
completion: @escaping (String?, NSError?) -> Void
|
|
1269
|
+
) {
|
|
1270
|
+
print("[SKComposite] Bridge.downloadVideo ENTER itemId=\(itemId) mode=\(mode) overlayMode=\(overlayMode)")
|
|
1209
1271
|
DispatchQueue.main.async {
|
|
1210
1272
|
guard let shortKit = self.shortKit else {
|
|
1273
|
+
print("[SKComposite] Bridge.downloadVideo ERROR shortKit==nil")
|
|
1211
1274
|
completion(nil, NSError(domain: "ShortKitBridge", code: 1, userInfo: [
|
|
1212
1275
|
NSLocalizedDescriptionKey: "ShortKit not initialized"
|
|
1213
1276
|
]))
|
|
@@ -1215,25 +1278,70 @@ extension ShortKitBridge {
|
|
|
1215
1278
|
}
|
|
1216
1279
|
|
|
1217
1280
|
guard let item = self.itemCache[itemId] else {
|
|
1281
|
+
print("[SKComposite] Bridge.downloadVideo ERROR itemCache miss — cache has \(self.itemCache.count) items; keys=\(Array(self.itemCache.keys).prefix(5))")
|
|
1218
1282
|
completion(nil, NSError(domain: "ShortKitBridge", code: 2, userInfo: [
|
|
1219
1283
|
NSLocalizedDescriptionKey: "Content item not found: \(itemId)"
|
|
1220
1284
|
]))
|
|
1221
1285
|
return
|
|
1222
1286
|
}
|
|
1287
|
+
print("[SKComposite] Bridge.downloadVideo resolved item id=\(item.id) title=\(item.title) isLive=\(item.isLive) downloadUrl=\(item.downloadUrl ?? "<nil>")")
|
|
1223
1288
|
|
|
1224
1289
|
let downloadMode: DownloadMode = mode == "interruptive" ? .interruptive : .nonInterruptive
|
|
1225
1290
|
|
|
1291
|
+
let resolvedOverlayMode: DownloadOverlayMode
|
|
1292
|
+
switch overlayMode {
|
|
1293
|
+
case "static":
|
|
1294
|
+
print("[SKComposite] Bridge.downloadVideo resolving overlay — feedRegistry has \(self.feedRegistry.count) feeds")
|
|
1295
|
+
guard let overlay = self.findActiveOverlay(for: itemId) else {
|
|
1296
|
+
print("[SKComposite] Bridge.downloadVideo ERROR no visible overlay found for itemId=\(itemId)")
|
|
1297
|
+
completion(nil, ShortKitDownloadError.overlayNotVisibleForSnapshot as NSError)
|
|
1298
|
+
return
|
|
1299
|
+
}
|
|
1300
|
+
print("[SKComposite] Bridge.downloadVideo found overlay class=\(type(of: overlay)) bounds=\(overlay.bounds)")
|
|
1301
|
+
resolvedOverlayMode = .staticSnapshot(source: overlay)
|
|
1302
|
+
case "deterministic":
|
|
1303
|
+
print("[SKComposite] Bridge.downloadVideo deterministic mode requested — Phase 2, not implemented")
|
|
1304
|
+
completion(nil, ShortKitDownloadError.downloadNotAvailable as NSError)
|
|
1305
|
+
return
|
|
1306
|
+
default:
|
|
1307
|
+
resolvedOverlayMode = .none
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1226
1310
|
Task {
|
|
1227
1311
|
do {
|
|
1228
|
-
|
|
1312
|
+
print("[SKComposite] Bridge.downloadVideo calling shortKit.downloadVideo(mode=\(downloadMode), overlayMode=\(overlayMode))")
|
|
1313
|
+
let fileURL = try await shortKit.downloadVideo(item, mode: downloadMode, overlayMode: resolvedOverlayMode)
|
|
1314
|
+
print("[SKComposite] Bridge.downloadVideo SUCCESS fileURL=\(fileURL.absoluteString)")
|
|
1229
1315
|
completion(fileURL.absoluteString, nil)
|
|
1230
1316
|
} catch {
|
|
1231
|
-
|
|
1317
|
+
let ns = error as NSError
|
|
1318
|
+
print("[SKComposite] Bridge.downloadVideo ERROR domain=\(ns.domain) code=\(ns.code) desc=\(ns.localizedDescription) userInfo=\(ns.userInfo)")
|
|
1319
|
+
completion(nil, ns)
|
|
1232
1320
|
}
|
|
1233
1321
|
}
|
|
1234
1322
|
}
|
|
1235
1323
|
}
|
|
1236
1324
|
|
|
1325
|
+
/// Finds the currently-attached overlay for the given item ID by iterating
|
|
1326
|
+
/// all registered feed view controllers. Returns the first match, or nil
|
|
1327
|
+
/// if no visible cell is found for this item in any registered feed.
|
|
1328
|
+
@MainActor
|
|
1329
|
+
private func findActiveOverlay(for itemId: String) -> (UIView & FeedOverlay)? {
|
|
1330
|
+
for (feedId, entry) in feedRegistry {
|
|
1331
|
+
guard let vc = entry.vc else {
|
|
1332
|
+
print("[SKComposite] findActiveOverlay feedId=\(feedId) vc was deallocated")
|
|
1333
|
+
continue
|
|
1334
|
+
}
|
|
1335
|
+
if let overlay = vc.overlayView(forItemId: itemId) {
|
|
1336
|
+
print("[SKComposite] findActiveOverlay matched in feedId=\(feedId)")
|
|
1337
|
+
return overlay
|
|
1338
|
+
} else {
|
|
1339
|
+
print("[SKComposite] findActiveOverlay miss in feedId=\(feedId) — no visible cell for itemId=\(itemId)")
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
return nil
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1237
1345
|
@objc public func cancelDownload() {
|
|
1238
1346
|
shortKit?.cancelDownload()
|
|
1239
1347
|
}
|
package/ios/ShortKitModule.mm
CHANGED
|
@@ -363,12 +363,16 @@ RCT_EXPORT_METHOD(getStoryboardData:(NSString *)playbackId
|
|
|
363
363
|
|
|
364
364
|
RCT_EXPORT_METHOD(downloadVideo:(NSString *)itemId
|
|
365
365
|
mode:(NSString *)mode
|
|
366
|
+
overlayMode:(NSString *)overlayMode
|
|
366
367
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
367
368
|
reject:(RCTPromiseRejectBlock)reject) {
|
|
368
|
-
[
|
|
369
|
+
NSLog(@"[SKComposite] Module.downloadVideo ENTER itemId=%@ mode=%@ overlayMode=%@", itemId, mode, overlayMode);
|
|
370
|
+
[_shortKitBridge downloadVideo:itemId mode:mode overlayMode:overlayMode completion:^(NSString *fileUrl, NSError *error) {
|
|
369
371
|
if (error) {
|
|
372
|
+
NSLog(@"[SKComposite] Module.downloadVideo REJECT domain=%@ code=%ld desc=%@", error.domain, (long)error.code, error.localizedDescription);
|
|
370
373
|
reject(@"DOWNLOAD_ERROR", error.localizedDescription, error);
|
|
371
374
|
} else {
|
|
375
|
+
NSLog(@"[SKComposite] Module.downloadVideo RESOLVE fileUrl=%@", fileUrl);
|
|
372
376
|
resolve(fileUrl);
|
|
373
377
|
}
|
|
374
378
|
}];
|
|
@@ -32,10 +32,22 @@ import ShortKitSDK
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/// JSON-serialized `FeedInput[]` used to seed the expanded feed when
|
|
36
|
+
/// the user taps and `clickAction == .feed`. See `ShortKitPlayerProps.feedItems`
|
|
37
|
+
/// in the JS layer for full semantics. The parsed list is forwarded
|
|
38
|
+
/// to the SDK via `ShortKitPlayerViewController.setFeedItems(_:)`.
|
|
39
|
+
@objc public var feedItems: String? {
|
|
40
|
+
didSet {
|
|
41
|
+
guard feedItems != oldValue else { return }
|
|
42
|
+
applyFeedItems()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
// MARK: - Child VC
|
|
36
47
|
|
|
37
48
|
private var playerVC: ShortKitPlayerViewController?
|
|
38
49
|
private var parsedConfig: PlayerConfig?
|
|
50
|
+
private var parsedFeedItems: [FeedInput] = []
|
|
39
51
|
|
|
40
52
|
// MARK: - Lifecycle
|
|
41
53
|
|
|
@@ -93,6 +105,13 @@ import ShortKitSDK
|
|
|
93
105
|
vc.configure(with: item)
|
|
94
106
|
}
|
|
95
107
|
|
|
108
|
+
// Forward the host-provided expanded-feed seed list, if any.
|
|
109
|
+
// Set before the view loads so it's available the first time
|
|
110
|
+
// `openFeed()` runs — same ordering rationale as `configure(with:)`.
|
|
111
|
+
if !parsedFeedItems.isEmpty {
|
|
112
|
+
vc.setFeedItems(parsedFeedItems)
|
|
113
|
+
}
|
|
114
|
+
|
|
96
115
|
parentVC.addChild(vc)
|
|
97
116
|
vc.view.frame = bounds
|
|
98
117
|
vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
@@ -145,6 +164,18 @@ import ShortKitSDK
|
|
|
145
164
|
playerVC?.configure(with: item)
|
|
146
165
|
}
|
|
147
166
|
|
|
167
|
+
private func applyFeedItems() {
|
|
168
|
+
// Empty / nil JSON ⇒ clear the host seed.
|
|
169
|
+
guard let json = feedItems,
|
|
170
|
+
let items = ShortKitBridge.parseFeedInputs(json) else {
|
|
171
|
+
parsedFeedItems = []
|
|
172
|
+
playerVC?.setFeedItems([])
|
|
173
|
+
return
|
|
174
|
+
}
|
|
175
|
+
parsedFeedItems = items
|
|
176
|
+
playerVC?.setFeedItems(items)
|
|
177
|
+
}
|
|
178
|
+
|
|
148
179
|
private func applyActive() {
|
|
149
180
|
guard let vc = playerVC else { return }
|
|
150
181
|
if active {
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>ShortKitSDK.framework/ShortKitSDK</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>ShortKitSDK.framework</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
+
<string>x86_64</string>
|
|
17
18
|
</array>
|
|
18
19
|
<key>SupportedPlatform</key>
|
|
19
20
|
<string>ios</string>
|
|
21
|
+
<key>SupportedPlatformVariant</key>
|
|
22
|
+
<string>simulator</string>
|
|
20
23
|
</dict>
|
|
21
24
|
<dict>
|
|
22
25
|
<key>BinaryPath</key>
|
|
23
26
|
<string>ShortKitSDK.framework/ShortKitSDK</string>
|
|
24
27
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-
|
|
28
|
+
<string>ios-arm64</string>
|
|
26
29
|
<key>LibraryPath</key>
|
|
27
30
|
<string>ShortKitSDK.framework</string>
|
|
28
31
|
<key>SupportedArchitectures</key>
|
|
29
32
|
<array>
|
|
30
33
|
<string>arm64</string>
|
|
31
|
-
<string>x86_64</string>
|
|
32
34
|
</array>
|
|
33
35
|
<key>SupportedPlatform</key>
|
|
34
36
|
<string>ios</string>
|
|
35
|
-
<key>SupportedPlatformVariant</key>
|
|
36
|
-
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
<key>CFBundlePackageType</key>
|
|
12
12
|
<string>FMWK</string>
|
|
13
13
|
<key>CFBundleVersion</key>
|
|
14
|
-
<string>0.2.
|
|
14
|
+
<string>0.2.33</string>
|
|
15
15
|
<key>CFBundleShortVersionString</key>
|
|
16
|
-
<string>0.2.
|
|
16
|
+
<string>0.2.33</string>
|
|
17
17
|
<key>MinimumOSVersion</key>
|
|
18
18
|
<string>16.0</string>
|
|
19
19
|
</dict>
|