@shortkitsdk/react-native 0.2.6 → 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/ShortKitReactNative.podspec +1 -0
- package/android/build.gradle.kts +17 -1
- package/android/src/main/java/com/shortkit/reactnative/ReactCarouselOverlayHost.kt +379 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactLoadingHost.kt +40 -0
- package/android/src/main/java/com/shortkit/reactnative/ReactOverlayHost.kt +570 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitBridge.kt +1029 -0
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedView.kt +212 -219
- package/android/src/main/java/com/shortkit/reactnative/ShortKitFeedViewManager.kt +17 -3
- package/android/src/main/java/com/shortkit/reactnative/ShortKitModule.kt +157 -742
- package/android/src/main/java/com/shortkit/reactnative/ShortKitPlayerNativeView.kt +11 -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 +444 -0
- package/ios/SKFabricSurfaceWrapper.h +18 -0
- package/ios/SKFabricSurfaceWrapper.mm +57 -0
- package/ios/ShortKitBridge.swift +220 -63
- package/ios/ShortKitFeedView.swift +82 -228
- package/ios/ShortKitFeedViewManager.mm +3 -2
- package/ios/ShortKitModule.mm +69 -37
- 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 +1 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.abi.json +3683 -1249
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios.private.swiftinterface +56 -15
- 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 +56 -15
- package/ios/ShortKitSDK.xcframework/ios-arm64/ShortKitSDK.framework/ShortKitSDK +0 -0
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Headers/ShortKitSDK-Swift.h +1 -1
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.abi.json +3683 -1249
- package/ios/ShortKitSDK.xcframework/ios-arm64-simulator/ShortKitSDK.framework/Modules/ShortKitSDK.swiftmodule/arm64-apple-ios-simulator.private.swiftinterface +56 -15
- 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 +56 -15
- 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 +6 -24
- package/src/ShortKitFeed.tsx +124 -41
- package/src/ShortKitLoadingSurface.tsx +24 -0
- package/src/ShortKitOverlaySurface.tsx +313 -0
- package/src/ShortKitPlayer.tsx +30 -9
- package/src/ShortKitProvider.tsx +28 -285
- package/src/index.ts +9 -3
- package/src/serialization.ts +20 -39
- package/src/specs/NativeShortKitModule.ts +74 -45
- package/src/specs/ShortKitFeedViewNativeComponent.ts +3 -2
- package/src/types.ts +84 -16
- package/src/useShortKit.ts +1 -3
- package/src/useShortKitPlayer.ts +7 -7
- 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 -219
- package/ios/ShortKitOverlayBridge.swift +0 -111
- package/src/CarouselOverlayManager.tsx +0 -70
- package/src/OverlayManager.tsx +0 -87
- package/src/useShortKitCarousel.ts +0 -29
|
@@ -11,6 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.platforms = { :ios => "16.0" }
|
|
12
12
|
s.source = { :git => "https://github.com/shortkit/shortkit.git", :tag => "v#{s.version}" }
|
|
13
13
|
s.source_files = "ios/*.{h,m,mm,cpp,swift}"
|
|
14
|
+
s.public_header_files = "ios/ShortKitModule.h", "ios/SKFabricSurfaceWrapper.h"
|
|
14
15
|
s.requires_arc = true
|
|
15
16
|
|
|
16
17
|
# When the vendored XCFramework is present (npm published package), use it.
|
package/android/build.gradle.kts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
plugins {
|
|
2
2
|
id("com.android.library")
|
|
3
3
|
id("org.jetbrains.kotlin.android")
|
|
4
|
+
id("org.jetbrains.kotlin.plugin.serialization")
|
|
4
5
|
id("com.facebook.react")
|
|
5
6
|
}
|
|
6
7
|
|
|
@@ -30,5 +31,20 @@ android {
|
|
|
30
31
|
|
|
31
32
|
dependencies {
|
|
32
33
|
implementation("com.facebook.react:react-android")
|
|
33
|
-
|
|
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")
|
|
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")
|
|
48
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
|
|
49
|
+
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
|
|
34
50
|
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
package com.shortkit.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.graphics.Bitmap
|
|
5
|
+
import android.os.Bundle
|
|
6
|
+
import android.widget.FrameLayout
|
|
7
|
+
import com.facebook.react.ReactApplication
|
|
8
|
+
import com.facebook.react.bridge.Arguments
|
|
9
|
+
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
10
|
+
import com.facebook.react.runtime.ReactSurfaceImpl
|
|
11
|
+
import com.shortkit.sdk.model.CarouselImage
|
|
12
|
+
import com.shortkit.sdk.model.ImageCarouselItem
|
|
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
|
|
20
|
+
import kotlinx.serialization.encodeToString
|
|
21
|
+
import kotlinx.serialization.json.Json
|
|
22
|
+
import java.io.File
|
|
23
|
+
import java.io.FileOutputStream
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A [FrameLayout] that conforms to [CarouselOverlay] and hosts a React Native
|
|
27
|
+
* Fabric [ReactSurface] for rendering the developer's React carousel overlay
|
|
28
|
+
* component inside a feed cell.
|
|
29
|
+
*
|
|
30
|
+
* Unlike [ReactOverlayHost], this does not subscribe to player state (carousels
|
|
31
|
+
* are image-based). It pushes [ImageCarouselItem] data as surface properties.
|
|
32
|
+
*
|
|
33
|
+
* Android equivalent of `react_native_sdk/ios/ReactCarouselOverlayHost.swift`.
|
|
34
|
+
*/
|
|
35
|
+
class ReactCarouselOverlayHost(context: Context) : FrameLayout(context), CarouselOverlay {
|
|
36
|
+
|
|
37
|
+
private companion object {
|
|
38
|
+
const val TAG = "SK:CarouselHost"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ------------------------------------------------------------------
|
|
42
|
+
// Configuration
|
|
43
|
+
// ------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Suffix for the surface module name (e.g. "news" -> "ShortKitCarouselOverlay_news"). */
|
|
46
|
+
var carouselOverlayName: String = "Default"
|
|
47
|
+
|
|
48
|
+
// ------------------------------------------------------------------
|
|
49
|
+
// State
|
|
50
|
+
// ------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
private var surface: ReactSurface? = null
|
|
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
|
|
58
|
+
|
|
59
|
+
// ------------------------------------------------------------------
|
|
60
|
+
// Fabric layout workaround
|
|
61
|
+
// ------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
private val layoutHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
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
|
+
|
|
73
|
+
override fun requestLayout() {
|
|
74
|
+
super.requestLayout()
|
|
75
|
+
if (!isInLayoutPass) {
|
|
76
|
+
@Suppress("UNNECESSARY_SAFE_CALL")
|
|
77
|
+
layoutHandler?.post(layoutRunnable)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ------------------------------------------------------------------
|
|
82
|
+
// Init
|
|
83
|
+
// ------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
init {
|
|
86
|
+
setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ------------------------------------------------------------------
|
|
90
|
+
// Surface warmup
|
|
91
|
+
// ------------------------------------------------------------------
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Eagerly create the Fabric surface so it's mounted and ready before
|
|
95
|
+
* the cell scrolls into view. Called from the overlay factory closure
|
|
96
|
+
* at pool creation time — mirrors iOS ReactCarouselOverlayHost.prepareSurface().
|
|
97
|
+
*/
|
|
98
|
+
fun prepareSurface() {
|
|
99
|
+
createSurfaceIfNeeded()
|
|
100
|
+
|
|
101
|
+
// Pre-measure the host and surface view to screen dimensions so they
|
|
102
|
+
// have non-zero size before being attached to a cell. This prevents
|
|
103
|
+
// the black flash that occurs when the host is added at 0x0 and needs
|
|
104
|
+
// an async layout pass to become visible.
|
|
105
|
+
val displayW = context.resources.displayMetrics.widthPixels
|
|
106
|
+
val displayH = context.resources.displayMetrics.heightPixels
|
|
107
|
+
val wSpec = MeasureSpec.makeMeasureSpec(displayW, MeasureSpec.EXACTLY)
|
|
108
|
+
val hSpec = MeasureSpec.makeMeasureSpec(displayH, MeasureSpec.EXACTLY)
|
|
109
|
+
measure(wSpec, hSpec)
|
|
110
|
+
layout(0, 0, displayW, displayH)
|
|
111
|
+
measureAndLayoutSurfaceView(displayW, displayH)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ------------------------------------------------------------------
|
|
115
|
+
// CarouselOverlay — native image cache
|
|
116
|
+
// ------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/** Injected by the SDK's PrefetchManager. Returns a cached Bitmap or null. */
|
|
119
|
+
override var cachedImage: ((String) -> Bitmap?)? = null
|
|
120
|
+
|
|
121
|
+
// ------------------------------------------------------------------
|
|
122
|
+
// CarouselOverlay
|
|
123
|
+
// ------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
override fun configure(item: ImageCarouselItem) {
|
|
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
|
|
144
|
+
}
|
|
145
|
+
ImageCarouselItem(
|
|
146
|
+
id = item.id,
|
|
147
|
+
images = fastImages,
|
|
148
|
+
caption = item.caption,
|
|
149
|
+
title = item.title,
|
|
150
|
+
description = item.description,
|
|
151
|
+
author = item.author,
|
|
152
|
+
section = item.section,
|
|
153
|
+
articleUrl = item.articleUrl,
|
|
154
|
+
)
|
|
155
|
+
} else item
|
|
156
|
+
|
|
157
|
+
val json = Json.encodeToString(fastItem)
|
|
158
|
+
val bundle = Bundle().apply { putString("item", json) }
|
|
159
|
+
applySurfaceProps(bundle)
|
|
160
|
+
|
|
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
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Apply props to the surface, handling all lifecycle states. */
|
|
213
|
+
private fun applySurfaceProps(bundle: Bundle) {
|
|
214
|
+
val s = surface
|
|
215
|
+
if (s != null && s.isRunning) {
|
|
216
|
+
(s as? ReactSurfaceImpl)?.updateInitProps(bundle)
|
|
217
|
+
} else if (s != null && !s.isRunning) {
|
|
218
|
+
(s as? ReactSurfaceImpl)?.updateInitProps(bundle)
|
|
219
|
+
s.start()
|
|
220
|
+
} else {
|
|
221
|
+
pendingProps = bundle
|
|
222
|
+
createSurfaceIfNeeded()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Write a Bitmap to the temp directory as JPEG, returning a file:// URL.
|
|
228
|
+
* Uses a hash of the remote URL as filename to avoid duplicates.
|
|
229
|
+
* Matches iOS ReactCarouselOverlayHost.writeTempImage().
|
|
230
|
+
*/
|
|
231
|
+
private fun writeTempImage(bitmap: Bitmap, remoteUrl: String): String? {
|
|
232
|
+
val hash = remoteUrl.hashCode()
|
|
233
|
+
val fileName = "sk-carousel-${kotlin.math.abs(hash)}.jpg"
|
|
234
|
+
val file = File(context.cacheDir, fileName)
|
|
235
|
+
|
|
236
|
+
// Skip if already written
|
|
237
|
+
if (file.exists()) return "file://${file.absolutePath}"
|
|
238
|
+
|
|
239
|
+
return try {
|
|
240
|
+
FileOutputStream(file).use { out ->
|
|
241
|
+
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)
|
|
242
|
+
}
|
|
243
|
+
"file://${file.absolutePath}"
|
|
244
|
+
} catch (e: Exception) {
|
|
245
|
+
android.util.Log.w(TAG, "writeTempImage failed for $remoteUrl", e)
|
|
246
|
+
null
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
override fun resetState() {
|
|
251
|
+
val surfaceImpl = surface as? ReactSurfaceImpl ?: return
|
|
252
|
+
surfaceImpl.updateInitProps(Bundle())
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
override fun fadeOutForTransition() {
|
|
256
|
+
ShortKitBridge.shared?.emitCarouselOverlayEvent(
|
|
257
|
+
"onCarouselOverlayFadeOut",
|
|
258
|
+
Arguments.createMap()
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
override fun restoreFromTransition() {
|
|
263
|
+
ShortKitBridge.shared?.emitCarouselOverlayEvent(
|
|
264
|
+
"onCarouselOverlayRestore",
|
|
265
|
+
Arguments.createMap()
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ------------------------------------------------------------------
|
|
270
|
+
// Surface Creation
|
|
271
|
+
// ------------------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
274
|
+
super.onSizeChanged(w, h, oldw, oldh)
|
|
275
|
+
if (w > 0 && h > 0) {
|
|
276
|
+
measureAndLayoutSurfaceView(w, h)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
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
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} finally {
|
|
297
|
+
isInLayoutPass = false
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private fun measureAndLayoutSurfaceView(w: Int, h: Int) {
|
|
302
|
+
val sv = surface?.view ?: return
|
|
303
|
+
if (sv.width == w && sv.height == h) return
|
|
304
|
+
val wSpec = android.view.View.MeasureSpec.makeMeasureSpec(w, android.view.View.MeasureSpec.EXACTLY)
|
|
305
|
+
val hSpec = android.view.View.MeasureSpec.makeMeasureSpec(h, android.view.View.MeasureSpec.EXACTLY)
|
|
306
|
+
sv.measure(wSpec, hSpec)
|
|
307
|
+
sv.layout(0, 0, w, h)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
override fun onAttachedToWindow() {
|
|
311
|
+
super.onAttachedToWindow()
|
|
312
|
+
|
|
313
|
+
val parentView = parent as? android.view.View
|
|
314
|
+
if (parentView != null && parentView.width > 0 && parentView.height > 0 && (width == 0 || height == 0)) {
|
|
315
|
+
val wSpec = android.view.View.MeasureSpec.makeMeasureSpec(parentView.width, android.view.View.MeasureSpec.EXACTLY)
|
|
316
|
+
val hSpec = android.view.View.MeasureSpec.makeMeasureSpec(parentView.height, android.view.View.MeasureSpec.EXACTLY)
|
|
317
|
+
measure(wSpec, hSpec)
|
|
318
|
+
layout(0, 0, parentView.width, parentView.height)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Create the surface object and attach its view, but do NOT start it yet.
|
|
324
|
+
* Called from prepareSurface() to warm the surface ahead of configure().
|
|
325
|
+
*/
|
|
326
|
+
private fun createSurfaceIfNeeded() {
|
|
327
|
+
if (surface != null) return
|
|
328
|
+
|
|
329
|
+
val reactHost = (context.applicationContext as? ReactApplication)?.reactHost
|
|
330
|
+
if (reactHost == null) {
|
|
331
|
+
android.util.Log.e(TAG, "createSurface FAILED: reactHost is null")
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
val moduleName = "ShortKitCarouselOverlay_$carouselOverlayName"
|
|
335
|
+
|
|
336
|
+
// Pass pending props as initial props so the JS component has data on first render.
|
|
337
|
+
// If called from prepareSurface() (no item yet), pendingProps is null — the surface
|
|
338
|
+
// is created but NOT started. It will be started in configure() with real data.
|
|
339
|
+
val initialProps = pendingProps
|
|
340
|
+
pendingProps = null
|
|
341
|
+
|
|
342
|
+
val newSurface = reactHost.createSurface(context, moduleName, initialProps)
|
|
343
|
+
surface = newSurface
|
|
344
|
+
|
|
345
|
+
newSurface.view?.let { surfaceView ->
|
|
346
|
+
surfaceView.layoutParams = LayoutParams(
|
|
347
|
+
LayoutParams.MATCH_PARENT,
|
|
348
|
+
LayoutParams.MATCH_PARENT
|
|
349
|
+
)
|
|
350
|
+
addView(surfaceView)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Only start if we have item data — avoids rendering a blank frame.
|
|
354
|
+
if (initialProps != null) {
|
|
355
|
+
newSurface.start()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Use parent dimensions if host hasn't been laid out yet
|
|
359
|
+
val parentView = parent as? android.view.View
|
|
360
|
+
val w = if (width > 0) width else parentView?.width ?: 0
|
|
361
|
+
val h = if (height > 0) height else parentView?.height ?: 0
|
|
362
|
+
if (w > 0 && h > 0) {
|
|
363
|
+
measureAndLayoutSurfaceView(w, h)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// ------------------------------------------------------------------
|
|
368
|
+
// Cleanup
|
|
369
|
+
// ------------------------------------------------------------------
|
|
370
|
+
|
|
371
|
+
override fun onDetachedFromWindow() {
|
|
372
|
+
super.onDetachedFromWindow()
|
|
373
|
+
pendingWriteJob?.cancel()
|
|
374
|
+
pendingWriteJob = null
|
|
375
|
+
if (surface?.isRunning == true) {
|
|
376
|
+
surface?.stop()
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
package com.shortkit.reactnative
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.widget.FrameLayout
|
|
5
|
+
import com.facebook.react.ReactApplication
|
|
6
|
+
import com.facebook.react.interfaces.fabric.ReactSurface
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A [FrameLayout] that hosts a React Native Fabric surface for the custom
|
|
10
|
+
* loading component. Used as the loading view provider for the ShortKit SDK.
|
|
11
|
+
*
|
|
12
|
+
* Android equivalent of `react_native_sdk/ios/ReactLoadingHost.swift`.
|
|
13
|
+
*/
|
|
14
|
+
class ReactLoadingHost(context: Context) : FrameLayout(context) {
|
|
15
|
+
|
|
16
|
+
private var surface: ReactSurface? = null
|
|
17
|
+
|
|
18
|
+
override fun onAttachedToWindow() {
|
|
19
|
+
super.onAttachedToWindow()
|
|
20
|
+
if (surface == null) {
|
|
21
|
+
val reactHost = (context.applicationContext as? ReactApplication)?.reactHost ?: return
|
|
22
|
+
val newSurface = reactHost.createSurface(context, "ShortKitLoading", null)
|
|
23
|
+
newSurface.view?.let { surfaceView ->
|
|
24
|
+
surfaceView.layoutParams = LayoutParams(
|
|
25
|
+
LayoutParams.MATCH_PARENT,
|
|
26
|
+
LayoutParams.MATCH_PARENT
|
|
27
|
+
)
|
|
28
|
+
addView(surfaceView)
|
|
29
|
+
}
|
|
30
|
+
newSurface.start()
|
|
31
|
+
surface = newSurface
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
override fun onDetachedFromWindow() {
|
|
36
|
+
super.onDetachedFromWindow()
|
|
37
|
+
surface?.stop()
|
|
38
|
+
surface = null
|
|
39
|
+
}
|
|
40
|
+
}
|