@shortkitsdk/react-native 0.2.5 → 0.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ShortKitReactNative.podspec +1 -0
- package/android/build.gradle.kts +5 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +319 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +559 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +984 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +88 -220
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +12 -3
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +126 -706
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +2 -2
- package/android/src/main/java/com/shortkit/reactnative/ShortKitWidgetNativeView.kt +2 -2
- package/ios/ReactCarouselOverlayHost.swift +177 -0
- package/ios/ReactLoadingHost.swift +38 -0
- package/ios/ReactOverlayHost.swift +458 -0
- package/ios/SKFabricSurfaceWrapper.h +18 -0
- package/ios/SKFabricSurfaceWrapper.mm +57 -0
- package/ios/ShortKitBridge.swift +266 -65
- package/ios/ShortKitFeedView.swift +63 -207
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +86 -32
- package/ios/ShortKitPlayerNativeView.swift +39 -8
- package/ios/ShortKitReactNative-Bridging-Header.h +2 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +2 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3998 -962
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +85 -24
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +85 -24
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +2 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3998 -962
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +85 -24
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +85 -24
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/Info.plist +43 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Info.plist +16 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +28917 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/Modules/module.modulemap +4 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +418 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Info.plist +16 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +28917 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +824 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/Modules/module.modulemap +4 -0
- package/ios/ShortKitSDK.xcframework.bak/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitWidgetNativeView.swift +3 -3
- package/package.json +1 -1
- package/src/ShortKitCarouselOverlaySurface.tsx +55 -0
- package/src/ShortKitCommands.ts +31 -0
- package/src/ShortKitContext.ts +11 -25
- package/src/ShortKitFeed.tsx +110 -41
- package/src/ShortKitLoadingSurface.tsx +24 -0
- package/src/ShortKitOverlaySurface.tsx +205 -0
- package/src/ShortKitPlayer.tsx +6 -7
- package/src/ShortKitProvider.tsx +65 -250
- package/src/index.ts +9 -4
- package/src/serialization.ts +22 -42
- package/src/specs/NativeShortKitModule.ts +67 -53
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +104 -19
- package/src/useShortKit.ts +1 -3
- package/src/useShortKitPlayer.ts +7 -8
- package/android/src/main/java/com/shortkit/reactnative/ShortKitCarouselOverlayBridge.kt +0 -48
- package/android/src/main/java/com/shortkit/reactnative/ShortKitOverlayBridge.kt +0 -128
- package/ios/ShortKitCarouselOverlayBridge.swift +0 -54
- package/ios/ShortKitOverlayBridge.swift +0 -113
- package/src/CarouselOverlayManager.tsx +0 -71
- package/src/OverlayManager.tsx +0 -87
- package/src/useShortKitCarousel.ts +0 -29
|
@@ -1,843 +1,263 @@
|
|
|
1
1
|
package com.shortkit.reactnative
|
|
2
2
|
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
3
5
|
import com.facebook.react.bridge.Arguments
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
4
7
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
8
|
import com.facebook.react.bridge.ReactMethod
|
|
6
9
|
import com.facebook.react.bridge.WritableMap
|
|
7
10
|
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
8
|
-
import com.shortkit.CarouselImage
|
|
9
|
-
import com.shortkit.ContentItem
|
|
10
|
-
import com.shortkit.ContentSignal
|
|
11
|
-
import com.shortkit.CustomFeedItem
|
|
12
|
-
import com.shortkit.ImageCarouselItem
|
|
13
|
-
import com.shortkit.FeedConfig
|
|
14
|
-
import com.shortkit.FeedHeight
|
|
15
|
-
import com.shortkit.FeedSource
|
|
16
|
-
import com.shortkit.FeedTransitionPhase
|
|
17
|
-
import com.shortkit.JsonValue
|
|
18
|
-
import com.shortkit.ShortKit
|
|
19
|
-
import com.shortkit.ShortKitPlayer
|
|
20
|
-
import com.shortkit.VideoOverlayMode
|
|
21
|
-
import com.shortkit.CarouselOverlayMode
|
|
22
|
-
import com.shortkit.SurveyOverlayMode
|
|
23
|
-
import com.shortkit.AdOverlayMode
|
|
24
|
-
import kotlinx.coroutines.CoroutineScope
|
|
25
|
-
import kotlinx.coroutines.Dispatchers
|
|
26
|
-
import kotlinx.coroutines.SupervisorJob
|
|
27
|
-
import kotlinx.coroutines.cancel
|
|
28
|
-
import kotlinx.coroutines.launch
|
|
29
|
-
import org.json.JSONArray
|
|
30
|
-
import org.json.JSONObject
|
|
31
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Thin TurboModule that delegates all logic to [ShortKitBridge].
|
|
14
|
+
*
|
|
15
|
+
* Responsibilities:
|
|
16
|
+
* - Forward every @ReactMethod to the bridge
|
|
17
|
+
* - Handle event emission via RCTDeviceEventEmitter
|
|
18
|
+
* - Buffer operations that arrive before initialize() creates the bridge
|
|
19
|
+
*/
|
|
32
20
|
class ShortKitModule(reactContext: ReactApplicationContext) :
|
|
33
21
|
NativeShortKitModuleSpec(reactContext) {
|
|
34
22
|
|
|
35
23
|
companion object {
|
|
36
24
|
const val NAME = "ShortKitModule"
|
|
37
|
-
|
|
38
|
-
/** Static reference for Fabric view access (mirrors iOS ShortKitBridge.shared). */
|
|
39
|
-
@Volatile
|
|
40
|
-
var shared: ShortKitModule? = null
|
|
41
|
-
private set
|
|
42
25
|
}
|
|
43
26
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// -----------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
private var shortKit: ShortKit? = null
|
|
49
|
-
private var scope: CoroutineScope? = null
|
|
27
|
+
private var bridge: ShortKitBridge? = null
|
|
28
|
+
private var pendingBridgeOps: MutableList<() -> Unit>? = null
|
|
50
29
|
private var listenerCount = 0
|
|
51
|
-
@Volatile
|
|
52
|
-
private var hasListeners = false
|
|
53
|
-
private var pendingFeedItems: String? = null
|
|
54
|
-
private var pendingAppendItems: String? = null
|
|
30
|
+
@Volatile private var hasListeners = false
|
|
55
31
|
|
|
56
|
-
|
|
57
|
-
val sdk: ShortKit? get() = shortKit
|
|
58
|
-
|
|
59
|
-
// -----------------------------------------------------------------------
|
|
32
|
+
// -----------------------------------------------------------------
|
|
60
33
|
// Module boilerplate
|
|
61
|
-
//
|
|
34
|
+
// -----------------------------------------------------------------
|
|
62
35
|
|
|
63
36
|
override fun getName(): String = NAME
|
|
64
37
|
|
|
65
38
|
override fun initialize() {
|
|
66
39
|
super.initialize()
|
|
67
|
-
shared = this
|
|
68
40
|
}
|
|
69
41
|
|
|
70
42
|
override fun onCatalystInstanceDestroy() {
|
|
71
|
-
teardown()
|
|
72
|
-
|
|
43
|
+
bridge?.teardown()
|
|
44
|
+
bridge = null
|
|
73
45
|
super.onCatalystInstanceDestroy()
|
|
74
46
|
}
|
|
75
47
|
|
|
76
|
-
//
|
|
77
|
-
// Event
|
|
78
|
-
//
|
|
48
|
+
// -----------------------------------------------------------------
|
|
49
|
+
// Event listener management
|
|
50
|
+
// -----------------------------------------------------------------
|
|
79
51
|
|
|
80
|
-
|
|
52
|
+
@ReactMethod
|
|
53
|
+
fun addListener(eventType: String?) {
|
|
81
54
|
listenerCount++
|
|
82
55
|
hasListeners = true
|
|
83
56
|
}
|
|
84
57
|
|
|
85
|
-
|
|
58
|
+
@ReactMethod
|
|
59
|
+
fun removeListeners(count: Double) {
|
|
86
60
|
listenerCount = maxOf(0, listenerCount - count.toInt())
|
|
87
61
|
hasListeners = listenerCount > 0
|
|
88
62
|
}
|
|
89
63
|
|
|
90
|
-
//
|
|
91
|
-
// Lifecycle
|
|
92
|
-
//
|
|
64
|
+
// -----------------------------------------------------------------
|
|
65
|
+
// Lifecycle
|
|
66
|
+
// -----------------------------------------------------------------
|
|
93
67
|
|
|
94
68
|
@ReactMethod
|
|
95
69
|
override fun initialize(
|
|
96
70
|
apiKey: String,
|
|
97
|
-
|
|
98
|
-
embedId: String?,
|
|
71
|
+
hasLoadingView: Boolean,
|
|
99
72
|
clientAppName: String?,
|
|
100
73
|
clientAppVersion: String?,
|
|
101
74
|
customDimensions: String?
|
|
102
75
|
) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
val feedConfig = parseFeedConfig(config)
|
|
107
|
-
val dims = parseCustomDimensions(customDimensions)
|
|
76
|
+
bridge?.teardown()
|
|
77
|
+
bridge = null
|
|
108
78
|
|
|
109
79
|
val context = reactApplicationContext
|
|
110
80
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
this.shortKit = sdk
|
|
123
|
-
shared = this
|
|
124
|
-
|
|
125
|
-
// Replay any feed items that arrived before initialization
|
|
126
|
-
pendingFeedItems?.let { json ->
|
|
127
|
-
parseCustomFeedItems(json)?.let { sdk.setFeedItems(it) }
|
|
128
|
-
pendingFeedItems = null
|
|
129
|
-
}
|
|
130
|
-
pendingAppendItems?.let { json ->
|
|
131
|
-
parseCustomFeedItems(json)?.let { sdk.appendFeedItems(it) }
|
|
132
|
-
pendingAppendItems = null
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
subscribeToFlows(sdk.player)
|
|
81
|
+
Handler(Looper.getMainLooper()).post {
|
|
82
|
+
bridge = ShortKitBridge(
|
|
83
|
+
apiKey = apiKey,
|
|
84
|
+
context = context,
|
|
85
|
+
hasLoadingView = hasLoadingView,
|
|
86
|
+
clientAppName = clientAppName,
|
|
87
|
+
clientAppVersion = clientAppVersion,
|
|
88
|
+
customDimensionsJSON = customDimensions,
|
|
89
|
+
emitEvent = { name, body -> sendEvent(name, body) }
|
|
90
|
+
)
|
|
136
91
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
val params = Arguments.createMap().apply {
|
|
140
|
-
putString("contentId", contentId)
|
|
141
|
-
putInt("index", index)
|
|
142
|
-
}
|
|
143
|
-
sendEvent("onContentTapped", params)
|
|
144
|
-
}
|
|
92
|
+
pendingBridgeOps?.forEach { it() }
|
|
93
|
+
pendingBridgeOps = null
|
|
145
94
|
}
|
|
146
95
|
}
|
|
147
96
|
|
|
97
|
+
@ReactMethod
|
|
98
|
+
override fun destroy() {
|
|
99
|
+
bridge?.teardown()
|
|
100
|
+
bridge = null
|
|
101
|
+
}
|
|
102
|
+
|
|
148
103
|
@ReactMethod
|
|
149
104
|
override fun setUserId(userId: String) {
|
|
150
|
-
|
|
105
|
+
bridge?.setUserId(userId)
|
|
151
106
|
}
|
|
152
107
|
|
|
153
108
|
@ReactMethod
|
|
154
109
|
override fun clearUserId() {
|
|
155
|
-
|
|
110
|
+
bridge?.clearUserId()
|
|
156
111
|
}
|
|
157
112
|
|
|
158
113
|
@ReactMethod
|
|
159
114
|
override fun onPause() {
|
|
160
|
-
|
|
115
|
+
bridge?.onPause()
|
|
161
116
|
}
|
|
162
117
|
|
|
163
|
-
/// Called when the app foregrounds. We do NOT auto-resume here because:
|
|
164
|
-
/// 1. The user may have manually paused before backgrounding.
|
|
165
|
-
/// 2. The ShortKit SDK's internal lifecycle management already resumes
|
|
166
|
-
/// playback when appropriate via Activity lifecycle callbacks.
|
|
167
118
|
@ReactMethod
|
|
168
119
|
override fun onResume() {
|
|
169
|
-
|
|
120
|
+
bridge?.onResume()
|
|
170
121
|
}
|
|
171
122
|
|
|
172
|
-
|
|
173
|
-
override fun destroy() {
|
|
174
|
-
teardown()
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// -----------------------------------------------------------------------
|
|
123
|
+
// -----------------------------------------------------------------
|
|
178
124
|
// Player controls
|
|
179
|
-
//
|
|
125
|
+
// -----------------------------------------------------------------
|
|
180
126
|
|
|
181
127
|
@ReactMethod
|
|
182
|
-
override fun play() {
|
|
183
|
-
shortKit?.player?.play()
|
|
184
|
-
}
|
|
128
|
+
override fun play() { bridge?.play() }
|
|
185
129
|
|
|
186
130
|
@ReactMethod
|
|
187
|
-
override fun pause() {
|
|
188
|
-
shortKit?.player?.pause()
|
|
189
|
-
}
|
|
131
|
+
override fun pause() { bridge?.pause() }
|
|
190
132
|
|
|
191
133
|
@ReactMethod
|
|
192
|
-
override fun seek(seconds: Double) {
|
|
193
|
-
shortKit?.player?.seek(seconds)
|
|
194
|
-
}
|
|
134
|
+
override fun seek(seconds: Double) { bridge?.seek(seconds) }
|
|
195
135
|
|
|
196
136
|
@ReactMethod
|
|
197
|
-
override fun seekAndPlay(seconds: Double) {
|
|
198
|
-
shortKit?.player?.seekAndPlay(seconds)
|
|
199
|
-
}
|
|
137
|
+
override fun seekAndPlay(seconds: Double) { bridge?.seekAndPlay(seconds) }
|
|
200
138
|
|
|
201
139
|
@ReactMethod
|
|
202
|
-
override fun skipToNext() {
|
|
203
|
-
shortKit?.player?.skipToNext()
|
|
204
|
-
}
|
|
140
|
+
override fun skipToNext() { bridge?.skipToNext() }
|
|
205
141
|
|
|
206
142
|
@ReactMethod
|
|
207
|
-
override fun skipToPrevious() {
|
|
208
|
-
shortKit?.player?.skipToPrevious()
|
|
209
|
-
}
|
|
143
|
+
override fun skipToPrevious() { bridge?.skipToPrevious() }
|
|
210
144
|
|
|
211
145
|
@ReactMethod
|
|
212
|
-
override fun setMuted(muted: Boolean) {
|
|
213
|
-
shortKit?.player?.setMuted(muted)
|
|
214
|
-
}
|
|
146
|
+
override fun setMuted(muted: Boolean) { bridge?.setMuted(muted) }
|
|
215
147
|
|
|
216
148
|
@ReactMethod
|
|
217
|
-
override fun setPlaybackRate(rate: Double) {
|
|
218
|
-
shortKit?.player?.setPlaybackRate(rate.toFloat())
|
|
219
|
-
}
|
|
149
|
+
override fun setPlaybackRate(rate: Double) { bridge?.setPlaybackRate(rate) }
|
|
220
150
|
|
|
221
151
|
@ReactMethod
|
|
222
|
-
override fun setCaptionsEnabled(enabled: Boolean) {
|
|
223
|
-
shortKit?.player?.setCaptionsEnabled(enabled)
|
|
224
|
-
}
|
|
152
|
+
override fun setCaptionsEnabled(enabled: Boolean) { bridge?.setCaptionsEnabled(enabled) }
|
|
225
153
|
|
|
226
154
|
@ReactMethod
|
|
227
|
-
override fun selectCaptionTrack(language: String) {
|
|
228
|
-
shortKit?.player?.selectCaptionTrack(language)
|
|
229
|
-
}
|
|
155
|
+
override fun selectCaptionTrack(language: String) { bridge?.selectCaptionTrack(language) }
|
|
230
156
|
|
|
231
157
|
@ReactMethod
|
|
232
|
-
override fun sendContentSignal(signal: String) {
|
|
233
|
-
val contentSignal = if (signal == "positive") ContentSignal.POSITIVE else ContentSignal.NEGATIVE
|
|
234
|
-
shortKit?.player?.sendContentSignal(contentSignal)
|
|
235
|
-
}
|
|
158
|
+
override fun sendContentSignal(signal: String) { bridge?.sendContentSignal(signal) }
|
|
236
159
|
|
|
237
160
|
@ReactMethod
|
|
238
|
-
override fun setMaxBitrate(bitrate: Double) {
|
|
239
|
-
shortKit?.player?.setMaxBitrate(bitrate.toInt())
|
|
240
|
-
}
|
|
161
|
+
override fun setMaxBitrate(bitrate: Double) { bridge?.setMaxBitrate(bitrate) }
|
|
241
162
|
|
|
242
|
-
//
|
|
163
|
+
// -----------------------------------------------------------------
|
|
243
164
|
// Custom feed
|
|
244
|
-
//
|
|
165
|
+
// -----------------------------------------------------------------
|
|
245
166
|
|
|
246
167
|
@ReactMethod
|
|
247
|
-
override fun setFeedItems(items: String) {
|
|
248
|
-
val
|
|
249
|
-
if (
|
|
250
|
-
|
|
251
|
-
sdk.setFeedItems(parsed)
|
|
168
|
+
override fun setFeedItems(feedId: String, items: String) {
|
|
169
|
+
val b = bridge
|
|
170
|
+
if (b != null) {
|
|
171
|
+
b.setFeedItems(feedId, items)
|
|
252
172
|
} else {
|
|
253
|
-
|
|
173
|
+
bufferOp { bridge?.setFeedItems(feedId, items) }
|
|
254
174
|
}
|
|
255
175
|
}
|
|
256
176
|
|
|
257
177
|
@ReactMethod
|
|
258
|
-
override fun appendFeedItems(items: String) {
|
|
259
|
-
val
|
|
260
|
-
if (
|
|
261
|
-
|
|
262
|
-
sdk.appendFeedItems(parsed)
|
|
178
|
+
override fun appendFeedItems(feedId: String, items: String) {
|
|
179
|
+
val b = bridge
|
|
180
|
+
if (b != null) {
|
|
181
|
+
b.appendFeedItems(feedId, items)
|
|
263
182
|
} else {
|
|
264
|
-
|
|
183
|
+
bufferOp { bridge?.appendFeedItems(feedId, items) }
|
|
265
184
|
}
|
|
266
185
|
}
|
|
267
186
|
|
|
268
187
|
@ReactMethod
|
|
269
|
-
override fun fetchContent(limit: Double, promise:
|
|
270
|
-
val
|
|
271
|
-
if (
|
|
188
|
+
override fun fetchContent(limit: Double, filterJSON: String?, promise: Promise) {
|
|
189
|
+
val b = bridge
|
|
190
|
+
if (b == null) {
|
|
272
191
|
promise.resolve("[]")
|
|
273
192
|
return
|
|
274
193
|
}
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
val items = sdk.fetchContent(limit.toInt())
|
|
278
|
-
val arr = JSONArray()
|
|
279
|
-
for (item in items) {
|
|
280
|
-
arr.put(JSONObject(serializeContentItemToJSON(item)))
|
|
281
|
-
}
|
|
282
|
-
promise.resolve(arr.toString())
|
|
283
|
-
} catch (e: Exception) {
|
|
284
|
-
promise.resolve("[]")
|
|
285
|
-
}
|
|
286
|
-
}
|
|
194
|
+
b.fetchContent(limit.toInt(), filterJSON) { result -> promise.resolve(result) }
|
|
287
195
|
}
|
|
288
196
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
197
|
+
@ReactMethod
|
|
198
|
+
override fun applyFilter(feedId: String, filterJSON: String?) {
|
|
199
|
+
val b = bridge
|
|
200
|
+
if (b != null) {
|
|
201
|
+
b.applyFilter(feedId, filterJSON)
|
|
202
|
+
} else {
|
|
203
|
+
bufferOp { bridge?.applyFilter(feedId, filterJSON) }
|
|
296
204
|
}
|
|
297
|
-
sendEvent(name, params)
|
|
298
205
|
}
|
|
299
206
|
|
|
300
|
-
|
|
301
|
-
|
|
207
|
+
@ReactMethod
|
|
208
|
+
override fun preloadFeed(configJSON: String, promise: Promise) {
|
|
209
|
+
val b = bridge
|
|
210
|
+
if (b == null) {
|
|
211
|
+
promise.resolve("")
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
b.preloadFeed(configJSON) { result -> promise.resolve(result) }
|
|
302
215
|
}
|
|
303
216
|
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
//
|
|
217
|
+
// -----------------------------------------------------------------
|
|
218
|
+
// Storyboard / Seek Thumbnails
|
|
219
|
+
// -----------------------------------------------------------------
|
|
307
220
|
|
|
308
|
-
|
|
309
|
-
|
|
221
|
+
@ReactMethod
|
|
222
|
+
override fun prefetchStoryboard(playbackId: String) {
|
|
223
|
+
bridge?.prefetchStoryboard(playbackId)
|
|
310
224
|
}
|
|
311
225
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
val newScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
|
319
|
-
scope = newScope
|
|
320
|
-
|
|
321
|
-
// Player state
|
|
322
|
-
newScope.launch {
|
|
323
|
-
player.playerState.collect { state ->
|
|
324
|
-
val params = Arguments.createMap().apply {
|
|
325
|
-
putString("state", playerStateString(state))
|
|
326
|
-
if (state is com.shortkit.PlayerState.Error) {
|
|
327
|
-
putString("errorMessage", state.message)
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
sendEvent("onPlayerStateChanged", params)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Current item
|
|
335
|
-
newScope.launch {
|
|
336
|
-
player.currentItem.collect { item ->
|
|
337
|
-
if (item != null) {
|
|
338
|
-
sendEvent("onCurrentItemChanged", contentItemMap(item))
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Time updates
|
|
344
|
-
newScope.launch {
|
|
345
|
-
player.time.collect { time ->
|
|
346
|
-
val params = Arguments.createMap().apply {
|
|
347
|
-
putDouble("current", time.currentMs / 1000.0)
|
|
348
|
-
putDouble("duration", time.durationMs / 1000.0)
|
|
349
|
-
putDouble("buffered", time.bufferedMs / 1000.0)
|
|
350
|
-
}
|
|
351
|
-
sendEvent("onTimeUpdate", params)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Muted state
|
|
356
|
-
newScope.launch {
|
|
357
|
-
player.isMuted.collect { muted ->
|
|
358
|
-
val params = Arguments.createMap().apply {
|
|
359
|
-
putBoolean("isMuted", muted)
|
|
360
|
-
}
|
|
361
|
-
sendEvent("onMutedChanged", params)
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Playback rate
|
|
366
|
-
newScope.launch {
|
|
367
|
-
player.playbackRate.collect { rate ->
|
|
368
|
-
val params = Arguments.createMap().apply {
|
|
369
|
-
putDouble("rate", rate.toDouble())
|
|
370
|
-
}
|
|
371
|
-
sendEvent("onPlaybackRateChanged", params)
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Captions enabled
|
|
376
|
-
newScope.launch {
|
|
377
|
-
player.captionsEnabled.collect { enabled ->
|
|
378
|
-
val params = Arguments.createMap().apply {
|
|
379
|
-
putBoolean("enabled", enabled)
|
|
380
|
-
}
|
|
381
|
-
sendEvent("onCaptionsEnabledChanged", params)
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Active caption track
|
|
386
|
-
newScope.launch {
|
|
387
|
-
player.activeCaptionTrack.collect { track ->
|
|
388
|
-
if (track != null) {
|
|
389
|
-
val params = Arguments.createMap().apply {
|
|
390
|
-
putString("language", track.language)
|
|
391
|
-
putString("label", track.label)
|
|
392
|
-
putString("sourceUrl", track.url ?: "")
|
|
393
|
-
}
|
|
394
|
-
sendEvent("onActiveCaptionTrackChanged", params)
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
// Active cue (ms -> seconds)
|
|
400
|
-
newScope.launch {
|
|
401
|
-
player.activeCue.collect { cue ->
|
|
402
|
-
if (cue != null) {
|
|
403
|
-
val params = Arguments.createMap().apply {
|
|
404
|
-
putString("text", cue.text)
|
|
405
|
-
putDouble("startTime", cue.startMs / 1000.0)
|
|
406
|
-
putDouble("endTime", cue.endMs / 1000.0)
|
|
407
|
-
}
|
|
408
|
-
sendEvent("onActiveCueChanged", params)
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// Did loop
|
|
414
|
-
newScope.launch {
|
|
415
|
-
player.didLoop.collect { event ->
|
|
416
|
-
val params = Arguments.createMap().apply {
|
|
417
|
-
putString("contentId", event.contentId)
|
|
418
|
-
putInt("loopCount", event.loopCount)
|
|
419
|
-
}
|
|
420
|
-
sendEvent("onDidLoop", params)
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Feed transition
|
|
425
|
-
newScope.launch {
|
|
426
|
-
player.feedTransition.collect { event ->
|
|
427
|
-
val params = Arguments.createMap().apply {
|
|
428
|
-
putString("phase", when (event.phase) {
|
|
429
|
-
FeedTransitionPhase.BEGAN -> "began"
|
|
430
|
-
FeedTransitionPhase.ENDED -> "ended"
|
|
431
|
-
})
|
|
432
|
-
putString("direction", when (event.direction) {
|
|
433
|
-
com.shortkit.FeedDirection.FORWARD -> "forward"
|
|
434
|
-
com.shortkit.FeedDirection.BACKWARD -> "backward"
|
|
435
|
-
else -> "forward"
|
|
436
|
-
})
|
|
437
|
-
if (event.from != null) {
|
|
438
|
-
putString("fromItem", serializeContentItemToJSON(event.from!!))
|
|
439
|
-
}
|
|
440
|
-
if (event.to != null) {
|
|
441
|
-
putString("toItem", serializeContentItemToJSON(event.to!!))
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
sendEvent("onFeedTransition", params)
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// Format change (Long -> Double for bitrate)
|
|
449
|
-
newScope.launch {
|
|
450
|
-
player.formatChange.collect { event ->
|
|
451
|
-
val params = Arguments.createMap().apply {
|
|
452
|
-
putString("contentId", event.contentId)
|
|
453
|
-
putDouble("fromBitrate", event.fromBitrate.toDouble())
|
|
454
|
-
putDouble("toBitrate", event.toBitrate.toDouble())
|
|
455
|
-
putString("fromResolution", event.fromResolution ?: "")
|
|
456
|
-
putString("toResolution", event.toResolution ?: "")
|
|
457
|
-
}
|
|
458
|
-
sendEvent("onFormatChange", params)
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Prefetched ahead count
|
|
463
|
-
newScope.launch {
|
|
464
|
-
player.prefetchedAheadCount.collect { count ->
|
|
465
|
-
val params = Arguments.createMap().apply {
|
|
466
|
-
putInt("count", count)
|
|
467
|
-
}
|
|
468
|
-
sendEvent("onPrefetchedAheadCountChanged", params)
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// Remaining content count
|
|
473
|
-
newScope.launch {
|
|
474
|
-
player.remainingContentCount.collect { count ->
|
|
475
|
-
val params = Arguments.createMap().apply {
|
|
476
|
-
putInt("count", count)
|
|
477
|
-
}
|
|
478
|
-
sendEvent("onRemainingContentCountChanged", params)
|
|
479
|
-
}
|
|
226
|
+
@ReactMethod
|
|
227
|
+
override fun getStoryboardData(playbackId: String, promise: Promise) {
|
|
228
|
+
val b = bridge
|
|
229
|
+
if (b == null) {
|
|
230
|
+
promise.resolve(null)
|
|
231
|
+
return
|
|
480
232
|
}
|
|
233
|
+
b.getStoryboardData(playbackId) { result -> promise.resolve(result) }
|
|
481
234
|
}
|
|
482
235
|
|
|
483
|
-
//
|
|
236
|
+
// -----------------------------------------------------------------
|
|
484
237
|
// Event emission
|
|
485
|
-
//
|
|
238
|
+
// -----------------------------------------------------------------
|
|
486
239
|
|
|
487
240
|
private fun sendEvent(name: String, params: WritableMap) {
|
|
488
|
-
|
|
241
|
+
// NOTE: In RN 0.78 new architecture, the codegen EventEmitter<T> does
|
|
242
|
+
// NOT call the legacy addListener/removeListeners methods, so the
|
|
243
|
+
// hasListeners flag stays false forever. We skip the guard and always
|
|
244
|
+
// emit. The try/catch handles the case where the catalyst instance
|
|
245
|
+
// is torn down.
|
|
489
246
|
try {
|
|
490
247
|
reactApplicationContext
|
|
491
248
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
492
249
|
.emit(name, params)
|
|
493
|
-
} catch (
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
// -----------------------------------------------------------------------
|
|
499
|
-
// Teardown
|
|
500
|
-
// -----------------------------------------------------------------------
|
|
501
|
-
|
|
502
|
-
private fun teardown() {
|
|
503
|
-
scope?.cancel()
|
|
504
|
-
scope = null
|
|
505
|
-
shortKit?.release()
|
|
506
|
-
shortKit = null
|
|
507
|
-
if (shared === this) shared = null
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// -----------------------------------------------------------------------
|
|
511
|
-
// Content item serialization
|
|
512
|
-
// -----------------------------------------------------------------------
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* Build a flat WritableMap for `onCurrentItemChanged`.
|
|
516
|
-
* `captionTracks` and `customMetadata` are JSON-serialized strings.
|
|
517
|
-
*/
|
|
518
|
-
private fun contentItemMap(item: ContentItem): WritableMap {
|
|
519
|
-
return Arguments.createMap().apply {
|
|
520
|
-
putString("id", item.id)
|
|
521
|
-
item.playbackId?.let { putString("playbackId", it) }
|
|
522
|
-
putString("title", item.title)
|
|
523
|
-
item.description?.let { putString("description", it) }
|
|
524
|
-
putDouble("duration", item.duration)
|
|
525
|
-
putString("streamingUrl", item.streamingUrl)
|
|
526
|
-
putString("thumbnailUrl", item.thumbnailUrl)
|
|
527
|
-
|
|
528
|
-
// Caption tracks as JSON string
|
|
529
|
-
putString("captionTracks", serializeCaptionTracks(item.captionTracks))
|
|
530
|
-
|
|
531
|
-
// Custom metadata as JSON string
|
|
532
|
-
item.customMetadata?.let { meta ->
|
|
533
|
-
putString("customMetadata", serializeCustomMetadata(meta))
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
item.author?.let { putString("author", it) }
|
|
537
|
-
item.articleUrl?.let { putString("articleUrl", it) }
|
|
538
|
-
item.commentCount?.let { putInt("commentCount", it) }
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* Serialize a full ContentItem to a JSON string for delegate/overlay events.
|
|
544
|
-
*/
|
|
545
|
-
private fun serializeContentItemToJSON(item: ContentItem): String {
|
|
546
|
-
return try {
|
|
547
|
-
val obj = JSONObject().apply {
|
|
548
|
-
put("id", item.id)
|
|
549
|
-
put("title", item.title)
|
|
550
|
-
item.description?.let { put("description", it) }
|
|
551
|
-
put("duration", item.duration)
|
|
552
|
-
put("streamingUrl", item.streamingUrl)
|
|
553
|
-
put("thumbnailUrl", item.thumbnailUrl)
|
|
554
|
-
put("captionTracks", buildCaptionTracksJSONArray(item.captionTracks))
|
|
555
|
-
item.customMetadata?.let { put("customMetadata", buildCustomMetadataJSONObject(it)) }
|
|
556
|
-
item.author?.let { put("author", it) }
|
|
557
|
-
item.articleUrl?.let { put("articleUrl", it) }
|
|
558
|
-
item.commentCount?.let { put("commentCount", it) }
|
|
559
|
-
}
|
|
560
|
-
obj.toString()
|
|
561
|
-
} catch (_: Exception) {
|
|
562
|
-
"{}"
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/**
|
|
567
|
-
* Serialize caption tracks list to a JSON string: `[{"language":"en","label":"English","sourceUrl":"..."}]`
|
|
568
|
-
*/
|
|
569
|
-
private fun serializeCaptionTracks(tracks: List<com.shortkit.CaptionTrack>): String {
|
|
570
|
-
return try {
|
|
571
|
-
buildCaptionTracksJSONArray(tracks).toString()
|
|
572
|
-
} catch (_: Exception) {
|
|
573
|
-
"[]"
|
|
250
|
+
} catch (e: Exception) {
|
|
251
|
+
android.util.Log.e("SK:Module", "sendEvent($name) EXCEPTION: ${e.message}", e)
|
|
574
252
|
}
|
|
575
253
|
}
|
|
576
254
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
val obj = JSONObject().apply {
|
|
581
|
-
put("language", track.language)
|
|
582
|
-
put("label", track.label)
|
|
583
|
-
put("sourceUrl", track.url ?: "")
|
|
584
|
-
}
|
|
585
|
-
arr.put(obj)
|
|
586
|
-
}
|
|
587
|
-
return arr
|
|
588
|
-
}
|
|
255
|
+
// -----------------------------------------------------------------
|
|
256
|
+
// Op buffering
|
|
257
|
+
// -----------------------------------------------------------------
|
|
589
258
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
private fun serializeCustomMetadata(meta: Map<String, JsonValue>): String {
|
|
594
|
-
return try {
|
|
595
|
-
buildCustomMetadataJSONObject(meta).toString()
|
|
596
|
-
} catch (_: Exception) {
|
|
597
|
-
"{}"
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
private fun buildCustomMetadataJSONObject(meta: Map<String, JsonValue>): JSONObject {
|
|
602
|
-
val obj = JSONObject()
|
|
603
|
-
for ((key, value) in meta) {
|
|
604
|
-
obj.put(key, jsonValueToAny(value))
|
|
605
|
-
}
|
|
606
|
-
return obj
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Convert a ShortKit JsonValue sealed class to a native type suitable for JSONObject.
|
|
611
|
-
*/
|
|
612
|
-
private fun jsonValueToAny(value: JsonValue): Any {
|
|
613
|
-
return when (value) {
|
|
614
|
-
is JsonValue.StringValue -> value.value
|
|
615
|
-
is JsonValue.NumberValue -> value.value
|
|
616
|
-
is JsonValue.BoolValue -> value.value
|
|
617
|
-
is JsonValue.ObjectValue -> {
|
|
618
|
-
val obj = JSONObject()
|
|
619
|
-
for ((k, v) in value.value) {
|
|
620
|
-
obj.put(k, jsonValueToAny(v))
|
|
621
|
-
}
|
|
622
|
-
obj
|
|
623
|
-
}
|
|
624
|
-
is JsonValue.NullValue -> JSONObject.NULL
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// -----------------------------------------------------------------------
|
|
629
|
-
// Player state serialization
|
|
630
|
-
// -----------------------------------------------------------------------
|
|
631
|
-
|
|
632
|
-
private fun playerStateString(state: com.shortkit.PlayerState): String {
|
|
633
|
-
return when (state) {
|
|
634
|
-
is com.shortkit.PlayerState.Idle -> "idle"
|
|
635
|
-
is com.shortkit.PlayerState.Loading -> "loading"
|
|
636
|
-
is com.shortkit.PlayerState.Ready -> "ready"
|
|
637
|
-
is com.shortkit.PlayerState.Playing -> "playing"
|
|
638
|
-
is com.shortkit.PlayerState.Paused -> "paused"
|
|
639
|
-
is com.shortkit.PlayerState.Seeking -> "seeking"
|
|
640
|
-
is com.shortkit.PlayerState.Buffering -> "buffering"
|
|
641
|
-
is com.shortkit.PlayerState.Ended -> "ended"
|
|
642
|
-
is com.shortkit.PlayerState.Error -> "error"
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
// -----------------------------------------------------------------------
|
|
647
|
-
// Config parsing
|
|
648
|
-
// -----------------------------------------------------------------------
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* Parse the JSON config string from JS into a FeedConfig.
|
|
652
|
-
*
|
|
653
|
-
* Expected JSON shape:
|
|
654
|
-
* ```json
|
|
655
|
-
* {
|
|
656
|
-
* "feedHeight": "{\"type\":\"fullscreen\"}",
|
|
657
|
-
* "overlay": "\"none\"",
|
|
658
|
-
* "carouselMode": "\"none\"",
|
|
659
|
-
* "surveyMode": "\"none\"",
|
|
660
|
-
* "muteOnStart": true
|
|
661
|
-
* }
|
|
662
|
-
* ```
|
|
663
|
-
*/
|
|
664
|
-
private fun parseFeedConfig(json: String): FeedConfig {
|
|
665
|
-
return try {
|
|
666
|
-
val obj = JSONObject(json)
|
|
667
|
-
|
|
668
|
-
val feedHeight = parseFeedHeight(obj.optString("feedHeight", null))
|
|
669
|
-
val muteOnStart = obj.optBoolean("muteOnStart", true)
|
|
670
|
-
val videoOverlay = parseVideoOverlay(obj.optString("overlay", null))
|
|
671
|
-
|
|
672
|
-
val feedSourceStr = obj.optString("feedSource", "algorithmic")
|
|
673
|
-
val feedSource = if (feedSourceStr == "custom") FeedSource.CUSTOM else FeedSource.ALGORITHMIC
|
|
674
|
-
|
|
675
|
-
val carouselOverlay = parseCarouselOverlay(obj.optString("carouselOverlay", null))
|
|
676
|
-
|
|
677
|
-
FeedConfig(
|
|
678
|
-
feedHeight = feedHeight,
|
|
679
|
-
videoOverlay = videoOverlay,
|
|
680
|
-
carouselOverlay = carouselOverlay,
|
|
681
|
-
surveyOverlay = SurveyOverlayMode.None,
|
|
682
|
-
adOverlay = AdOverlayMode.None,
|
|
683
|
-
muteOnStart = muteOnStart,
|
|
684
|
-
feedSource = feedSource
|
|
685
|
-
)
|
|
686
|
-
} catch (_: Exception) {
|
|
687
|
-
FeedConfig()
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
/**
|
|
692
|
-
* Parse a double-stringified feedHeight JSON.
|
|
693
|
-
* e.g. `"{\"type\":\"fullscreen\"}"` or `"{\"type\":\"percentage\",\"value\":0.8}"`
|
|
694
|
-
*/
|
|
695
|
-
private fun parseFeedHeight(json: String?): FeedHeight {
|
|
696
|
-
if (json.isNullOrEmpty()) return FeedHeight.Fullscreen
|
|
697
|
-
return try {
|
|
698
|
-
val obj = JSONObject(json)
|
|
699
|
-
when (obj.optString("type")) {
|
|
700
|
-
"percentage" -> {
|
|
701
|
-
val value = obj.optDouble("value", 1.0)
|
|
702
|
-
FeedHeight.Percentage(value.toFloat())
|
|
703
|
-
}
|
|
704
|
-
else -> FeedHeight.Fullscreen
|
|
705
|
-
}
|
|
706
|
-
} catch (_: Exception) {
|
|
707
|
-
FeedHeight.Fullscreen
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
/**
|
|
712
|
-
* Parse a double-stringified overlay JSON.
|
|
713
|
-
* - `"\"none\""` -> None
|
|
714
|
-
* - `"{\"type\":\"custom\"}"` -> Custom with bridge overlay factory
|
|
715
|
-
*/
|
|
716
|
-
private fun parseVideoOverlay(json: String?): VideoOverlayMode {
|
|
717
|
-
if (json.isNullOrEmpty()) return VideoOverlayMode.None
|
|
718
|
-
return try {
|
|
719
|
-
// Try parsing — might be a simple string "none" or an object
|
|
720
|
-
val parsed = json.trim()
|
|
721
|
-
|
|
722
|
-
// Strip outer quotes if double-stringified simple string
|
|
723
|
-
if (parsed == "\"none\"" || parsed == "none") {
|
|
724
|
-
return VideoOverlayMode.None
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
// Try as JSON object
|
|
728
|
-
val inner = if (parsed.startsWith("\"") && parsed.endsWith("\"")) {
|
|
729
|
-
// Double-stringified: strip outer quotes and unescape
|
|
730
|
-
JSONObject(parsed.substring(1, parsed.length - 1).replace("\\\"", "\""))
|
|
731
|
-
} else {
|
|
732
|
-
JSONObject(parsed)
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (inner.optString("type") == "custom") {
|
|
736
|
-
// The Fabric view will handle the actual overlay view creation.
|
|
737
|
-
// For the module, we signal custom mode so the SDK allocates an overlay slot.
|
|
738
|
-
VideoOverlayMode.Custom { ShortKitOverlayBridge(reactApplicationContext) }
|
|
739
|
-
} else {
|
|
740
|
-
VideoOverlayMode.None
|
|
741
|
-
}
|
|
742
|
-
} catch (_: Exception) {
|
|
743
|
-
VideoOverlayMode.None
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
/**
|
|
748
|
-
* Parse a double-stringified carousel overlay JSON.
|
|
749
|
-
* - `"\"none\""` -> None
|
|
750
|
-
* - `"{\"type\":\"custom\"}"` -> Custom with bridge overlay factory
|
|
751
|
-
*/
|
|
752
|
-
private fun parseCarouselOverlay(json: String?): CarouselOverlayMode {
|
|
753
|
-
if (json.isNullOrEmpty()) return CarouselOverlayMode.None
|
|
754
|
-
return try {
|
|
755
|
-
val parsed = json.trim()
|
|
756
|
-
|
|
757
|
-
if (parsed == "\"none\"" || parsed == "none") {
|
|
758
|
-
return CarouselOverlayMode.None
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
val inner = if (parsed.startsWith("\"") && parsed.endsWith("\"")) {
|
|
762
|
-
JSONObject(parsed.substring(1, parsed.length - 1).replace("\\\"", "\""))
|
|
763
|
-
} else {
|
|
764
|
-
JSONObject(parsed)
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
if (inner.optString("type") == "custom") {
|
|
768
|
-
CarouselOverlayMode.Custom { ShortKitCarouselOverlayBridge(reactApplicationContext) }
|
|
769
|
-
} else {
|
|
770
|
-
CarouselOverlayMode.None
|
|
771
|
-
}
|
|
772
|
-
} catch (_: Exception) {
|
|
773
|
-
CarouselOverlayMode.None
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
private fun parseCustomFeedItems(json: String): List<CustomFeedItem>? {
|
|
778
|
-
return try {
|
|
779
|
-
val arr = JSONArray(json)
|
|
780
|
-
val result = mutableListOf<CustomFeedItem>()
|
|
781
|
-
for (i in 0 until arr.length()) {
|
|
782
|
-
val obj = arr.getJSONObject(i)
|
|
783
|
-
when (obj.optString("type")) {
|
|
784
|
-
"video" -> {
|
|
785
|
-
val playbackId = obj.optString("playbackId", null) ?: continue
|
|
786
|
-
result.add(CustomFeedItem.Video(playbackId))
|
|
787
|
-
}
|
|
788
|
-
"imageCarousel" -> {
|
|
789
|
-
val itemObj = obj.optJSONObject("item") ?: continue
|
|
790
|
-
val carouselItem = parseImageCarouselItem(itemObj) ?: continue
|
|
791
|
-
result.add(CustomFeedItem.ImageCarousel(carouselItem))
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
result.ifEmpty { null }
|
|
796
|
-
} catch (_: Exception) {
|
|
797
|
-
null
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
private fun parseImageCarouselItem(obj: JSONObject): ImageCarouselItem? {
|
|
802
|
-
val id = obj.optString("id", null) ?: return null
|
|
803
|
-
val imagesArr = obj.optJSONArray("images") ?: return null
|
|
804
|
-
val images = mutableListOf<CarouselImage>()
|
|
805
|
-
for (i in 0 until imagesArr.length()) {
|
|
806
|
-
val imgObj = imagesArr.getJSONObject(i)
|
|
807
|
-
images.add(CarouselImage(
|
|
808
|
-
url = imgObj.getString("url"),
|
|
809
|
-
alt = imgObj.optString("alt", null)
|
|
810
|
-
))
|
|
811
|
-
}
|
|
812
|
-
return ImageCarouselItem(
|
|
813
|
-
id = id,
|
|
814
|
-
images = images,
|
|
815
|
-
autoScrollInterval = if (obj.has("autoScrollInterval")) obj.getDouble("autoScrollInterval") else null,
|
|
816
|
-
caption = obj.optString("caption", null),
|
|
817
|
-
title = obj.optString("title", null),
|
|
818
|
-
description = obj.optString("description", null),
|
|
819
|
-
author = obj.optString("author", null),
|
|
820
|
-
section = obj.optString("section", null),
|
|
821
|
-
articleUrl = obj.optString("articleUrl", null)
|
|
822
|
-
)
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
/**
|
|
826
|
-
* Parse optional custom dimensions JSON string into map.
|
|
827
|
-
*/
|
|
828
|
-
private fun parseCustomDimensions(json: String?): Map<String, String>? {
|
|
829
|
-
if (json.isNullOrEmpty()) return null
|
|
830
|
-
return try {
|
|
831
|
-
val obj = JSONObject(json)
|
|
832
|
-
val map = mutableMapOf<String, String>()
|
|
833
|
-
val keys = obj.keys()
|
|
834
|
-
while (keys.hasNext()) {
|
|
835
|
-
val key = keys.next()
|
|
836
|
-
map[key] = obj.getString(key)
|
|
837
|
-
}
|
|
838
|
-
map
|
|
839
|
-
} catch (_: Exception) {
|
|
840
|
-
null
|
|
841
|
-
}
|
|
259
|
+
private fun bufferOp(op: () -> Unit) {
|
|
260
|
+
if (pendingBridgeOps == null) pendingBridgeOps = mutableListOf()
|
|
261
|
+
pendingBridgeOps!!.add(op)
|
|
842
262
|
}
|
|
843
263
|
}
|