@shortkitsdk/react-native 0.2.35 → 0.2.37
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 +252 -27
- 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 +14 -11
- 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
|
@@ -4,6 +4,7 @@ import android.content.Context
|
|
|
4
4
|
import android.os.Bundle
|
|
5
5
|
import android.os.Handler
|
|
6
6
|
import android.os.Looper
|
|
7
|
+
import android.view.MotionEvent
|
|
7
8
|
import android.widget.FrameLayout
|
|
8
9
|
import com.facebook.react.ReactApplication
|
|
9
10
|
import com.facebook.react.bridge.Arguments
|
|
@@ -11,6 +12,7 @@ import com.facebook.react.interfaces.fabric.ReactSurface
|
|
|
11
12
|
import com.facebook.react.runtime.ReactSurfaceImpl
|
|
12
13
|
import com.shortkit.sdk.ShortKitPlayer
|
|
13
14
|
import com.shortkit.sdk.model.ContentItem
|
|
15
|
+
import com.shortkit.sdk.model.FeedScrollPhase
|
|
14
16
|
import com.shortkit.sdk.model.PlayerState
|
|
15
17
|
import com.shortkit.sdk.model.VideoCarouselItem
|
|
16
18
|
import com.shortkit.sdk.overlay.VideoCarouselOverlay
|
|
@@ -40,9 +42,17 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
40
42
|
const val TAG = "SK:VidCarouselHost"
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
// Touch handling: this view sits
|
|
44
|
-
//
|
|
45
|
-
//
|
|
45
|
+
// Touch handling: this view sits ABOVE the ViewPager2 in z-order
|
|
46
|
+
// (overlay_container is the last child of cell_video_carousel.xml's
|
|
47
|
+
// root FrameLayout). RN's pointerEvents="box-none" lets the React
|
|
48
|
+
// tree claim DOWN only for interactive children; the inner pager
|
|
49
|
+
// sees DOWN for empty-area touches via standard z-order dispatch.
|
|
50
|
+
//
|
|
51
|
+
// For touches that DO land on interactive overlay children — chiefly
|
|
52
|
+
// the scrubber drag — we additionally call requestDisallowIntercept
|
|
53
|
+
// on the parent chain so the inner carousel pager and the outer feed
|
|
54
|
+
// pager don't intercept on subsequent MOVE events. Mirrors the same
|
|
55
|
+
// idiom used in ReactOverlayHost for the regular feed overlay.
|
|
46
56
|
|
|
47
57
|
// ------------------------------------------------------------------
|
|
48
58
|
// Configuration
|
|
@@ -65,6 +75,62 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
65
75
|
/** Cached carouselItem JSON — updateInitProps replaces all props, doesn't merge. */
|
|
66
76
|
private var carouselItemJSON: String? = null
|
|
67
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Currently configured carousel item — used to detect item transitions so
|
|
80
|
+
* we can emit an event instead of triggering a full Fabric remount, and
|
|
81
|
+
* (SHO-15) to surface-filter onCarouselActiveVideoCompleted events so
|
|
82
|
+
* only the host bound to the just-completed video's carousel emits.
|
|
83
|
+
*/
|
|
84
|
+
internal var currentCarouselItem: VideoCarouselItem? = null
|
|
85
|
+
private set
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Whether initial props have been pushed at least once. First mount goes
|
|
89
|
+
* through applySurfaceProps + emit (matches iOS asymmetry); subsequent
|
|
90
|
+
* different-item rebinds emit only.
|
|
91
|
+
*/
|
|
92
|
+
internal var hasPushedInitialProps: Boolean = false
|
|
93
|
+
private set
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Tracks active drag phase. While true, per-frame player-state emissions
|
|
97
|
+
* (playerState, isMuted, time) are suppressed; emitFullState() re-syncs
|
|
98
|
+
* cached values on settle.
|
|
99
|
+
*/
|
|
100
|
+
internal var isDragging: Boolean = false
|
|
101
|
+
private set
|
|
102
|
+
|
|
103
|
+
/** Test-only setter for [isDragging]. */
|
|
104
|
+
internal var isDraggingForTest: Boolean
|
|
105
|
+
get() = isDragging
|
|
106
|
+
set(value) { isDragging = value }
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Test seam for emit-event interception. Production uses the default;
|
|
110
|
+
* tests overwrite to capture emitted (name, params) pairs.
|
|
111
|
+
*/
|
|
112
|
+
internal var emitEvent: (String, com.facebook.react.bridge.WritableMap) -> Unit =
|
|
113
|
+
{ name, params -> ShortKitBridge.shared?.emitEvent(name, params) }
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Test seam for WritableMap creation. Production uses Arguments.createMap()
|
|
117
|
+
* which requires JNI; tests overwrite with JavaOnlyMap() to avoid Robolectric
|
|
118
|
+
* NativeLoader issues. Same pattern as ReactCarouselOverlayHost.
|
|
119
|
+
*/
|
|
120
|
+
internal var createMap: () -> com.facebook.react.bridge.WritableMap =
|
|
121
|
+
{ com.facebook.react.bridge.Arguments.createMap() }
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Test seam for ReactSurface creation. Production resolves from ReactHost.
|
|
125
|
+
* Tests inject a stub so the surface is non-null after applySurfaceProps()
|
|
126
|
+
* and hasPushedInitialProps can flip. Same pattern as ReactCarouselOverlayHost.
|
|
127
|
+
*/
|
|
128
|
+
internal var createSurface: (moduleName: String, initialProps: Bundle?) -> ReactSurface? =
|
|
129
|
+
{ moduleName, initialProps ->
|
|
130
|
+
(context.applicationContext as? ReactApplication)?.reactHost
|
|
131
|
+
?.createSurface(context, moduleName, initialProps)
|
|
132
|
+
}
|
|
133
|
+
|
|
68
134
|
// Player state
|
|
69
135
|
private var player: ShortKitPlayer? = null
|
|
70
136
|
private var isActive: Boolean = false
|
|
@@ -76,6 +142,17 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
76
142
|
private var timeDirty: Boolean = false
|
|
77
143
|
private val handler = Handler(Looper.getMainLooper())
|
|
78
144
|
private var timeCoalesceRunnable: Runnable? = null
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Bottom strip in which a DOWN signals "interactive overlay drag pending"
|
|
148
|
+
* (chiefly the scrubber). Width = scrubberTouchArea height (40dp) plus the
|
|
149
|
+
* sample's BOTTOM_SAFE_AREA (100dp) = 140dp. Matches the JS scrubberTouchArea
|
|
150
|
+
* geometry in NewsVideoCarouselOverlay.tsx; future overlays with non-default
|
|
151
|
+
* BOTTOM_SAFE_AREA will need a prop to communicate this down.
|
|
152
|
+
*/
|
|
153
|
+
private val interactiveBottomStripPx: Int
|
|
154
|
+
get() = (140 * resources.displayMetrics.density).toInt()
|
|
155
|
+
|
|
79
156
|
private var flowScope: CoroutineScope? = null
|
|
80
157
|
|
|
81
158
|
// ------------------------------------------------------------------
|
|
@@ -129,7 +206,11 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
129
206
|
// ------------------------------------------------------------------
|
|
130
207
|
|
|
131
208
|
override fun configure(item: VideoCarouselItem) {
|
|
209
|
+
val isSameItem = item.id == currentCarouselItem?.id
|
|
210
|
+
currentCarouselItem = item
|
|
211
|
+
|
|
132
212
|
isActive = false
|
|
213
|
+
isDragging = false
|
|
133
214
|
timeDirty = false
|
|
134
215
|
stopTimeCoalescing()
|
|
135
216
|
cachedCurrentTime = 0.0
|
|
@@ -146,20 +227,28 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
146
227
|
|
|
147
228
|
val json = Json.encodeToString(item)
|
|
148
229
|
carouselItemJSON = json
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
230
|
+
|
|
231
|
+
if (!hasPushedInitialProps) {
|
|
232
|
+
// First mount: BOTH applySurfaceProps AND emit. Matches iOS asymmetry
|
|
233
|
+
// at ReactVideoCarouselOverlayHost.swift:127-138.
|
|
234
|
+
applySurfaceProps(buildInitialPropsBundle(item, json))
|
|
235
|
+
// Only flip the flag if surface was actually created. If reactHost was
|
|
236
|
+
// null (createSurface seam returned null), surface remains null and
|
|
237
|
+
// pendingProps is parked — stay in "first mount" mode so the next
|
|
238
|
+
// configure() retries. Mirrors iOS pattern.
|
|
239
|
+
if (surface != null) {
|
|
240
|
+
hasPushedInitialProps = true
|
|
158
241
|
}
|
|
242
|
+
// Emit fires regardless of surface state — external subscribers
|
|
243
|
+
// should see the item update even when the in-surface listener
|
|
244
|
+
// isn't yet wired. iOS does the same at :135-137.
|
|
245
|
+
emitItemChanged(item, json)
|
|
246
|
+
} else if (!isSameItem) {
|
|
247
|
+
emitItemChanged(item, json)
|
|
159
248
|
}
|
|
160
|
-
|
|
249
|
+
// else: same item rebind — no emit, no Fabric prop push.
|
|
161
250
|
|
|
162
|
-
// Pre-size the surface view
|
|
251
|
+
// Pre-size the surface view (existing logic, unchanged).
|
|
163
252
|
val parentView = parent as? android.view.View
|
|
164
253
|
val w = if (width > 0) width
|
|
165
254
|
else if (parentView != null && parentView.width > 0) parentView.width
|
|
@@ -170,6 +259,58 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
170
259
|
measureAndLayoutSurfaceView(w, h)
|
|
171
260
|
}
|
|
172
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Build the initial-properties bundle for first-mount setProperties path.
|
|
264
|
+
* Mirrors iOS buildInitialProps(item:json:) at
|
|
265
|
+
* ReactVideoCarouselOverlayHost.swift:143-158.
|
|
266
|
+
*
|
|
267
|
+
* Note: when videos is empty, activeVideo + activeVideoIndex keys are
|
|
268
|
+
* OMITTED from the bundle (matches iOS first-mount behavior). The
|
|
269
|
+
* emit path uses a separate convention — it emits putNull("activeVideo")
|
|
270
|
+
* to match iOS NSNull(). See emitItemChanged() below.
|
|
271
|
+
*/
|
|
272
|
+
private fun buildInitialPropsBundle(item: VideoCarouselItem, json: String): Bundle =
|
|
273
|
+
Bundle().apply {
|
|
274
|
+
putString("surfaceId", surfaceId)
|
|
275
|
+
putString("carouselItem", json)
|
|
276
|
+
putBoolean("isActive", false)
|
|
277
|
+
putString("playerState", "idle")
|
|
278
|
+
putBoolean("isMuted", cachedIsMuted)
|
|
279
|
+
if (item.videos.isNotEmpty()) {
|
|
280
|
+
putString("activeVideo", ShortKitBridge.serializeContentItemToJSON(item.videos.first()))
|
|
281
|
+
putInt("activeVideoIndex", 0)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Emit onVideoCarouselItemChanged for React diff on cell reuse.
|
|
287
|
+
* Mirrors iOS emitItemChanged(item:json:) at
|
|
288
|
+
* ReactVideoCarouselOverlayHost.swift:162-183.
|
|
289
|
+
*
|
|
290
|
+
* Empty-videos: uses putNull("activeVideo") — NOT key omission. This
|
|
291
|
+
* matches iOS NSNull() and avoids the empty-string-falsy-check bug
|
|
292
|
+
* documented at iOS :175-180.
|
|
293
|
+
*/
|
|
294
|
+
private fun emitItemChanged(item: VideoCarouselItem, json: String) {
|
|
295
|
+
val params = createMap().apply {
|
|
296
|
+
putString("surfaceId", surfaceId)
|
|
297
|
+
putString("carouselItem", json)
|
|
298
|
+
putBoolean("isActive", false)
|
|
299
|
+
putString("playerState", "idle")
|
|
300
|
+
putBoolean("isMuted", cachedIsMuted)
|
|
301
|
+
putInt("activeVideoIndex", 0)
|
|
302
|
+
if (item.videos.isNotEmpty()) {
|
|
303
|
+
putString(
|
|
304
|
+
"activeVideo",
|
|
305
|
+
ShortKitBridge.serializeContentItemToJSON(item.videos.first()),
|
|
306
|
+
)
|
|
307
|
+
} else {
|
|
308
|
+
putNull("activeVideo")
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
emitEvent("onVideoCarouselItemChanged", params)
|
|
312
|
+
}
|
|
313
|
+
|
|
173
314
|
override fun updateActiveVideo(index: Int, item: ContentItem) {
|
|
174
315
|
// Only emit when active — matches ReactOverlayHost pattern.
|
|
175
316
|
// During initial setup, configure() sets the first video via surface props.
|
|
@@ -221,12 +362,12 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
221
362
|
scope.launch {
|
|
222
363
|
player.playerState.collect { state ->
|
|
223
364
|
cachedPlayerState = ShortKitBridge.playerStateString(state)
|
|
224
|
-
if (isActive) {
|
|
225
|
-
val params =
|
|
365
|
+
if (isActive && !isDragging) {
|
|
366
|
+
val params = createMap().apply {
|
|
226
367
|
putString("surfaceId", surfaceId)
|
|
227
368
|
putString("playerState", cachedPlayerState)
|
|
228
369
|
}
|
|
229
|
-
|
|
370
|
+
emitEvent("onOverlayPlayerStateChanged", params)
|
|
230
371
|
}
|
|
231
372
|
}
|
|
232
373
|
}
|
|
@@ -234,12 +375,12 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
234
375
|
scope.launch {
|
|
235
376
|
player.isMuted.collect { muted ->
|
|
236
377
|
cachedIsMuted = muted
|
|
237
|
-
if (isActive) {
|
|
238
|
-
val params =
|
|
378
|
+
if (isActive && !isDragging) {
|
|
379
|
+
val params = createMap().apply {
|
|
239
380
|
putString("surfaceId", surfaceId)
|
|
240
381
|
putBoolean("isMuted", cachedIsMuted)
|
|
241
382
|
}
|
|
242
|
-
|
|
383
|
+
emitEvent("onOverlayMutedChanged", params)
|
|
243
384
|
}
|
|
244
385
|
}
|
|
245
386
|
}
|
|
@@ -249,11 +390,56 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
249
390
|
cachedCurrentTime = time.currentMs / 1000.0
|
|
250
391
|
cachedDuration = time.durationMs / 1000.0
|
|
251
392
|
cachedBuffered = time.bufferedMs / 1000.0
|
|
252
|
-
if (isActive) {
|
|
393
|
+
if (isActive && !isDragging) {
|
|
253
394
|
timeDirty = true
|
|
254
395
|
}
|
|
255
396
|
}
|
|
256
397
|
}
|
|
398
|
+
|
|
399
|
+
scope.launch {
|
|
400
|
+
player.feedScrollPhase.collect { phase ->
|
|
401
|
+
when (phase) {
|
|
402
|
+
is FeedScrollPhase.Dragging -> {
|
|
403
|
+
isDragging = true
|
|
404
|
+
stopTimeCoalescing()
|
|
405
|
+
}
|
|
406
|
+
is FeedScrollPhase.Settled -> {
|
|
407
|
+
val wasDragging = isDragging
|
|
408
|
+
isDragging = false
|
|
409
|
+
if (isActive) {
|
|
410
|
+
startTimeCoalescing()
|
|
411
|
+
if (wasDragging) emitFullState()
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// SHO-15: subscribe to ShortKitCarousel.activeVideoCompleted and emit
|
|
419
|
+
// onCarouselActiveVideoCompleted to JS. Surface-filtering: only the
|
|
420
|
+
// currently-active host (one per active carousel cell) emits, AND the
|
|
421
|
+
// completed item's id must belong to this host's bound carousel.
|
|
422
|
+
scope.launch {
|
|
423
|
+
val carousel = com.shortkit.sdk.ShortKit.activeInstance()?.carousel ?: return@launch
|
|
424
|
+
carousel.activeVideoCompleted.collect { event ->
|
|
425
|
+
if (!isActive) return@collect
|
|
426
|
+
val ourCarousel = currentCarouselItem ?: return@collect
|
|
427
|
+
val belongs = ourCarousel.videos.any { it.id == event.contentItem.id }
|
|
428
|
+
if (!belongs) return@collect
|
|
429
|
+
|
|
430
|
+
val feedId = ShortKitBridge.shared?.activeSurfaceFeedId() ?: ""
|
|
431
|
+
val params = Arguments.createMap().apply {
|
|
432
|
+
putString("feedId", feedId)
|
|
433
|
+
putString("surfaceId", surfaceId)
|
|
434
|
+
putString("contentItem", ShortKitBridge.serializeContentItemToJSON(event.contentItem))
|
|
435
|
+
putInt("indexInCarousel", event.indexInCarousel)
|
|
436
|
+
putString("carouselItem", ShortKitBridge.serializeVideoCarouselItemToJSON(event.carouselItem))
|
|
437
|
+
putBoolean("wasLast", event.wasLast)
|
|
438
|
+
putBoolean("willAutoAdvance", event.willAutoAdvance)
|
|
439
|
+
}
|
|
440
|
+
ShortKitBridge.shared?.emitEvent("onCarouselActiveVideoCompleted", params)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
257
443
|
}
|
|
258
444
|
|
|
259
445
|
// ------------------------------------------------------------------
|
|
@@ -330,6 +516,24 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
330
516
|
}
|
|
331
517
|
}
|
|
332
518
|
|
|
519
|
+
/**
|
|
520
|
+
* Pre-dispatch hook so a DOWN inside the scrubber strip immediately tells
|
|
521
|
+
* every ancestor (the cell, the inner carousel pager, and the outer feed
|
|
522
|
+
* pager) to stop intercepting touch events. Without this, the inner
|
|
523
|
+
* carousel pager's horizontal-pan recognizer and the outer feed pager's
|
|
524
|
+
* vertical-pan recognizer can grab a scrubber drag mid-stream.
|
|
525
|
+
*
|
|
526
|
+
* Mirrors ReactOverlayHost.onInterceptTouchEvent for the regular feed.
|
|
527
|
+
*/
|
|
528
|
+
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
|
529
|
+
if (ev.action == MotionEvent.ACTION_DOWN && height > 0) {
|
|
530
|
+
if (ev.y > height - interactiveBottomStripPx) {
|
|
531
|
+
parent?.requestDisallowInterceptTouchEvent(true)
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return super.onInterceptTouchEvent(ev)
|
|
535
|
+
}
|
|
536
|
+
|
|
333
537
|
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
334
538
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
335
539
|
if (w > 0 && h > 0) {
|
|
@@ -387,17 +591,18 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
387
591
|
private fun createSurfaceIfNeeded() {
|
|
388
592
|
if (surface != null) return
|
|
389
593
|
|
|
390
|
-
val reactHost = (context.applicationContext as? ReactApplication)?.reactHost
|
|
391
|
-
if (reactHost == null) {
|
|
392
|
-
android.util.Log.e(TAG, "createSurface FAILED: reactHost is null")
|
|
393
|
-
return
|
|
394
|
-
}
|
|
395
594
|
val moduleName = "ShortKitVideoCarouselOverlay_$videoCarouselOverlayName"
|
|
396
595
|
|
|
397
596
|
val initialProps = pendingProps
|
|
398
597
|
pendingProps = null
|
|
399
598
|
|
|
400
|
-
val newSurface =
|
|
599
|
+
val newSurface = createSurface(moduleName, initialProps)
|
|
600
|
+
if (newSurface == null) {
|
|
601
|
+
android.util.Log.e(TAG, "createSurface FAILED: reactHost is null")
|
|
602
|
+
// Restore pending props so a future createSurfaceIfNeeded() can retry.
|
|
603
|
+
if (initialProps != null) pendingProps = initialProps
|
|
604
|
+
return
|
|
605
|
+
}
|
|
401
606
|
surface = newSurface
|
|
402
607
|
|
|
403
608
|
newSurface.view?.let { surfaceView ->
|
|
@@ -430,6 +635,7 @@ class ReactVideoCarouselOverlayHost(context: Context) : FrameLayout(context), Vi
|
|
|
430
635
|
flowScope?.cancel()
|
|
431
636
|
flowScope = null
|
|
432
637
|
isActive = false
|
|
638
|
+
currentCarouselItem = null
|
|
433
639
|
stopTimeCoalescing()
|
|
434
640
|
if (surface?.isRunning == true) {
|
|
435
641
|
surface?.stop()
|