@shortkitsdk/react-native 0.2.35 → 0.2.36
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/build.gradle.kts +8 -0
- package/android/libs/shortkit-release.aar +0 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +94 -46
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +46 -7
- package/android/src/main/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHost.kt +233 -27
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +151 -1
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +135 -6
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +15 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +21 -11
- package/android/src/test/java/com/shortkit/reactnative/ReactCarouselOverlayHostEmitTest.kt +134 -0
- package/android/src/test/java/com/shortkit/reactnative/ReactOverlayHostDragTest.kt +45 -0
- package/android/src/test/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHostDragTest.kt +69 -0
- package/android/src/test/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHostEmitTest.kt +144 -0
- package/android/src/test/java/com/shortkit/reactnative/ShortKitFeedViewActivePropTest.kt +57 -0
- package/ios/ReactOverlayHost.swift +10 -8
- package/ios/ReactVideoCarouselOverlayHost.swift +6 -5
- package/ios/ShortKitBridge.swift +18 -0
- package/ios/ShortKitModule.mm +5 -0
- package/ios/ShortKitPlayerNativeView.swift +36 -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 +932 -84
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +26 -2
- 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 +26 -2
- 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 +932 -84
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +26 -2
- 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 +26 -2
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.abi.json +932 -84
- package/ios/ShortKitSDK.xcframework/ios-arm64_x86_64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/x86_64-apple-ios-simulator.private.swiftinterface +26 -2
- 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 +26 -2
- 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/package.json +1 -1
- package/src/ShortKitCommands.ts +20 -0
- package/src/ShortKitFeed.tsx +21 -0
- package/src/specs/NativeShortKitModule.ts +10 -0
- package/src/types.ts +35 -0
package/android/src/test/java/com/shortkit/reactnative/ReactVideoCarouselOverlayHostEmitTest.kt
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
package com.shortkit.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Bundle
|
|
5
|
+
import android.view.ViewGroup
|
|
6
|
+
import androidx.test.core.app.ApplicationProvider
|
|
7
|
+
import com.facebook.react.bridge.JavaOnlyMap
|
|
8
|
+
import com.facebook.react.bridge.ReadableType
|
|
9
|
+
import com.facebook.react.bridge.WritableMap
|
|
10
|
+
import com.facebook.react.interfaces.TaskInterface
|
|
11
|
+
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
12
|
+
import com.shortkit.sdk.model.ContentItem
|
|
13
|
+
import com.shortkit.sdk.model.VideoCarouselItem
|
|
14
|
+
import org.junit.Assert.assertEquals
|
|
15
|
+
import org.junit.Assert.assertTrue
|
|
16
|
+
import org.junit.Test
|
|
17
|
+
import org.junit.runner.RunWith
|
|
18
|
+
import org.robolectric.RobolectricTestRunner
|
|
19
|
+
import java.util.concurrent.TimeUnit
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Tests SHO-8 emit-on-change gating in ReactVideoCarouselOverlayHost,
|
|
23
|
+
* including the iOS first-mount asymmetry where BOTH setProperties AND
|
|
24
|
+
* emitItemChanged are called on first mount (matches iOS at
|
|
25
|
+
* ReactVideoCarouselOverlayHost.swift:127-138).
|
|
26
|
+
*/
|
|
27
|
+
@RunWith(RobolectricTestRunner::class)
|
|
28
|
+
class ReactVideoCarouselOverlayHostEmitTest {
|
|
29
|
+
|
|
30
|
+
/** No-op TaskInterface returned by stub surface lifecycle methods. */
|
|
31
|
+
private object NoOpTask : TaskInterface<Void> {
|
|
32
|
+
override fun waitForCompletion() = Unit
|
|
33
|
+
override fun waitForCompletion(duration: Long, timeUnit: TimeUnit): Boolean = true
|
|
34
|
+
override fun getResult(): Void? = null
|
|
35
|
+
override fun getError(): Exception? = null
|
|
36
|
+
override fun isCompleted(): Boolean = true
|
|
37
|
+
override fun isCancelled(): Boolean = false
|
|
38
|
+
override fun isFaulted(): Boolean = false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Minimal ReactSurface stub for Robolectric tests. ApplicationProvider does
|
|
43
|
+
* not supply a ReactApplication, so createSurfaceIfNeeded() would normally
|
|
44
|
+
* fail silently with reactHost == null. The createSurface seam lets tests
|
|
45
|
+
* inject this stub so surface != null after applySurfaceProps(), allowing
|
|
46
|
+
* hasPushedInitialProps to flip correctly.
|
|
47
|
+
*
|
|
48
|
+
* isRunning is false so applySurfaceProps() calls start() (no-op here) and
|
|
49
|
+
* the updateInitProps cast to ReactSurfaceImpl is a safe no-op via `as?`.
|
|
50
|
+
*/
|
|
51
|
+
private class StubReactSurface : ReactSurface {
|
|
52
|
+
private val ctx: Context = ApplicationProvider.getApplicationContext()
|
|
53
|
+
override val surfaceID: Int = 0
|
|
54
|
+
override val moduleName: String = "StubSurface"
|
|
55
|
+
override val isRunning: Boolean = false
|
|
56
|
+
override val view: ViewGroup? = null
|
|
57
|
+
override val context: Context get() = ctx
|
|
58
|
+
override fun prerender(): TaskInterface<Void> = NoOpTask
|
|
59
|
+
override fun start(): TaskInterface<Void> = NoOpTask
|
|
60
|
+
override fun stop(): TaskInterface<Void> = NoOpTask
|
|
61
|
+
override fun clear() = Unit
|
|
62
|
+
override fun detach() = Unit
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun makeHost(): ReactVideoCarouselOverlayHost {
|
|
66
|
+
val host = ReactVideoCarouselOverlayHost(ApplicationProvider.getApplicationContext())
|
|
67
|
+
host.createMap = { JavaOnlyMap() }
|
|
68
|
+
// Inject a stub surface so hasPushedInitialProps can flip in tests.
|
|
69
|
+
// Same pattern as Task 1's ReactCarouselOverlayHost — see commit 6e5e1dce.
|
|
70
|
+
host.createSurface = { _, _ -> StubReactSurface() }
|
|
71
|
+
return host
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private fun video(id: String) = ContentItem(
|
|
75
|
+
id = id,
|
|
76
|
+
title = "t",
|
|
77
|
+
duration = 5.0,
|
|
78
|
+
streamingUrl = "https://example.com/$id.m3u8",
|
|
79
|
+
thumbnailUrl = "https://example.com/$id.jpg",
|
|
80
|
+
captionTracks = emptyList(),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
private fun item(id: String, videoCount: Int = 1) = VideoCarouselItem(
|
|
84
|
+
id = id,
|
|
85
|
+
videos = (0 until videoCount).map { video("$id-$it") },
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@Test
|
|
89
|
+
fun firstConfigureBothPushesAndEmits() {
|
|
90
|
+
val host = makeHost()
|
|
91
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
92
|
+
host.emitEvent = { name, params -> emitted.add(name to params) }
|
|
93
|
+
|
|
94
|
+
host.configure(item("A"))
|
|
95
|
+
|
|
96
|
+
assertEquals("A", host.currentCarouselItem?.id)
|
|
97
|
+
assertTrue(host.hasPushedInitialProps)
|
|
98
|
+
assertEquals(1, emitted.size)
|
|
99
|
+
assertEquals("onVideoCarouselItemChanged", emitted[0].first)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Test
|
|
103
|
+
fun sameItemConfigureIsNoOpAfterFirstMount() {
|
|
104
|
+
val host = makeHost()
|
|
105
|
+
host.configure(item("A"))
|
|
106
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
107
|
+
host.emitEvent = { name, params -> emitted.add(name to params) }
|
|
108
|
+
|
|
109
|
+
host.configure(item("A"))
|
|
110
|
+
|
|
111
|
+
assertTrue("same item must not emit", emitted.isEmpty())
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@Test
|
|
115
|
+
fun differentItemEmitsOnly() {
|
|
116
|
+
val host = makeHost()
|
|
117
|
+
host.configure(item("A"))
|
|
118
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
119
|
+
host.emitEvent = { name, params -> emitted.add(name to params) }
|
|
120
|
+
|
|
121
|
+
host.configure(item("B"))
|
|
122
|
+
|
|
123
|
+
assertEquals("B", host.currentCarouselItem?.id)
|
|
124
|
+
assertEquals(1, emitted.size)
|
|
125
|
+
assertEquals("onVideoCarouselItemChanged", emitted[0].first)
|
|
126
|
+
val params = emitted[0].second
|
|
127
|
+
assertEquals(host.surfaceId, params.getString("surfaceId"))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
fun emptyVideosUsesNullSentinelForActiveVideo() {
|
|
132
|
+
val host = makeHost()
|
|
133
|
+
host.configure(item("A", videoCount = 0))
|
|
134
|
+
val emitted = mutableListOf<Pair<String, WritableMap>>()
|
|
135
|
+
host.emitEvent = { name, params -> emitted.add(name to params) }
|
|
136
|
+
|
|
137
|
+
host.configure(item("B", videoCount = 0))
|
|
138
|
+
|
|
139
|
+
// Per spec: empty videos must use putNull, NOT key absent.
|
|
140
|
+
// iOS uses NSNull at ReactVideoCarouselOverlayHost.swift:179.
|
|
141
|
+
val params = emitted[0].second
|
|
142
|
+
assertEquals(ReadableType.Null, params.getType("activeVideo"))
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package com.shortkit.reactnative
|
|
2
|
+
|
|
3
|
+
import androidx.test.core.app.ApplicationProvider
|
|
4
|
+
import org.junit.Assert.assertFalse
|
|
5
|
+
import org.junit.Assert.assertTrue
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
import org.junit.runner.RunWith
|
|
8
|
+
import org.robolectric.RobolectricTestRunner
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tests the `active` prop state machine on ShortKitFeedView.
|
|
12
|
+
*
|
|
13
|
+
* Covers:
|
|
14
|
+
* - Default state (active=true, activePropSet=false)
|
|
15
|
+
* - First-mount race precondition (active=false set before embed)
|
|
16
|
+
* - activePropSet flips on every call
|
|
17
|
+
*/
|
|
18
|
+
@RunWith(RobolectricTestRunner::class)
|
|
19
|
+
class ShortKitFeedViewActivePropTest {
|
|
20
|
+
|
|
21
|
+
private fun makeView() = ShortKitFeedView(
|
|
22
|
+
ApplicationProvider.getApplicationContext()
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun activeDefaultsToTrueAndPropSetIsFalse() {
|
|
27
|
+
val view = makeView()
|
|
28
|
+
assertTrue(view.active)
|
|
29
|
+
assertFalse(view.activePropSet)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
fun settingActiveTrueFlipsPropSet() {
|
|
34
|
+
val view = makeView()
|
|
35
|
+
view.setActiveFromBridge(true)
|
|
36
|
+
assertTrue(view.active)
|
|
37
|
+
assertTrue(view.activePropSet)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Test
|
|
41
|
+
fun settingActiveFalseFlipsPropSet() {
|
|
42
|
+
val view = makeView()
|
|
43
|
+
view.setActiveFromBridge(false)
|
|
44
|
+
assertFalse(view.active)
|
|
45
|
+
assertTrue(view.activePropSet)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
fun activeReflectsLatestValue() {
|
|
50
|
+
val view = makeView()
|
|
51
|
+
view.setActiveFromBridge(false)
|
|
52
|
+
view.setActiveFromBridge(true)
|
|
53
|
+
assertTrue(view.active)
|
|
54
|
+
view.setActiveFromBridge(false)
|
|
55
|
+
assertFalse(view.active)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -140,16 +140,18 @@ import ShortKitSDK
|
|
|
140
140
|
timeCoalesceTimer?.invalidate()
|
|
141
141
|
timeCoalesceTimer = nil
|
|
142
142
|
|
|
143
|
-
// Reset cached state so recycled cells don't flash stale
|
|
144
|
-
// the previous item
|
|
145
|
-
// are
|
|
146
|
-
//
|
|
147
|
-
//
|
|
143
|
+
// Reset item-owned cached state so recycled cells don't flash stale
|
|
144
|
+
// values from the previous item. Player-owned values (mute, rate,
|
|
145
|
+
// captions) are NOT reset here: the player is a single global
|
|
146
|
+
// instance with session-wide state, and its `CurrentValueSubject`
|
|
147
|
+
// publishers are subscribed to ONCE in `attach(player:)` (gated by
|
|
148
|
+
// `customOverlay == nil` in FeedCell). On cell reuse, attach() does
|
|
149
|
+
// not re-run, so the subscription does not re-emit the publisher's
|
|
150
|
+
// current value to overwrite a reset cache. Resetting here would
|
|
151
|
+
// leave the cache stuck at the default until the user manually
|
|
152
|
+
// toggles the value again.
|
|
148
153
|
cachedTime = (0, 0, 0)
|
|
149
154
|
cachedPlayerState = "idle"
|
|
150
|
-
cachedIsMuted = true
|
|
151
|
-
cachedPlaybackRate = 1.0
|
|
152
|
-
cachedCaptionsEnabled = false
|
|
153
155
|
cachedActiveCue = nil
|
|
154
156
|
cachedActiveCueJson = nil
|
|
155
157
|
cachedFeedScrollPhase = nil
|
|
@@ -97,13 +97,14 @@ import ShortKitSDK
|
|
|
97
97
|
timeDirty = false
|
|
98
98
|
timeCoalesceTimer?.invalidate()
|
|
99
99
|
timeCoalesceTimer = nil
|
|
100
|
-
// Reset cached state so recycled cells don't flash stale
|
|
101
|
-
// the previous item
|
|
102
|
-
//
|
|
103
|
-
//
|
|
100
|
+
// Reset item-owned cached state so recycled cells don't flash stale
|
|
101
|
+
// values from the previous item. Player-owned values (mute) are
|
|
102
|
+
// NOT reset here: the player's `CurrentValueSubject` publishers are
|
|
103
|
+
// subscribed to ONCE in `attach(player:)`, which doesn't re-run on
|
|
104
|
+
// cell reuse, so a reset would not be refreshed by a subsequent
|
|
105
|
+
// publisher tick — the cache would be stuck at the default.
|
|
104
106
|
cachedTime = (0, 0, 0)
|
|
105
107
|
cachedPlayerState = "idle"
|
|
106
|
-
cachedIsMuted = true
|
|
107
108
|
|
|
108
109
|
createSurfaceIfNeeded()
|
|
109
110
|
|
package/ios/ShortKitBridge.swift
CHANGED
|
@@ -100,6 +100,20 @@ import ShortKitSDK
|
|
|
100
100
|
])
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
// Wire per-feed video-carousel cell long-press event. Recognizer is
|
|
104
|
+
// owned by the cell and dies with the cell — no bridge bookkeeping
|
|
105
|
+
// needed. State is forwarded as a string ("began"/"ended"/"cancelled")
|
|
106
|
+
// because the underlying enum is `String`-backed.
|
|
107
|
+
vc.onVideoCarouselCellLongPress = { [weak self] payload in
|
|
108
|
+
self?.emitOnMain("onVideoCarouselCellLongPress", body: [
|
|
109
|
+
"feedId": id,
|
|
110
|
+
"id": payload.id,
|
|
111
|
+
"index": payload.index,
|
|
112
|
+
"pageIndex": payload.pageIndex,
|
|
113
|
+
"state": payload.state.rawValue,
|
|
114
|
+
])
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
// Wire per-feed transition event. The FVC fires this closure from
|
|
104
118
|
// handleSwipe(to:) — one per transition, bound to this feed. This
|
|
105
119
|
// replaces the global `player.feedTransition` subscription pattern
|
|
@@ -337,6 +351,10 @@ import ShortKitSDK
|
|
|
337
351
|
shortKit?.player.setCaptionsEnabled(enabled)
|
|
338
352
|
}
|
|
339
353
|
|
|
354
|
+
@objc public func setFeedScrollEnabled(_ enabled: Bool) {
|
|
355
|
+
shortKit?.player.setFeedScrollEnabled(enabled)
|
|
356
|
+
}
|
|
357
|
+
|
|
340
358
|
@objc public func selectCaptionTrack(_ language: String) {
|
|
341
359
|
shortKit?.player.selectCaptionTrack(language: language)
|
|
342
360
|
}
|
package/ios/ShortKitModule.mm
CHANGED
|
@@ -72,6 +72,7 @@ RCT_EXPORT_MODULE(ShortKitModule)
|
|
|
72
72
|
@"onDismiss",
|
|
73
73
|
@"onFeedReady",
|
|
74
74
|
@"onVideoCarouselCellTap",
|
|
75
|
+
@"onVideoCarouselCellLongPress",
|
|
75
76
|
@"onRefreshStateChanged",
|
|
76
77
|
@"onRefreshStateChangedPerFeed",
|
|
77
78
|
@"onDidFetchContentItems",
|
|
@@ -248,6 +249,10 @@ RCT_EXPORT_METHOD(setCaptionsEnabled:(BOOL)enabled) {
|
|
|
248
249
|
[_shortKitBridge setCaptionsEnabled:enabled];
|
|
249
250
|
}
|
|
250
251
|
|
|
252
|
+
RCT_EXPORT_METHOD(setFeedScrollEnabled:(BOOL)enabled) {
|
|
253
|
+
[_shortKitBridge setFeedScrollEnabled:enabled];
|
|
254
|
+
}
|
|
255
|
+
|
|
251
256
|
RCT_EXPORT_METHOD(selectCaptionTrack:(NSString *)language) {
|
|
252
257
|
[_shortKitBridge selectCaptionTrack:language];
|
|
253
258
|
}
|
|
@@ -48,6 +48,12 @@ import ShortKitSDK
|
|
|
48
48
|
private var playerVC: ShortKitPlayerViewController?
|
|
49
49
|
private var parsedConfig: PlayerConfig?
|
|
50
50
|
private var parsedFeedItems: [FeedInput] = []
|
|
51
|
+
/// Parsed feedMask mode pulled out of the serialized config JSON in
|
|
52
|
+
/// `applyConfig()`. Stored separately because the SDK's
|
|
53
|
+
/// `ShortKitPlayerViewController.feedMask` is a top-level property,
|
|
54
|
+
/// not part of `PlayerConfig`. Mirrors the same pattern in the
|
|
55
|
+
/// widget bridge.
|
|
56
|
+
private var parsedFeedMask: FeedMaskMode = .none
|
|
51
57
|
|
|
52
58
|
// MARK: - Lifecycle
|
|
53
59
|
|
|
@@ -112,6 +118,13 @@ import ShortKitSDK
|
|
|
112
118
|
vc.setFeedItems(parsedFeedItems)
|
|
113
119
|
}
|
|
114
120
|
|
|
121
|
+
// Forward the optional host-supplied feed mask. The SDK's
|
|
122
|
+
// `feedMask` lives as a top-level VC property (not in
|
|
123
|
+
// PlayerConfig), so it has to be set after construction. The
|
|
124
|
+
// factory closure inside the mode is invoked by the SDK each
|
|
125
|
+
// time the feed expansion runs.
|
|
126
|
+
vc.feedMask = parsedFeedMask
|
|
127
|
+
|
|
115
128
|
parentVC.addChild(vc)
|
|
116
129
|
vc.view.frame = bounds
|
|
117
130
|
vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
@@ -152,6 +165,11 @@ import ShortKitSDK
|
|
|
152
165
|
private func applyConfig() {
|
|
153
166
|
guard let json = config else { return }
|
|
154
167
|
parsedConfig = Self.parsePlayerConfig(json)
|
|
168
|
+
// FeedMask is stored as a sibling field in the serialized
|
|
169
|
+
// config JSON; extract it here so it's available when the VC
|
|
170
|
+
// gets (re)constructed in embedPlayerVCIfNeeded. Mirrors the
|
|
171
|
+
// widget bridge.
|
|
172
|
+
parsedFeedMask = Self.extractFeedMask(json)
|
|
155
173
|
// Config changes require re-embedding
|
|
156
174
|
if playerVC != nil {
|
|
157
175
|
destroyPlayerVC()
|
|
@@ -159,6 +177,24 @@ import ShortKitSDK
|
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
179
|
|
|
180
|
+
/// Pulls the `feedMask` field out of the raw config JSON without
|
|
181
|
+
/// re-parsing the rest of `PlayerConfig`. Returns `.none` if the
|
|
182
|
+
/// JSON is malformed or omits `feedMask`. Mirrors how the
|
|
183
|
+
/// equivalent value is read on `ShortKitWidgetNativeView`.
|
|
184
|
+
private static func extractFeedMask(_ json: String) -> FeedMaskMode {
|
|
185
|
+
guard let data = json.data(using: .utf8),
|
|
186
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
|
|
187
|
+
return .none
|
|
188
|
+
}
|
|
189
|
+
if let mask = obj["feedMask"] {
|
|
190
|
+
if let data = try? JSONSerialization.data(withJSONObject: mask),
|
|
191
|
+
let json = String(data: data, encoding: .utf8) {
|
|
192
|
+
return ShortKitBridge.parseFeedMask(json)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return .none
|
|
196
|
+
}
|
|
197
|
+
|
|
162
198
|
private func applyContentItem() {
|
|
163
199
|
guard let json = contentItem, let item = Self.parseContentItem(json) else { return }
|
|
164
200
|
playerVC?.configure(with: item)
|
|
@@ -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</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>
|
|
18
17
|
</array>
|
|
19
18
|
<key>SupportedPlatform</key>
|
|
20
19
|
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
20
|
</dict>
|
|
24
21
|
<dict>
|
|
25
22
|
<key>BinaryPath</key>
|
|
26
23
|
<string>ShortKitSDK.framework/ShortKitSDK</string>
|
|
27
24
|
<key>LibraryIdentifier</key>
|
|
28
|
-
<string>ios-
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
29
26
|
<key>LibraryPath</key>
|
|
30
27
|
<string>ShortKitSDK.framework</string>
|
|
31
28
|
<key>SupportedArchitectures</key>
|
|
32
29
|
<array>
|
|
33
30
|
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
34
32
|
</array>
|
|
35
33
|
<key>SupportedPlatform</key>
|
|
36
34
|
<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.36</string>
|
|
15
15
|
<key>CFBundleShortVersionString</key>
|
|
16
|
-
<string>0.2.
|
|
16
|
+
<string>0.2.36</string>
|
|
17
17
|
<key>MinimumOSVersion</key>
|
|
18
18
|
<string>16.0</string>
|
|
19
19
|
</dict>
|