@shortkitsdk/react-native 0.2.11 → 0.2.12

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.
Files changed (32) hide show
  1. package/android/build.gradle.kts +13 -1
  2. package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +115 -55
  3. package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +67 -56
  4. package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +71 -26
  5. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +160 -35
  6. package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +5 -0
  7. package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +43 -10
  8. package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +9 -0
  9. package/ios/ReactOverlayHost.swift +13 -27
  10. package/ios/ShortKitBridge.swift +36 -2
  11. package/ios/ShortKitFeedView.swift +24 -3
  12. package/ios/ShortKitModule.mm +4 -1
  13. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +720 -144
  14. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +19 -5
  15. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
  16. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +19 -5
  17. package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
  18. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +720 -144
  19. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +19 -5
  20. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftdoc +0 -0
  21. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.swiftinterface +19 -5
  22. package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
  23. package/package.json +1 -1
  24. package/src/ShortKitContext.ts +2 -1
  25. package/src/ShortKitFeed.tsx +14 -0
  26. package/src/ShortKitOverlaySurface.tsx +153 -45
  27. package/src/ShortKitPlayer.tsx +25 -3
  28. package/src/ShortKitProvider.tsx +4 -2
  29. package/src/index.ts +4 -0
  30. package/src/serialization.ts +1 -0
  31. package/src/specs/NativeShortKitModule.ts +18 -1
  32. package/src/types.ts +6 -0
@@ -31,8 +31,20 @@ android {
31
31
 
32
32
  dependencies {
33
33
  implementation("com.facebook.react:react-android")
34
- implementation("dev.shortkit:shortkit:0.2.6")
34
+ // When Reco's settings.gradle has the composite build, Gradle substitutes
35
+ // this with the live source from android_sdk/shortkit automatically.
36
+ // implementation("dev.shortkit:shortkit:0.2.11")
37
+ implementation(files("/Users/michaelseleman/shortkit/android_sdk/shortkit/build/outputs/aar/shortkit-release.aar"))
38
+ // Transitive deps needed when using local AAR (Maven artifact bundles these automatically)
39
+ implementation("androidx.media3:media3-exoplayer:1.5.1")
40
+ implementation("androidx.media3:media3-exoplayer-hls:1.5.1")
41
+ implementation("androidx.media3:media3-datasource:1.5.1")
42
+ implementation("androidx.media3:media3-ui:1.5.1")
43
+ implementation("androidx.recyclerview:recyclerview:1.4.0")
35
44
  implementation("androidx.viewpager2:viewpager2:1.1.0")
45
+ implementation("androidx.fragment:fragment-ktx:1.8.5")
46
+ implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
47
+ implementation("com.squareup.okhttp3:okhttp:4.12.0")
36
48
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
37
49
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
38
50
  }
@@ -11,6 +11,12 @@ import com.facebook.react.runtime.ReactSurfaceImpl
11
11
  import com.shortkit.sdk.model.CarouselImage
12
12
  import com.shortkit.sdk.model.ImageCarouselItem
13
13
  import com.shortkit.sdk.overlay.CarouselOverlay
14
+ import kotlinx.coroutines.CoroutineScope
15
+ import kotlinx.coroutines.Dispatchers
16
+ import kotlinx.coroutines.Job
17
+ import kotlinx.coroutines.SupervisorJob
18
+ import kotlinx.coroutines.launch
19
+ import kotlinx.coroutines.withContext
14
20
  import kotlinx.serialization.encodeToString
15
21
  import kotlinx.serialization.json.Json
16
22
  import java.io.File
@@ -45,6 +51,10 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
45
51
 
46
52
  private var surface: ReactSurface? = null
47
53
  private var pendingProps: Bundle? = null
54
+ private var isInLayoutPass: Boolean = false
55
+ private val ioScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
56
+ private var pendingWriteJob: Job? = null
57
+ private var configureGeneration: Int = 0
48
58
 
49
59
  // ------------------------------------------------------------------
50
60
  // Fabric layout workaround
@@ -52,15 +62,19 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
52
62
 
53
63
  private val layoutHandler = android.os.Handler(android.os.Looper.getMainLooper())
54
64
 
65
+ private val layoutRunnable = Runnable {
66
+ measure(
67
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
68
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
69
+ )
70
+ layout(left, top, right, bottom)
71
+ }
72
+
55
73
  override fun requestLayout() {
56
74
  super.requestLayout()
57
- @Suppress("UNNECESSARY_SAFE_CALL")
58
- layoutHandler?.post {
59
- measure(
60
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
61
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
62
- )
63
- layout(left, top, right, bottom)
75
+ if (!isInLayoutPass) {
76
+ @Suppress("UNNECESSARY_SAFE_CALL")
77
+ layoutHandler?.post(layoutRunnable)
64
78
  }
65
79
  }
66
80
 
@@ -109,65 +123,104 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
109
123
  // ------------------------------------------------------------------
110
124
 
111
125
  override fun configure(item: ImageCarouselItem) {
112
- // Replace remote URLs with local file:// URLs for any natively-cached images.
113
- val modifiedItem = cachedImage?.let { lookup ->
114
- val localImages = item.images.map { image ->
115
- val bitmap = lookup(image.url)
116
- if (bitmap != null) {
117
- val localUrl = writeTempImage(bitmap, image.url)
118
- if (localUrl != null) {
119
- CarouselImage(url = localUrl, alt = image.alt)
120
- } else {
121
- image
122
- }
123
- } else {
124
- image
125
- }
126
+ // Increment generation any in-flight coroutine from a previous
127
+ // configure() call will see a stale generation and bail out.
128
+ val gen = ++configureGeneration
129
+
130
+ // Cancel any in-flight write job from a previous configure() call.
131
+ pendingWriteJob?.cancel()
132
+
133
+ // Fast path: check for pre-existing temp files synchronously (just
134
+ // file.exists(), no I/O). This avoids the flicker of remote→local
135
+ // URL swap for images that were cached in a previous session.
136
+ val lookup = cachedImage
137
+ val fastItem = if (lookup != null) {
138
+ val fastImages = item.images.map { image ->
139
+ val hash = image.url.hashCode()
140
+ val fileName = "sk-carousel-${kotlin.math.abs(hash)}.jpg"
141
+ val file = File(context.cacheDir, fileName)
142
+ if (file.exists()) CarouselImage(url = "file://${file.absolutePath}", alt = image.alt)
143
+ else image
126
144
  }
127
145
  ImageCarouselItem(
128
146
  id = item.id,
129
- images = localImages,
147
+ images = fastImages,
130
148
  caption = item.caption,
131
149
  title = item.title,
132
150
  description = item.description,
133
151
  author = item.author,
134
152
  section = item.section,
135
- articleUrl = item.articleUrl
153
+ articleUrl = item.articleUrl,
136
154
  )
137
- } ?: item
155
+ } else item
156
+
157
+ val json = Json.encodeToString(fastItem)
158
+ val bundle = Bundle().apply { putString("item", json) }
159
+ applySurfaceProps(bundle)
138
160
 
139
- val json = Json.encodeToString(modifiedItem)
140
- val bundle = Bundle().apply {
141
- putString("item", json)
161
+ // Pre-size the surface view NOW — before the overlay is attached to a cell.
162
+ val parentView = parent as? android.view.View
163
+ val w = if (width > 0) width
164
+ else if (parentView != null && parentView.width > 0) parentView.width
165
+ else context.resources.displayMetrics.widthPixels
166
+ val h = if (height > 0) height
167
+ else if (parentView != null && parentView.height > 0) parentView.height
168
+ else context.resources.displayMetrics.heightPixels
169
+ measureAndLayoutSurfaceView(w, h)
170
+
171
+ // Background: write any newly-cached images to temp files.
172
+ // Only runs if there are remote URLs remaining (not all were fast-pathed).
173
+ if (lookup == null) return
174
+ val hasRemoteUrls = fastItem.images.any { !it.url.startsWith("file://") }
175
+ if (!hasRemoteUrls) return
176
+
177
+ pendingWriteJob = ioScope.launch {
178
+ val modifiedItem = withContext(Dispatchers.IO) {
179
+ val localImages = item.images.map { image ->
180
+ val bitmap = lookup(image.url)
181
+ if (bitmap != null) {
182
+ val localUrl = writeTempImage(bitmap, image.url)
183
+ if (localUrl != null) CarouselImage(url = localUrl, alt = image.alt)
184
+ else image
185
+ } else {
186
+ image
187
+ }
188
+ }
189
+ ImageCarouselItem(
190
+ id = item.id,
191
+ images = localImages,
192
+ caption = item.caption,
193
+ title = item.title,
194
+ description = item.description,
195
+ author = item.author,
196
+ section = item.section,
197
+ articleUrl = item.articleUrl,
198
+ )
199
+ }
200
+
201
+ // Back on Main — only update if this configure() is still current
202
+ if (gen != configureGeneration) return@launch
203
+ val hasLocalImages = modifiedItem.images.any { it.url.startsWith("file://") }
204
+ if (hasLocalImages) {
205
+ val localJson = Json.encodeToString(modifiedItem)
206
+ val localBundle = Bundle().apply { putString("item", localJson) }
207
+ applySurfaceProps(localBundle)
208
+ }
142
209
  }
210
+ }
143
211
 
212
+ /** Apply props to the surface, handling all lifecycle states. */
213
+ private fun applySurfaceProps(bundle: Bundle) {
144
214
  val s = surface
145
215
  if (s != null && s.isRunning) {
146
- // Surface already running (cell reuse) — update props in place
147
216
  (s as? ReactSurfaceImpl)?.updateInitProps(bundle)
148
217
  } else if (s != null && !s.isRunning) {
149
- // Surface pre-created by prepareSurface() but not started yet.
150
- // Set props THEN start — the JS component mounts once with correct data.
151
218
  (s as? ReactSurfaceImpl)?.updateInitProps(bundle)
152
219
  s.start()
153
220
  } else {
154
- // No surface at all — create with item as initial props
155
221
  pendingProps = bundle
156
222
  createSurfaceIfNeeded()
157
223
  }
158
-
159
- // Pre-size the surface view NOW — before the overlay is attached to a cell.
160
- // This eliminates the black flash: when the cell displays the overlay, the
161
- // surface view already has correct dimensions and rendered content.
162
- // Use parent dimensions if attached, otherwise use display dimensions.
163
- val parentView = parent as? android.view.View
164
- val w = if (width > 0) width
165
- else if (parentView != null && parentView.width > 0) parentView.width
166
- else context.resources.displayMetrics.widthPixels
167
- val h = if (height > 0) height
168
- else if (parentView != null && parentView.height > 0) parentView.height
169
- else context.resources.displayMetrics.heightPixels
170
- measureAndLayoutSurfaceView(w, h)
171
224
  }
172
225
 
173
226
  /**
@@ -225,18 +278,23 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
225
278
  }
226
279
 
227
280
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
228
- super.onLayout(changed, left, top, right, bottom)
229
- val w = right - left
230
- val h = bottom - top
231
- if (w > 0 && h > 0) {
232
- measureAndLayoutSurfaceView(w, h)
233
- } else {
234
- val parentView = parent as? android.view.View
235
- val pw = parentView?.width ?: 0
236
- val ph = parentView?.height ?: 0
237
- if (pw > 0 && ph > 0) {
238
- measureAndLayoutSurfaceView(pw, ph)
281
+ isInLayoutPass = true
282
+ try {
283
+ super.onLayout(changed, left, top, right, bottom)
284
+ val w = right - left
285
+ val h = bottom - top
286
+ if (w > 0 && h > 0) {
287
+ measureAndLayoutSurfaceView(w, h)
288
+ } else {
289
+ val parentView = parent as? android.view.View
290
+ val pw = parentView?.width ?: 0
291
+ val ph = parentView?.height ?: 0
292
+ if (pw > 0 && ph > 0) {
293
+ measureAndLayoutSurfaceView(pw, ph)
294
+ }
239
295
  }
296
+ } finally {
297
+ isInLayoutPass = false
240
298
  }
241
299
  }
242
300
 
@@ -312,6 +370,8 @@ class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), Carouse
312
370
 
313
371
  override fun onDetachedFromWindow() {
314
372
  super.onDetachedFromWindow()
373
+ pendingWriteJob?.cancel()
374
+ pendingWriteJob = null
315
375
  if (surface?.isRunning == true) {
316
376
  surface?.stop()
317
377
  }
@@ -60,19 +60,24 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
60
60
 
61
61
  private val layoutHandler = Handler(Looper.getMainLooper())
62
62
 
63
+ /** Reusable runnable to avoid lambda allocation on every requestLayout. */
64
+ private val layoutRunnable = Runnable {
65
+ measure(
66
+ MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
67
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
68
+ )
69
+ layout(left, top, right, bottom)
70
+ }
71
+
63
72
  /**
64
73
  * Fabric may suppress layout propagation to native child views inside
65
74
  * the SDK's RecyclerView cells. Override to force a manual layout pass.
66
75
  */
67
76
  override fun requestLayout() {
68
77
  super.requestLayout()
69
- @Suppress("UNNECESSARY_SAFE_CALL")
70
- layoutHandler?.post {
71
- measure(
72
- MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
73
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
74
- )
75
- layout(left, top, right, bottom)
78
+ if (!isInLayoutPass) {
79
+ @Suppress("UNNECESSARY_SAFE_CALL")
80
+ layoutHandler?.post(layoutRunnable)
76
81
  }
77
82
  }
78
83
 
@@ -103,6 +108,7 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
103
108
  private var timeCoalesceRunnable: Runnable? = null
104
109
 
105
110
  private var flowScope: CoroutineScope? = null
111
+ private var isInLayoutPass: Boolean = false
106
112
 
107
113
  // ------------------------------------------------------------------
108
114
  // Init
@@ -147,14 +153,17 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
147
153
  resubscribeToPlayer(player)
148
154
  }
149
155
 
156
+ /** Whether the surface has been started at least once. */
157
+ private var surfaceHasStarted: Boolean = false
158
+
150
159
  override fun configure(item: ContentItem) {
160
+ val isSameItem = item.id == currentItem?.id
151
161
  currentItem = item
152
162
  isActive = false
153
163
  timeDirty = false
154
164
  stopTimeCoalescing()
155
165
 
156
166
  // Reset ALL cached state so recycled cells don't flash stale values.
157
- // Mirrors iOS ReactOverlayHost.configure(with:).
158
167
  cachedCurrentTime = 0.0
159
168
  cachedDuration = 0.0
160
169
  cachedBuffered = 0.0
@@ -164,12 +173,25 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
164
173
 
165
174
  if (surface == null) {
166
175
  createSurfaceIfNeeded()
167
- } else {
176
+ surfaceHasStarted = true
177
+ } else if (!surfaceHasStarted) {
168
178
  (surface as? ReactSurfaceImpl)?.updateInitProps(buildInitialPropsBundle())
179
+ surfaceHasStarted = true
180
+ } else if (isSameItem) {
181
+ // Same item = deactivation only. The overlay already has this
182
+ // item's data from a previous swipe. Just deactivate (isActive
183
+ // set to false above, timer stopped). No event emission — avoids
184
+ // broadcasting to all 7 surfaces for no visual change.
185
+ } else {
186
+ // Different item — send via event for React tree diff (not
187
+ // updateInitProps which causes full Fabric remount).
188
+ val params = Arguments.createMap().apply {
189
+ putString("surfaceId", surfaceId)
190
+ putString("item", ShortKitBridge.serializeContentItemToJSON(item))
191
+ }
192
+ ShortKitBridge.shared?.emitEvent("onOverlayItemChanged", params)
169
193
  }
170
-
171
- // Pre-size the surface view NOW — before the overlay is attached to a cell.
172
- // Eliminates the black flash on cell display.
194
+ // Pre-size the surface view
173
195
  val parentView = parent as? android.view.View
174
196
  val w = if (width > 0) width
175
197
  else if (parentView != null && parentView.width > 0) parentView.width
@@ -184,9 +206,7 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
184
206
  isActive = true
185
207
  startTimeCoalescing()
186
208
 
187
- // Defer the event burst to the next tick. The JS surface needs time to
188
- // mount and establish event subscriptions (useEffect runs after render).
189
- // Mirrors iOS: DispatchQueue.main.async { self.emitFullState() }
209
+ // Defer the event burst to the next tick.
190
210
  handler.post {
191
211
  if (isActive) {
192
212
  emitFullState()
@@ -195,13 +215,16 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
195
215
  }
196
216
 
197
217
  override fun fadeOutForTransition() {
198
- val item = currentItem ?: return
199
- ShortKitBridge.shared?.emitOverlayEvent("onOverlayFadeOut", item)
218
+ // No-op. The JS overlay component handles fade via feedScrollPhase
219
+ // prop (dragging/settled), not these events. iOS ReactOverlayHost
220
+ // doesn't implement these methods at all. The previous implementation
221
+ // serialized the full ContentItem to JSON and emitted across the
222
+ // bridge to a listener that doesn't exist — 2-15ms of pure waste
223
+ // on every swipe.
200
224
  }
201
225
 
202
226
  override fun restoreFromTransition() {
203
- val item = currentItem ?: return
204
- ShortKitBridge.shared?.emitOverlayEvent("onOverlayRestore", item)
227
+ // No-op. See fadeOutForTransition comment.
205
228
  }
206
229
 
207
230
  // ------------------------------------------------------------------
@@ -257,19 +280,24 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
257
280
  * calling setMinimumSize/setMaximumSize in ReactOverlayHost.swift.
258
281
  */
259
282
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
260
- super.onLayout(changed, left, top, right, bottom)
261
- val w = right - left
262
- val h = bottom - top
263
- if (w > 0 && h > 0) {
264
- measureAndLayoutSurfaceView(w, h)
265
- } else {
266
- // Host still 0x0 — try parent dimensions
267
- val parentView = parent as? android.view.View
268
- val pw = parentView?.width ?: 0
269
- val ph = parentView?.height ?: 0
270
- if (pw > 0 && ph > 0) {
271
- measureAndLayoutSurfaceView(pw, ph)
283
+ isInLayoutPass = true
284
+ try {
285
+ super.onLayout(changed, left, top, right, bottom)
286
+ val w = right - left
287
+ val h = bottom - top
288
+ if (w > 0 && h > 0) {
289
+ measureAndLayoutSurfaceView(w, h)
290
+ } else {
291
+ // Host still 0x0 try parent dimensions
292
+ val parentView = parent as? android.view.View
293
+ val pw = parentView?.width ?: 0
294
+ val ph = parentView?.height ?: 0
295
+ if (pw > 0 && ph > 0) {
296
+ measureAndLayoutSurfaceView(pw, ph)
297
+ }
272
298
  }
299
+ } finally {
300
+ isInLayoutPass = false
273
301
  }
274
302
  }
275
303
 
@@ -289,7 +317,9 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
289
317
  private fun createSurfaceIfNeeded() {
290
318
  if (surface != null) return
291
319
 
292
- val reactHost = (context.applicationContext as? ReactApplication)?.reactHost
320
+ val appContext = context.applicationContext
321
+ val reactHost = (appContext as? ReactApplication)?.reactHost
322
+
293
323
  if (reactHost == null) {
294
324
  android.util.Log.e(TAG, "[$surfaceId] createSurface FAILED: reactHost is null")
295
325
  return
@@ -310,10 +340,10 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
310
340
  } else {
311
341
  android.util.Log.e(TAG, "[$surfaceId] createSurface: surfaceView is NULL")
312
342
  }
343
+
313
344
  newSurface.start()
314
345
 
315
- // The host may still be 0x0 at this point (RecyclerView hasn't laid
316
- // out the cell yet). Use the parent's dimensions if available.
346
+ // The host may still be 0x0 at this point. Use the parent's dimensions.
317
347
  val parentView = parent as? android.view.View
318
348
  val w = if (width > 0) width else parentView?.width ?: 0
319
349
  val h = if (height > 0) height else parentView?.height ?: 0
@@ -519,41 +549,22 @@ class ReactOverlayHost(context: Context) : FrameLayout(context), FeedOverlay {
519
549
  private fun emitFullState() {
520
550
  val bridge = ShortKitBridge.shared ?: return
521
551
 
522
- bridge.emitEvent("onOverlayActiveChanged", Arguments.createMap().apply {
552
+ val params = Arguments.createMap().apply {
523
553
  putString("surfaceId", surfaceId)
524
554
  putBoolean("isActive", true)
525
- })
526
- bridge.emitEvent("onOverlayPlayerStateChanged", Arguments.createMap().apply {
527
- putString("surfaceId", surfaceId)
528
555
  putString("playerState", cachedPlayerState)
529
- })
530
- bridge.emitEvent("onOverlayMutedChanged", Arguments.createMap().apply {
531
- putString("surfaceId", surfaceId)
532
556
  putBoolean("isMuted", cachedIsMuted)
533
- })
534
- bridge.emitEvent("onOverlayPlaybackRateChanged", Arguments.createMap().apply {
535
- putString("surfaceId", surfaceId)
536
557
  putDouble("playbackRate", cachedPlaybackRate)
537
- })
538
- bridge.emitEvent("onOverlayCaptionsEnabledChanged", Arguments.createMap().apply {
539
- putString("surfaceId", surfaceId)
540
558
  putBoolean("captionsEnabled", cachedCaptionsEnabled)
541
- })
542
-
543
- bridge.emitEvent("onOverlayActiveCueChanged", Arguments.createMap().apply {
544
- putString("surfaceId", surfaceId)
545
559
  val cueJson = cachedActiveCue?.toString()
546
560
  if (cueJson != null) {
547
561
  putString("activeCue", cueJson)
548
562
  } else {
549
563
  putNull("activeCue")
550
564
  }
551
- })
552
-
553
- bridge.emitEvent("onOverlayFeedScrollPhaseChanged", Arguments.createMap().apply {
554
- putString("surfaceId", surfaceId)
555
565
  cachedFeedScrollPhase?.let { putString("feedScrollPhase", it) }
556
566
  ?: putNull("feedScrollPhase")
557
- })
567
+ }
568
+ bridge.emitEvent("onOverlayFullState", params)
558
569
  }
559
570
  }