@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.
- package/android/build.gradle.kts +13 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +115 -55
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +67 -56
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +71 -26
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +160 -35
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +5 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +43 -10
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +9 -0
- package/ios/ReactOverlayHost.swift +13 -27
- package/ios/ShortKitBridge.swift +36 -2
- package/ios/ShortKitFeedView.swift +24 -3
- package/ios/ShortKitModule.mm +4 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +720 -144
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +19 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftdoc +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.swiftinterface +19 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +720 -144
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +19 -5
- 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 +19 -5
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/package.json +1 -1
- package/src/ShortKitContext.ts +2 -1
- package/src/ShortKitFeed.tsx +14 -0
- package/src/ShortKitOverlaySurface.tsx +153 -45
- package/src/ShortKitPlayer.tsx +25 -3
- package/src/ShortKitProvider.tsx +4 -2
- package/src/index.ts +4 -0
- package/src/serialization.ts +1 -0
- package/src/specs/NativeShortKitModule.ts +18 -1
- package/src/types.ts +6 -0
package/android/build.gradle.kts
CHANGED
|
@@ -31,8 +31,20 @@ android {
|
|
|
31
31
|
|
|
32
32
|
dependencies {
|
|
33
33
|
implementation("com.facebook.react:react-android")
|
|
34
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
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
|
-
}
|
|
155
|
+
} else item
|
|
156
|
+
|
|
157
|
+
val json = Json.encodeToString(fastItem)
|
|
158
|
+
val bundle = Bundle().apply { putString("item", json) }
|
|
159
|
+
applySurfaceProps(bundle)
|
|
138
160
|
|
|
139
|
-
|
|
140
|
-
val
|
|
141
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|