@shortkitsdk/react-native 0.2.32 → 0.2.34
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 +34 -0
- package/ios/ShortKitPlayerNativeView.swift +31 -0
- package/ios/ShortKitPlayerNativeViewManager.mm +1 -0
- 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 +1919 -106
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +49 -5
- 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 +49 -5
- 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 +1919 -106
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +49 -5
- 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 +49 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +1919 -106
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +49 -5
- 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 +49 -5
- 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 +159 -6
- package/ios/ShortKitWidgetNativeViewManager.mm +2 -0
- package/package.json +1 -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/ShortKitPlayerViewNativeComponent.ts +7 -0
- package/src/specs/ShortKitWidgetViewNativeComponent.ts +15 -0
- package/src/types.ts +95 -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:
|
|
@@ -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 {
|
|
@@ -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.34</string>
|
|
15
15
|
<key>CFBundleShortVersionString</key>
|
|
16
|
-
<string>0.2.
|
|
16
|
+
<string>0.2.34</string>
|
|
17
17
|
<key>MinimumOSVersion</key>
|
|
18
18
|
<string>16.0</string>
|
|
19
19
|
</dict>
|