@onekeyfe/react-native-chart-webview 3.0.39

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 (132) hide show
  1. package/ChartWebview.podspec +29 -0
  2. package/LICENSE +21 -0
  3. package/README.md +31 -0
  4. package/android/CMakeLists.txt +24 -0
  5. package/android/build.gradle +131 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/cpp-adapter.cpp +6 -0
  8. package/android/src/main/java/com/margelo/nitro/chartwebview/ChartWebview.kt +331 -0
  9. package/android/src/main/java/com/margelo/nitro/chartwebview/ChartWebviewPackage.kt +29 -0
  10. package/android/src/main/java/com/margelo/nitro/chartwebview/PooledChartWebView.kt +385 -0
  11. package/ios/ChartWebview.swift +747 -0
  12. package/lib/module/ChartWebview.nitro.js +4 -0
  13. package/lib/module/ChartWebview.nitro.js.map +1 -0
  14. package/lib/module/bridge.js +30 -0
  15. package/lib/module/bridge.js.map +1 -0
  16. package/lib/module/index.js +19 -0
  17. package/lib/module/index.js.map +1 -0
  18. package/lib/module/package.json +1 -0
  19. package/lib/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  20. package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  21. package/lib/nitrogen/generated/android/c++/JHybridChartWebviewSpec.cpp +186 -0
  22. package/lib/nitrogen/generated/android/c++/JHybridChartWebviewSpec.hpp +88 -0
  23. package/lib/nitrogen/generated/android/c++/views/JHybridChartWebviewStateUpdater.cpp +96 -0
  24. package/lib/nitrogen/generated/android/c++/views/JHybridChartWebviewStateUpdater.hpp +49 -0
  25. package/lib/nitrogen/generated/android/chartwebview+autolinking.cmake +83 -0
  26. package/lib/nitrogen/generated/android/chartwebview+autolinking.gradle +27 -0
  27. package/lib/nitrogen/generated/android/chartwebviewOnLoad.cpp +50 -0
  28. package/lib/nitrogen/generated/android/chartwebviewOnLoad.hpp +25 -0
  29. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/Func_void.kt +80 -0
  30. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/Func_void_std__string.kt +80 -0
  31. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/HybridChartWebviewSpec.kt +153 -0
  32. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/chartwebviewOnLoad.kt +35 -0
  33. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/views/HybridChartWebviewManager.kt +50 -0
  34. package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/views/HybridChartWebviewStateUpdater.kt +23 -0
  35. package/lib/nitrogen/generated/ios/ChartWebview+autolinking.rb +60 -0
  36. package/lib/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Bridge.cpp +49 -0
  37. package/lib/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Bridge.hpp +158 -0
  38. package/lib/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Umbrella.hpp +46 -0
  39. package/lib/nitrogen/generated/ios/ChartWebviewAutolinking.mm +33 -0
  40. package/lib/nitrogen/generated/ios/ChartWebviewAutolinking.swift +25 -0
  41. package/lib/nitrogen/generated/ios/c++/HybridChartWebviewSpecSwift.cpp +11 -0
  42. package/lib/nitrogen/generated/ios/c++/HybridChartWebviewSpecSwift.hpp +164 -0
  43. package/lib/nitrogen/generated/ios/c++/views/HybridChartWebviewComponent.mm +146 -0
  44. package/lib/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  45. package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  46. package/lib/nitrogen/generated/ios/swift/HybridChartWebviewSpec.swift +68 -0
  47. package/lib/nitrogen/generated/ios/swift/HybridChartWebviewSpec_cxx.swift +450 -0
  48. package/lib/nitrogen/generated/shared/c++/HybridChartWebviewSpec.cpp +45 -0
  49. package/lib/nitrogen/generated/shared/c++/HybridChartWebviewSpec.hpp +87 -0
  50. package/lib/nitrogen/generated/shared/c++/views/HybridChartWebviewComponent.cpp +207 -0
  51. package/lib/nitrogen/generated/shared/c++/views/HybridChartWebviewComponent.hpp +118 -0
  52. package/lib/nitrogen/generated/shared/json/ChartWebviewConfig.json +20 -0
  53. package/lib/nitrogen/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  54. package/lib/nitrogen/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  55. package/lib/nitrogen/nitrogen/generated/android/c++/JHybridChartWebviewSpec.cpp +186 -0
  56. package/lib/nitrogen/nitrogen/generated/android/c++/JHybridChartWebviewSpec.hpp +88 -0
  57. package/lib/nitrogen/nitrogen/generated/android/c++/views/JHybridChartWebviewStateUpdater.cpp +96 -0
  58. package/lib/nitrogen/nitrogen/generated/android/c++/views/JHybridChartWebviewStateUpdater.hpp +49 -0
  59. package/lib/nitrogen/nitrogen/generated/android/chartwebview+autolinking.cmake +83 -0
  60. package/lib/nitrogen/nitrogen/generated/android/chartwebview+autolinking.gradle +27 -0
  61. package/lib/nitrogen/nitrogen/generated/android/chartwebviewOnLoad.cpp +50 -0
  62. package/lib/nitrogen/nitrogen/generated/android/chartwebviewOnLoad.hpp +25 -0
  63. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/Func_void.kt +80 -0
  64. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/Func_void_std__string.kt +80 -0
  65. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/HybridChartWebviewSpec.kt +153 -0
  66. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/chartwebviewOnLoad.kt +35 -0
  67. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/views/HybridChartWebviewManager.kt +50 -0
  68. package/lib/nitrogen/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/views/HybridChartWebviewStateUpdater.kt +23 -0
  69. package/lib/nitrogen/nitrogen/generated/ios/ChartWebview+autolinking.rb +60 -0
  70. package/lib/nitrogen/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Bridge.cpp +49 -0
  71. package/lib/nitrogen/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Bridge.hpp +158 -0
  72. package/lib/nitrogen/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Umbrella.hpp +46 -0
  73. package/lib/nitrogen/nitrogen/generated/ios/ChartWebviewAutolinking.mm +33 -0
  74. package/lib/nitrogen/nitrogen/generated/ios/ChartWebviewAutolinking.swift +25 -0
  75. package/lib/nitrogen/nitrogen/generated/ios/c++/HybridChartWebviewSpecSwift.cpp +11 -0
  76. package/lib/nitrogen/nitrogen/generated/ios/c++/HybridChartWebviewSpecSwift.hpp +164 -0
  77. package/lib/nitrogen/nitrogen/generated/ios/c++/views/HybridChartWebviewComponent.mm +146 -0
  78. package/lib/nitrogen/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  79. package/lib/nitrogen/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  80. package/lib/nitrogen/nitrogen/generated/ios/swift/HybridChartWebviewSpec.swift +68 -0
  81. package/lib/nitrogen/nitrogen/generated/ios/swift/HybridChartWebviewSpec_cxx.swift +450 -0
  82. package/lib/nitrogen/nitrogen/generated/shared/c++/HybridChartWebviewSpec.cpp +45 -0
  83. package/lib/nitrogen/nitrogen/generated/shared/c++/HybridChartWebviewSpec.hpp +87 -0
  84. package/lib/nitrogen/nitrogen/generated/shared/c++/views/HybridChartWebviewComponent.cpp +207 -0
  85. package/lib/nitrogen/nitrogen/generated/shared/c++/views/HybridChartWebviewComponent.hpp +118 -0
  86. package/lib/nitrogen/nitrogen/generated/shared/json/ChartWebviewConfig.json +20 -0
  87. package/lib/typescript/package.json +1 -0
  88. package/lib/typescript/src/ChartWebview.nitro.d.ts +21 -0
  89. package/lib/typescript/src/ChartWebview.nitro.d.ts.map +1 -0
  90. package/lib/typescript/src/bridge.d.ts +2 -0
  91. package/lib/typescript/src/bridge.d.ts.map +1 -0
  92. package/lib/typescript/src/index.d.ts +145 -0
  93. package/lib/typescript/src/index.d.ts.map +1 -0
  94. package/nitro.json +17 -0
  95. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  96. package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
  97. package/nitrogen/generated/android/c++/JHybridChartWebviewSpec.cpp +186 -0
  98. package/nitrogen/generated/android/c++/JHybridChartWebviewSpec.hpp +88 -0
  99. package/nitrogen/generated/android/c++/views/JHybridChartWebviewStateUpdater.cpp +96 -0
  100. package/nitrogen/generated/android/c++/views/JHybridChartWebviewStateUpdater.hpp +49 -0
  101. package/nitrogen/generated/android/chartwebview+autolinking.cmake +83 -0
  102. package/nitrogen/generated/android/chartwebview+autolinking.gradle +27 -0
  103. package/nitrogen/generated/android/chartwebviewOnLoad.cpp +50 -0
  104. package/nitrogen/generated/android/chartwebviewOnLoad.hpp +25 -0
  105. package/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/Func_void.kt +80 -0
  106. package/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/Func_void_std__string.kt +80 -0
  107. package/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/HybridChartWebviewSpec.kt +153 -0
  108. package/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/chartwebviewOnLoad.kt +35 -0
  109. package/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/views/HybridChartWebviewManager.kt +50 -0
  110. package/nitrogen/generated/android/kotlin/com/margelo/nitro/chartwebview/views/HybridChartWebviewStateUpdater.kt +23 -0
  111. package/nitrogen/generated/ios/ChartWebview+autolinking.rb +60 -0
  112. package/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Bridge.cpp +49 -0
  113. package/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Bridge.hpp +158 -0
  114. package/nitrogen/generated/ios/ChartWebview-Swift-Cxx-Umbrella.hpp +46 -0
  115. package/nitrogen/generated/ios/ChartWebviewAutolinking.mm +33 -0
  116. package/nitrogen/generated/ios/ChartWebviewAutolinking.swift +25 -0
  117. package/nitrogen/generated/ios/c++/HybridChartWebviewSpecSwift.cpp +11 -0
  118. package/nitrogen/generated/ios/c++/HybridChartWebviewSpecSwift.hpp +164 -0
  119. package/nitrogen/generated/ios/c++/views/HybridChartWebviewComponent.mm +146 -0
  120. package/nitrogen/generated/ios/swift/Func_void.swift +47 -0
  121. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +47 -0
  122. package/nitrogen/generated/ios/swift/HybridChartWebviewSpec.swift +68 -0
  123. package/nitrogen/generated/ios/swift/HybridChartWebviewSpec_cxx.swift +450 -0
  124. package/nitrogen/generated/shared/c++/HybridChartWebviewSpec.cpp +45 -0
  125. package/nitrogen/generated/shared/c++/HybridChartWebviewSpec.hpp +87 -0
  126. package/nitrogen/generated/shared/c++/views/HybridChartWebviewComponent.cpp +207 -0
  127. package/nitrogen/generated/shared/c++/views/HybridChartWebviewComponent.hpp +118 -0
  128. package/nitrogen/generated/shared/json/ChartWebviewConfig.json +20 -0
  129. package/package.json +170 -0
  130. package/src/ChartWebview.nitro.ts +59 -0
  131. package/src/bridge.ts +27 -0
  132. package/src/index.tsx +20 -0
@@ -0,0 +1,385 @@
1
+ package com.margelo.nitro.chartwebview
2
+
3
+ import android.annotation.SuppressLint
4
+ import android.app.Activity
5
+ import android.content.Context
6
+ import android.graphics.Bitmap
7
+ import android.graphics.Rect
8
+ import android.os.Handler
9
+ import android.os.Looper
10
+ import android.view.PixelCopy
11
+ import android.view.View
12
+ import android.view.ViewGroup
13
+ import android.view.Window
14
+ import android.widget.FrameLayout
15
+ import android.widget.ImageView
16
+ import android.webkit.JavascriptInterface
17
+ import com.facebook.react.uimanager.ThemedReactContext
18
+ import android.webkit.WebResourceRequest
19
+ import android.webkit.WebResourceResponse
20
+ import android.webkit.WebView
21
+ import androidx.webkit.WebResourceErrorCompat
22
+ import androidx.webkit.WebViewAssetLoader
23
+ import androidx.webkit.WebViewClientCompat
24
+ import androidx.webkit.WebViewCompat
25
+ import androidx.webkit.WebViewFeature
26
+ import org.json.JSONObject
27
+ import java.net.URLEncoder
28
+ import java.util.concurrent.atomic.AtomicInteger
29
+
30
+ /**
31
+ * Owns one real [WebView] together with its message bridge, asset loader and
32
+ * load state. A single instance can be **reparented** across multiple
33
+ * [HybridChartWebview] hosts that share a `reuseKey`, so N mount points are
34
+ * backed by ONE WebView (state preserved, no reload on hand-off).
35
+ *
36
+ * The host currently displaying the WebView is [owner]; all page events
37
+ * (message / load / error) are routed to it. Hosts drive the WebView only
38
+ * through this class — they never touch the [WebView] directly.
39
+ */
40
+ @SuppressLint("SetJavaScriptEnabled")
41
+ class PooledChartWebView private constructor(
42
+ context: Context,
43
+ val key: String,
44
+ ) {
45
+ companion object {
46
+ // Live WebView count, logged on create/destroy. With pooling, N hosts that
47
+ // share a reuseKey must produce exactly ONE "CREATED" line — this is the
48
+ // signal the example uses to verify the singleton.
49
+ private val liveCount = AtomicInteger(0)
50
+
51
+ const val ASSET_HOST = "appassets.androidplatform.net"
52
+ private const val DEFAULT_ENTRY = "index.html"
53
+
54
+ // Tiny platform transport shim: defines the single hook the shared TS bridge
55
+ // (CHART_BRIDGE_JS) calls. The bulk of the bridge lives in the TS layer and
56
+ // arrives via the `bridgeScript` ctor arg — this is the only platform-specific
57
+ // piece (Android -> AndroidChartBridge).
58
+ private const val NATIVE_POST_SHIM_JS =
59
+ "(function(){window.__chartNativePost=function(s){" +
60
+ "try{AndroidChartBridge.postMessage(s);}catch(e){}};})();"
61
+
62
+ fun create(context: Context, key: String): PooledChartWebView =
63
+ PooledChartWebView(context.applicationContext, key)
64
+ }
65
+
66
+ // Shim (defines __chartNativePost) + the TS bridge that uses it. Set lazily via
67
+ // setBridgeScript once the host has the prop value — registering it in init
68
+ // would capture an empty script, since the first reconcile (window-attach) can
69
+ // run before the bridgeScript prop is applied. Re-asserted on page start/finish.
70
+ private var outboundBridgeJs: String = ""
71
+ private var bridgeRegistered = false
72
+
73
+ // Called by the host before the first load. Registers the document-start bridge
74
+ // once a non-empty script is available; subsequent calls are no-ops.
75
+ fun setBridgeScript(bridgeScript: String) {
76
+ if (bridgeRegistered || bridgeScript.isEmpty()) return
77
+ outboundBridgeJs = "$NATIVE_POST_SHIM_JS\n$bridgeScript"
78
+ bridgeRegistered = true
79
+ if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
80
+ WebViewCompat.addDocumentStartJavaScript(webView, outboundBridgeJs, setOf("*"))
81
+ }
82
+ }
83
+
84
+ /** The host currently displaying this WebView; page events route here. */
85
+ var owner: HybridChartWebview? = null
86
+
87
+ private var assetLoader: WebViewAssetLoader? = null
88
+ private var lastLoadedUrl: String? = null
89
+ private var lastLocalBundle: String? = null
90
+
91
+ // Last rendered frame, used to mask the brief blank frame while the WebView's
92
+ // surface is torn down and recreated during a reparent (move). Kept until
93
+ // clearSnapshot() or the next capture replaces it.
94
+ private var cachedSnapshot: Bitmap? = null
95
+ private var overlay: ImageView? = null
96
+
97
+ // How long the snapshot overlay stays up after a reparent, giving the WebView
98
+ // time to draw its first frame in the new container (~5 frames).
99
+ private val overlayHideDelayMs = 80L
100
+
101
+ val webView: WebView = WebView(context).apply {
102
+ settings.javaScriptEnabled = true
103
+ settings.domStorageEnabled = true
104
+ settings.databaseEnabled = true
105
+ settings.allowFileAccess = false
106
+ settings.allowContentAccess = false
107
+ settings.mediaPlaybackRequiresUserGesture = false
108
+ addJavascriptInterface(ChartBridge(), "AndroidChartBridge")
109
+ }
110
+
111
+ init {
112
+ val n = liveCount.incrementAndGet()
113
+ android.util.Log.d("ChartWebviewPool", "WebView CREATED key=$key liveCount=$n")
114
+
115
+ webView.webViewClient = object : WebViewClientCompat() {
116
+ override fun shouldInterceptRequest(
117
+ view: WebView,
118
+ request: WebResourceRequest,
119
+ ): WebResourceResponse? = assetLoader?.shouldInterceptRequest(request.url)
120
+
121
+ override fun onPageStarted(view: WebView, url: String?, favicon: android.graphics.Bitmap?) {
122
+ super.onPageStarted(view, url, favicon)
123
+ if (outboundBridgeJs.isNotEmpty()) view.evaluateJavascript(outboundBridgeJs, null)
124
+ }
125
+
126
+ override fun onPageFinished(view: WebView, url: String?) {
127
+ super.onPageFinished(view, url)
128
+ if (outboundBridgeJs.isNotEmpty()) view.evaluateJavascript(outboundBridgeJs, null)
129
+ owner?.dispatchLoadEnd()
130
+ // Prime the snapshot so the first move already has a frame to mask with.
131
+ refreshSnapshotSoon()
132
+ }
133
+
134
+ override fun onReceivedError(
135
+ view: WebView,
136
+ request: WebResourceRequest,
137
+ error: WebResourceErrorCompat,
138
+ ) {
139
+ super.onReceivedError(view, request, error)
140
+ if (request.isForMainFrame) {
141
+ owner?.dispatchError(error.description?.toString() ?: "WebView error")
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ // page -> native: route to the active owner on the UI thread.
148
+ private inner class ChartBridge {
149
+ @JavascriptInterface
150
+ fun postMessage(message: String) {
151
+ runOnUiThread { owner?.dispatchMessage(message) }
152
+ }
153
+ }
154
+
155
+ /** Move the WebView into [container], detaching it from any previous parent. */
156
+ fun attachTo(container: ViewGroup) {
157
+ runOnUiThread {
158
+ if (webView.parent === container) return@runOnUiThread
159
+ (webView.parent as? ViewGroup)?.removeView(webView)
160
+ container.addView(
161
+ webView,
162
+ FrameLayout.LayoutParams(
163
+ FrameLayout.LayoutParams.MATCH_PARENT,
164
+ FrameLayout.LayoutParams.MATCH_PARENT,
165
+ ),
166
+ )
167
+ // Mask the reparent's blank frame with the last captured frame, then
168
+ // refresh the cache (async) so the next move has a current frame.
169
+ showSnapshotOverlay(container)
170
+ refreshSnapshotSoon()
171
+ }
172
+ }
173
+
174
+ /** Remove the WebView from its current parent (keeps it alive, warm). */
175
+ fun detachFromParent() {
176
+ runOnUiThread {
177
+ // Capture while still attached to the window (PixelCopy needs that), so a
178
+ // release-before-claim ordering still leaves a fresh frame to mask with.
179
+ capturePixelCopy(null)
180
+ removeOverlay()
181
+ (webView.parent as? ViewGroup)?.removeView(webView)
182
+ }
183
+ }
184
+
185
+ /** Drop the cached snapshot and remove any visible overlay. */
186
+ fun clearSnapshot() {
187
+ runOnUiThread {
188
+ removeOverlay()
189
+ cachedSnapshot = null
190
+ }
191
+ }
192
+
193
+ /** The last captured frame (shown by inactive hosts as a placeholder). */
194
+ fun snapshot(): Bitmap? = cachedSnapshot
195
+
196
+ /// Capture the current frame a moment after things settle (async).
197
+ fun refreshSnapshotSoon() {
198
+ webView.postDelayed({ capturePixelCopy(null) }, 120)
199
+ }
200
+
201
+ /// Capture now and deliver the fresh frame (e.g. so the host that is losing
202
+ /// ownership can update its placeholder to its very last frame).
203
+ fun captureNow(callback: (Bitmap?) -> Unit) {
204
+ capturePixelCopy(callback)
205
+ }
206
+
207
+ // Capture the WebView's REAL on-screen pixels (incl. GPU-rendered chart
208
+ // canvas) via PixelCopy. `webView.draw()` on a software canvas would miss the
209
+ // hardware-accelerated <canvas>, so the chart candles wouldn't be captured.
210
+ // PixelCopy is async and needs the view attached to a window.
211
+ private fun capturePixelCopy(callback: ((Bitmap?) -> Unit)?) {
212
+ // Don't capture mid-reparent (overlay visible): the WebView may be blank for
213
+ // a frame and we'd cache a black image. Keep the last good frame instead.
214
+ if (overlay != null) { callback?.invoke(cachedSnapshot); return }
215
+ val w = webView.width
216
+ val h = webView.height
217
+ if (w <= 0 || h <= 0 || webView.windowToken == null) { callback?.invoke(cachedSnapshot); return }
218
+ val window = currentWindow() ?: run { callback?.invoke(cachedSnapshot); return }
219
+ val location = IntArray(2)
220
+ webView.getLocationInWindow(location)
221
+ val src = Rect(location[0], location[1], location[0] + w, location[1] + h)
222
+ val bmp = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
223
+ try {
224
+ PixelCopy.request(
225
+ window,
226
+ src,
227
+ bmp,
228
+ { result ->
229
+ if (result == PixelCopy.SUCCESS) cachedSnapshot = bmp
230
+ callback?.invoke(cachedSnapshot)
231
+ },
232
+ webView.handler ?: Handler(Looper.getMainLooper()),
233
+ )
234
+ } catch (e: Throwable) {
235
+ // Best-effort: a failure just means no flash mask this move.
236
+ callback?.invoke(cachedSnapshot)
237
+ }
238
+ }
239
+
240
+ // The window owning the container the WebView is attached to (the host
241
+ // Activity). The WebView itself was created with an application context.
242
+ private fun currentWindow(): Window? {
243
+ val ctx = (webView.parent as? View)?.context
244
+ (ctx as? ThemedReactContext)?.currentActivity?.window?.let { return it }
245
+ return (ctx as? Activity)?.window
246
+ }
247
+
248
+ private fun showSnapshotOverlay(container: ViewGroup) {
249
+ val snap = cachedSnapshot ?: return
250
+ removeOverlay()
251
+ val iv = ImageView(container.context).apply {
252
+ setImageBitmap(snap)
253
+ scaleType = ImageView.ScaleType.FIT_XY
254
+ layoutParams = FrameLayout.LayoutParams(
255
+ FrameLayout.LayoutParams.MATCH_PARENT,
256
+ FrameLayout.LayoutParams.MATCH_PARENT,
257
+ )
258
+ }
259
+ container.addView(iv) // added last => drawn on top of the WebView
260
+ overlay = iv
261
+ iv.postDelayed({ removeOverlay() }, overlayHideDelayMs)
262
+ }
263
+
264
+ private fun removeOverlay() {
265
+ overlay?.let { (it.parent as? ViewGroup)?.removeView(it) }
266
+ overlay = null
267
+ }
268
+
269
+ /**
270
+ * Apply the source props and (re)load only when the effective URL changes —
271
+ * so reparenting / redundant prop re-applies never reload (which would lose
272
+ * chart state, the whole point of pooling).
273
+ */
274
+ fun setSource(uri: String?, localBundle: String?, entry: String?, paramsJson: String?) {
275
+ // Never load before the document-start bridge is registered — otherwise the
276
+ // page boots without the bridge and its first $private requests are lost. The
277
+ // host re-calls setSource once the bridgeScript prop arrives.
278
+ if (!bridgeRegistered) return
279
+ if (localBundle != lastLocalBundle) {
280
+ lastLocalBundle = localBundle
281
+ rebuildAssetLoader(localBundle)
282
+ }
283
+ val target = computeTargetUrl(uri, localBundle, entry, paramsJson) ?: return
284
+ if (target == lastLoadedUrl) return
285
+ lastLoadedUrl = target
286
+ runOnUiThread { webView.loadUrl(target) }
287
+ }
288
+
289
+ fun postMessage(message: String) {
290
+ val payload = JSONObject.quote(message)
291
+ val js =
292
+ "(function(){try{window.postMessage(JSON.parse($payload), '*');}" +
293
+ "catch(e){window.postMessage($payload, '*');}})();"
294
+ runOnUiThread { webView.evaluateJavascript(js, null) }
295
+ }
296
+
297
+ fun reload() {
298
+ runOnUiThread { webView.reload() }
299
+ }
300
+
301
+ /** Permanently free the WebView (used for non-pooled / private instances). */
302
+ fun destroy() {
303
+ runOnUiThread {
304
+ (webView.parent as? ViewGroup)?.removeView(webView)
305
+ webView.destroy()
306
+ val n = liveCount.decrementAndGet()
307
+ android.util.Log.d("ChartWebviewPool", "WebView DESTROYED key=$key liveCount=$n")
308
+ }
309
+ }
310
+
311
+ private fun rebuildAssetLoader(localBundle: String?) {
312
+ if (localBundle.isNullOrEmpty()) {
313
+ assetLoader = null
314
+ return
315
+ }
316
+ assetLoader = WebViewAssetLoader.Builder()
317
+ .setDomain(ASSET_HOST)
318
+ .addPathHandler("/", WebViewAssetLoader.AssetsPathHandler(webView.context))
319
+ .build()
320
+ }
321
+
322
+ private fun computeTargetUrl(
323
+ uri: String?,
324
+ localBundle: String?,
325
+ entry: String?,
326
+ paramsJson: String?,
327
+ ): String? {
328
+ if (!uri.isNullOrEmpty()) return uri
329
+ if (localBundle.isNullOrEmpty()) return null
330
+ val entryPath = entry?.takeIf { it.isNotEmpty() } ?: DEFAULT_ENTRY
331
+ val query = buildQueryFromParamsJson(paramsJson)
332
+ return buildString {
333
+ append("https://")
334
+ append(ASSET_HOST)
335
+ append('/')
336
+ append(entryPath)
337
+ if (query.isNotEmpty()) {
338
+ append('?')
339
+ append(query)
340
+ }
341
+ }
342
+ }
343
+
344
+ private fun buildQueryFromParamsJson(paramsJson: String?): String {
345
+ if (paramsJson.isNullOrEmpty()) return ""
346
+ return try {
347
+ val obj = JSONObject(paramsJson)
348
+ val sb = StringBuilder()
349
+ val keys = obj.keys()
350
+ while (keys.hasNext()) {
351
+ val k = keys.next()
352
+ val rawValue = obj.get(k)
353
+ val value = if (rawValue == JSONObject.NULL) "" else rawValue.toString()
354
+ if (sb.isNotEmpty()) sb.append('&')
355
+ sb.append(encode(k))
356
+ sb.append('=')
357
+ sb.append(encode(value))
358
+ }
359
+ sb.toString()
360
+ } catch (e: Exception) {
361
+ ""
362
+ }
363
+ }
364
+
365
+ private fun encode(s: String): String =
366
+ URLEncoder.encode(s, "UTF-8").replace("+", "%20")
367
+
368
+ private fun runOnUiThread(action: () -> Unit) {
369
+ if (Looper.myLooper() == Looper.getMainLooper()) action() else webView.post { action() }
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Warm pool of [PooledChartWebView]s keyed by `reuseKey`. Entries are created on
375
+ * first use and kept warm (a single static chart instance, OKX-style), so the
376
+ * next mount point that claims the same key reuses the live WebView instead of
377
+ * recreating it. Non-pooled hosts don't use this — they own a private instance.
378
+ */
379
+ object ChartWebviewPool {
380
+ private val shared = HashMap<String, PooledChartWebView>()
381
+
382
+ @Synchronized
383
+ fun acquireShared(key: String, context: Context): PooledChartWebView =
384
+ shared.getOrPut(key) { PooledChartWebView.create(context, key) }
385
+ }