catalyst-core 0.1.0-canary.8 → 0.2.0-beta.1
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/README.md +1 -1
- package/changelog.md +5 -0
- package/dist/native/androidProject/app/build.gradle.kts +9 -0
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +2 -1
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +19 -2
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +13 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +148 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeCameraManager.kt +337 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/TransitionManager.kt +202 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +22 -15
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +123 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +244 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/HoldController.kt +70 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/TorchController.kt +57 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamState.kt +41 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamStateMachine.kt +40 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ViewfinderMapper.kt +75 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ZoomController.kt +156 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +16 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +42 -5
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +7 -3
- package/dist/native/androidProject/app/src/main/res/drawable/debug_viewfinder_border.xml +8 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +37 -0
- package/dist/native/androidSetup.js +1 -0
- package/dist/native/bridge/constants/NativeInterfaces.js +9 -5
- package/dist/native/bridge/hooks/index.js +1 -0
- package/dist/native/bridge/hooks/useCamera.js +9 -0
- package/dist/native/bridge/hooks/useDataProtection.js +2 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +2 -0
- package/dist/native/bridge/hooks/useFilePicker.js +2 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.js +1 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +1 -0
- package/dist/native/bridge/hooks/useIntent.js +1 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +43 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +3 -0
- package/dist/native/bridge/hooks/useNotification.js +2 -0
- package/dist/native/bridge/hooks/useSafeArea.js +2 -0
- package/dist/native/bridge/hooks/useVideoStream.js +6 -0
- package/dist/native/bridge/hooks/utils.js +1 -0
- package/dist/native/bridge/hooks.js +1 -137
- package/dist/native/bridge/useBaseHook.js +12 -7
- package/dist/native/bridge/utils/NativeBridge.js +41 -6
- package/dist/native/buildAndroid/assets.js +52 -0
- package/dist/native/buildAndroid/build.js +19 -0
- package/dist/native/buildAndroid/config.js +1 -0
- package/dist/native/buildAndroid/index.js +1 -0
- package/dist/native/buildAppAndroid.js +2 -150
- package/dist/native/buildAppIos.js +2 -305
- package/dist/native/buildIos/assets.js +8 -0
- package/dist/native/buildIos/build.js +40 -0
- package/dist/native/buildIos/config.js +71 -0
- package/dist/native/buildIos/index.js +6 -0
- package/dist/native/buildIos/plugins.js +45 -0
- package/dist/native/internalPluginUtils.js +7 -1
- package/dist/native/iosnativeWebView/Package.resolved +36 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/BarcodeDetector.swift +116 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraPreviewView.swift +58 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +308 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +63 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +194 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/TorchController.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamState.swift +44 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamStateMachine.swift +60 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +130 -0
- package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +11 -1
- package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +4 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +214 -2
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +169 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +32 -7
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +6 -2
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +30 -11
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +4 -2
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +1 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +4 -2
- package/dist/native/pluginComposerAndroid.js +23 -4
- package/dist/native/pluginComposerIos.js +11 -2
- package/dist/native/renameAndroidProject.js +3 -3
- package/dist/native/setupEmulatorIos.js +6 -4
- package/dist/native/setupServer.js +2 -3
- package/dist/native/utils.js +4 -1
- package/dist/otel.js +70 -2
- package/dist/scripts/build.js +1 -1
- package/dist/scripts/cleanCache.js +1 -0
- package/dist/scripts/devBuild.js +1 -1
- package/dist/scripts/devServe.js +1 -1
- package/dist/scripts/loadEnvironmentVariables.js +1 -2
- package/dist/scripts/registerAliases.js +6 -2
- package/dist/scripts/scriptUtils.js +2 -1
- package/dist/scripts/serve.js +2 -1
- package/dist/scripts/start.js +7 -2
- package/dist/server/expressServer.js +3 -8
- package/dist/server/renderer/document/Body.js +4 -4
- package/dist/server/renderer/document/Head.js +2 -2
- package/dist/server/renderer/extract.js +31 -27
- package/dist/server/renderer/handler.js +47 -24
- package/dist/server/renderer/index.js +1 -1
- package/dist/server/startServer.js +57 -7
- package/dist/server/utils/userAgentUtil.js +9 -3
- package/dist/server/utils/validator.js +1 -1
- package/dist/webpack/babel-plugins/remove-client.plugin.js +2 -2
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +2 -2
- package/dist/webpack/babel.config.client.js +1 -1
- package/dist/webpack/babel.config.ssr.js +1 -1
- package/dist/webpack/base.babel.js +7 -8
- package/dist/webpack/development.client.babel.js +7 -16
- package/dist/webpack/production.client.babel.js +1 -1
- package/dist/webpack/production.ssr.babel.js +5 -1
- package/mcp_v2/knowledge-base.json +193 -100
- package/mcp_v2/lib/helpers.js +52 -12
- package/mcp_v2/lib/seed.js +88 -0
- package/mcp_v2/mcp.js +154 -7
- package/mcp_v2/setup.js +34 -195
- package/mcp_v2/tools/build.js +1 -1
- package/mcp_v2/tools/config.js +16 -4
- package/mcp_v2/tools/conversion.js +11 -11
- package/mcp_v2/tools/github.js +1117 -0
- package/mcp_v2/tools/knowledge.js +16 -3
- package/mcp_v2/tools/sync.js +179 -10
- package/mcp_v2/tools/tasks.js +13 -4
- package/package.json +25 -21
- package/.release-meta.json +0 -4
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +0 -43
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +0 -28
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +0 -19
- package/license +0 -10
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
Catalyst offers a comprehensive suite of features designed for modern web development. It includes isomorphic rendering for optimal performance, an extendable server with full-stack capabilities, and configurable state management. The framework employs smart prefetching of data and chunks, allows easy configuration of global styles and layouts, and provides SEO optimization at both global and page levels.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Catalyst also supports universal apps for Android and iOS. See the [Universal App guide](https://catalyst.1mg.com/public_docs/content/Guides%20and%20Tutorials/First%20Universal%20App/first-universal-app) to get started.
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
package/changelog.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.0-beta.2] - 2026-05-06
|
|
4
|
+
|
|
5
|
+
- Moved Catalyst into a monorepo structure with `catalyst-core`, `create-catalyst-app`, the Catalyst docs app, and the Catalyst core test app managed from one repository.
|
|
6
|
+
- Replaced the internal-package pre-release flow with a local release sandbox that scaffolds a real app using current-branch CCA and a locally packed current-branch `catalyst-core`.
|
|
7
|
+
|
|
3
8
|
## [0.1.0-canary.8] - 2026-04-15
|
|
4
9
|
|
|
5
10
|
- Introduced Catalyst’s internal plugin architecture, adding the `PluginBridge` web API, native plugin runtimes for Android and iOS, and manifest-driven build composition for modular native capabilities.
|
|
@@ -208,6 +208,15 @@ dependencies {
|
|
|
208
208
|
// SLF4J simple logger for Ktor (optional, can be excluded if needed)
|
|
209
209
|
implementation("org.slf4j:slf4j-simple:2.0.9")
|
|
210
210
|
|
|
211
|
+
// CameraX
|
|
212
|
+
implementation("androidx.camera:camera-core:1.3.4")
|
|
213
|
+
implementation("androidx.camera:camera-camera2:1.3.4")
|
|
214
|
+
implementation("androidx.camera:camera-lifecycle:1.3.4")
|
|
215
|
+
implementation("androidx.camera:camera-view:1.3.4")
|
|
216
|
+
|
|
217
|
+
// ML Kit Barcode Scanning
|
|
218
|
+
implementation("com.google.mlkit:barcode-scanning:17.3.0")
|
|
219
|
+
|
|
211
220
|
// Notification dependencies - conditional based on config
|
|
212
221
|
if (isNotificationsEnabled()) {
|
|
213
222
|
implementation("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
android:usesCleartextTraffic="true"
|
|
25
25
|
android:networkSecurityConfig="@xml/network_security_config"
|
|
26
26
|
tools:targetApi="31">
|
|
27
|
+
<!-- nosemgrep: java.android.security.exported_activity.exported_activity - Launcher activity must be exported so Android can start the app from the launcher. -->
|
|
27
28
|
<activity
|
|
28
29
|
android:name="io.yourname.androidproject.SplashActivity"
|
|
29
30
|
android:exported="true"
|
|
@@ -51,4 +52,4 @@
|
|
|
51
52
|
</provider>
|
|
52
53
|
</application>
|
|
53
54
|
|
|
54
|
-
</manifest>
|
|
55
|
+
</manifest>
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt
CHANGED
|
@@ -51,6 +51,7 @@ class CustomWebView(
|
|
|
51
51
|
private var offlinePageVisible = false
|
|
52
52
|
private var lastTargetUrl: String? = null
|
|
53
53
|
private var defaultRequestHeaders: Map<String, String> = emptyMap()
|
|
54
|
+
var onPageStarted: (() -> Unit)? = null
|
|
54
55
|
|
|
55
56
|
// Counters for asset loading statistics
|
|
56
57
|
private var assetLoadAttempts = 0
|
|
@@ -464,6 +465,18 @@ class CustomWebView(
|
|
|
464
465
|
return "build/public/$fileName"
|
|
465
466
|
}
|
|
466
467
|
|
|
468
|
+
private fun withCorsHeaders(response: WebResourceResponse): WebResourceResponse {
|
|
469
|
+
val responseHeaders = response.responseHeaders
|
|
470
|
+
if (responseHeaders.isNullOrEmpty()) {
|
|
471
|
+
response.responseHeaders = mutableMapOf(
|
|
472
|
+
"Access-Control-Allow-Origin" to "*",
|
|
473
|
+
"Access-Control-Allow-Methods" to "GET, OPTIONS",
|
|
474
|
+
"Access-Control-Allow-Headers" to "*"
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
return response
|
|
478
|
+
}
|
|
479
|
+
|
|
467
480
|
private fun getMimeType(path: String): String {
|
|
468
481
|
return when {
|
|
469
482
|
path.endsWith(".html") -> "text/html"
|
|
@@ -532,7 +545,7 @@ class CustomWebView(
|
|
|
532
545
|
if (BuildConfig.DEBUG) {
|
|
533
546
|
Log.d(TAG, "✅ SERVICE_WORKER: Served from cache: $url")
|
|
534
547
|
}
|
|
535
|
-
response
|
|
548
|
+
withCorsHeaders(response)
|
|
536
549
|
} else {
|
|
537
550
|
if (BuildConfig.DEBUG) {
|
|
538
551
|
Log.d(TAG, "❌ SERVICE_WORKER: Cache miss: $url")
|
|
@@ -617,6 +630,9 @@ class CustomWebView(
|
|
|
617
630
|
domStorageEnabled = true
|
|
618
631
|
allowFileAccess = true
|
|
619
632
|
allowContentAccess = true
|
|
633
|
+
setSupportZoom(false)
|
|
634
|
+
builtInZoomControls = false
|
|
635
|
+
displayZoomControls = false
|
|
620
636
|
|
|
621
637
|
// TODO: Enable these when build optimization feature is implemented
|
|
622
638
|
// These are deprecated but may be needed for local file access in development
|
|
@@ -823,7 +839,7 @@ class CustomWebView(
|
|
|
823
839
|
Log.d(TAG, "✅ WEBVIEW_CLIENT: Served from cache in ${duration}ms: $url")
|
|
824
840
|
}
|
|
825
841
|
metricsMonitor.recordCacheHit(url)
|
|
826
|
-
return response
|
|
842
|
+
return withCorsHeaders(response)
|
|
827
843
|
} else {
|
|
828
844
|
if (BuildConfig.DEBUG) {
|
|
829
845
|
Log.d(TAG, "❌ WEBVIEW_CLIENT: Cache miss, triggering async fetch: $url")
|
|
@@ -893,6 +909,7 @@ class CustomWebView(
|
|
|
893
909
|
super.onPageStarted(view, url, favicon)
|
|
894
910
|
if (url != null && url != offlineAssetUrl) {
|
|
895
911
|
offlinePageVisible = false
|
|
912
|
+
onPageStarted?.invoke()
|
|
896
913
|
}
|
|
897
914
|
progressBar.visibility = View.VISIBLE
|
|
898
915
|
val startTime = System.currentTimeMillis()
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt
CHANGED
|
@@ -4,6 +4,7 @@ import android.content.Intent
|
|
|
4
4
|
import android.os.Build
|
|
5
5
|
import android.os.Bundle
|
|
6
6
|
import android.util.Log
|
|
7
|
+
import android.view.MotionEvent
|
|
7
8
|
import android.view.WindowManager
|
|
8
9
|
import androidx.activity.OnBackPressedCallback
|
|
9
10
|
import androidx.appcompat.app.AppCompatActivity
|
|
@@ -326,6 +327,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
326
327
|
try {
|
|
327
328
|
nativeBridge = NativeBridge(this, customWebView.getWebView(), properties)
|
|
328
329
|
customWebView.addJavascriptInterface(nativeBridge, "NativeBridge")
|
|
330
|
+
|
|
331
|
+
// Wire NativeCameraManager
|
|
332
|
+
val cameraManager = NativeCameraManager(this, binding.cameraPreview, customWebView.getWebView(), binding.debugViewfinderOverlay, binding.debugViewfinderOverlay.findViewById(R.id.debug_qr_status), binding.debugBarcodeOverlay)
|
|
333
|
+
nativeBridge.setCameraManager(cameraManager)
|
|
334
|
+
customWebView.onPageStarted = { cameraManager.stop() }
|
|
329
335
|
} catch (e: Exception) {
|
|
330
336
|
Log.e(TAG, "Failed to initialize NativeBridge: ${e.message}")
|
|
331
337
|
}
|
|
@@ -431,6 +437,13 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
|
|
|
431
437
|
customWebView.onResume()
|
|
432
438
|
}
|
|
433
439
|
|
|
440
|
+
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
|
441
|
+
if (::nativeBridge.isInitialized) {
|
|
442
|
+
nativeBridge.getCameraManager()?.onTouchEvent(ev)
|
|
443
|
+
}
|
|
444
|
+
return super.dispatchTouchEvent(ev)
|
|
445
|
+
}
|
|
446
|
+
|
|
434
447
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
|
435
448
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
|
436
449
|
if (::nativeBridge.isInitialized) {
|
package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt
CHANGED
|
@@ -151,9 +151,16 @@ class NativeBridge(
|
|
|
151
151
|
|
|
152
152
|
private var networkMonitor: NetworkMonitor? = null
|
|
153
153
|
|
|
154
|
+
// Video stream manager
|
|
155
|
+
private var nativeCameraManager: NativeCameraManager? = null
|
|
156
|
+
private var webViewOriginalBackground: Int = android.graphics.Color.WHITE
|
|
157
|
+
|
|
154
158
|
// Unified notification manager
|
|
155
159
|
private val notificationManager = AppNotificationManager(mainActivity, mainActivity.properties)
|
|
156
160
|
|
|
161
|
+
// Transition manager — snapshot overlay pattern for native page transitions
|
|
162
|
+
private val transitionManager = TransitionManager(mainActivity, webView)
|
|
163
|
+
|
|
157
164
|
companion object {
|
|
158
165
|
private const val TAG = "NativeBridge"
|
|
159
166
|
private const val GOOGLE_SIGN_IN_SAFE_EXECUTE_ACTION = "start Google Sign-In"
|
|
@@ -239,6 +246,90 @@ class NativeBridge(
|
|
|
239
246
|
}
|
|
240
247
|
}
|
|
241
248
|
|
|
249
|
+
fun setCameraManager(manager: NativeCameraManager) {
|
|
250
|
+
nativeCameraManager = manager
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
fun getCameraManager(): NativeCameraManager? = nativeCameraManager
|
|
254
|
+
|
|
255
|
+
@JavascriptInterface
|
|
256
|
+
fun startVideoStream(optionsRaw: String?) {
|
|
257
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "start video stream") {
|
|
258
|
+
android.util.Log.d("NativeBridge", "startVideoStream raw options: $optionsRaw")
|
|
259
|
+
val options = try { JSONObject(optionsRaw ?: "{}") } catch (e: Exception) { JSONObject() }
|
|
260
|
+
val facing = options.optString("facing", "back")
|
|
261
|
+
val viewfinderRect = options.optJSONObject("viewfinderRect")
|
|
262
|
+
val zoomOptions = options.optJSONObject("zoom")
|
|
263
|
+
val scanFormat = options.optString("format", "all")
|
|
264
|
+
val showQrDetected = options.optBoolean("showQrDetected", false)
|
|
265
|
+
android.util.Log.d("NativeBridge", "startVideoStream parsed: facing=$facing zoom=$zoomOptions scanFormat=$scanFormat viewfinderRect=$viewfinderRect showQrDetected=$showQrDetected")
|
|
266
|
+
mainActivity.runOnUiThread {
|
|
267
|
+
webViewOriginalBackground = webView.solidColor.takeIf { it != 0 } ?: android.graphics.Color.WHITE
|
|
268
|
+
webView.setBackgroundColor(android.graphics.Color.TRANSPARENT)
|
|
269
|
+
nativeCameraManager?.start(facing, viewfinderRect, zoomOptions, scanFormat, showQrDetected = showQrDetected)
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@JavascriptInterface
|
|
275
|
+
fun stopVideoStream(optionsRaw: String?) {
|
|
276
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "stop video stream") {
|
|
277
|
+
mainActivity.runOnUiThread {
|
|
278
|
+
webView.setBackgroundColor(webViewOriginalBackground)
|
|
279
|
+
nativeCameraManager?.stop()
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@JavascriptInterface
|
|
285
|
+
fun setVideoStreamZoom(multiplierRaw: String?) {
|
|
286
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "set video stream zoom") {
|
|
287
|
+
val multiplier = multiplierRaw?.trim()?.toFloatOrNull() ?: run {
|
|
288
|
+
android.util.Log.w("NativeBridge", "setVideoStreamZoom — invalid value: $multiplierRaw, ignoring")
|
|
289
|
+
return@safeExecute
|
|
290
|
+
}
|
|
291
|
+
android.util.Log.d("NativeBridge", "setVideoStreamZoom — multiplier=${multiplier}x")
|
|
292
|
+
mainActivity.runOnUiThread {
|
|
293
|
+
nativeCameraManager?.setZoom(multiplier)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@JavascriptInterface
|
|
299
|
+
fun setVideoStreamTorch(optionsRaw: String?) {
|
|
300
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "set video stream torch") {
|
|
301
|
+
val options = try { JSONObject(optionsRaw ?: "{}") } catch (e: Exception) { JSONObject() }
|
|
302
|
+
val on = options.optBoolean("on", false)
|
|
303
|
+
android.util.Log.d("NativeBridge", "setVideoStreamTorch — on=$on")
|
|
304
|
+
mainActivity.runOnUiThread {
|
|
305
|
+
nativeCameraManager?.setTorch(on)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@JavascriptInterface
|
|
311
|
+
fun setVideoStreamFps(optionsRaw: String?) {
|
|
312
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "set video stream fps") {
|
|
313
|
+
val options = try { JSONObject(optionsRaw ?: "{}") } catch (e: Exception) { JSONObject() }
|
|
314
|
+
val min = if (options.isNull("min")) null else options.optInt("min")
|
|
315
|
+
val max = if (options.isNull("max")) null else options.optInt("max")
|
|
316
|
+
android.util.Log.d("NativeBridge", "setVideoStreamFps — min=$min max=$max")
|
|
317
|
+
mainActivity.runOnUiThread {
|
|
318
|
+
nativeCameraManager?.setFps(min, max)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@JavascriptInterface
|
|
324
|
+
fun flipVideoStream(optionsRaw: String?) {
|
|
325
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "flip video stream") {
|
|
326
|
+
android.util.Log.d("NativeBridge", "flipVideoStream called")
|
|
327
|
+
mainActivity.runOnUiThread {
|
|
328
|
+
nativeCameraManager?.flip()
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
242
333
|
@JavascriptInterface
|
|
243
334
|
fun openCamera(options: String?) {
|
|
244
335
|
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, "open camera") {
|
|
@@ -1023,6 +1114,17 @@ class NativeBridge(
|
|
|
1023
1114
|
* Handle permission request results
|
|
1024
1115
|
*/
|
|
1025
1116
|
fun handlePermissionResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
|
1117
|
+
if (requestCode == NativeCameraManager.PERMISSION_REQUEST_CODE) {
|
|
1118
|
+
val granted = grantResults.isNotEmpty() &&
|
|
1119
|
+
grantResults[0] == android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
1120
|
+
val cam = nativeCameraManager
|
|
1121
|
+
if (cam == null) {
|
|
1122
|
+
Log.w(TAG, "handlePermissionResult — nativeCameraManager is null, ignoring camera permission result")
|
|
1123
|
+
return
|
|
1124
|
+
}
|
|
1125
|
+
cam.onPermissionResult(granted)
|
|
1126
|
+
return
|
|
1127
|
+
}
|
|
1026
1128
|
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.NOTIFICATION_PERMISSION_STATUS, "handle permission result") {
|
|
1027
1129
|
// Delegate to notification manager which handles notification permissions
|
|
1028
1130
|
notificationManager.getNotificationUtils().handlePermissionResult(requestCode, permissions, grantResults)
|
|
@@ -1133,6 +1235,45 @@ class NativeBridge(
|
|
|
1133
1235
|
}
|
|
1134
1236
|
}
|
|
1135
1237
|
|
|
1238
|
+
@JavascriptInterface
|
|
1239
|
+
fun startTransition(optionsRaw: String?) {
|
|
1240
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_TRANSITION_CANCELLED, "start transition") {
|
|
1241
|
+
val options = try {
|
|
1242
|
+
JSONObject(optionsRaw ?: "{}")
|
|
1243
|
+
} catch (e: Exception) {
|
|
1244
|
+
Log.w(TAG, "startTransition — malformed JSON, using defaults: $optionsRaw")
|
|
1245
|
+
JSONObject()
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
val type = options.optString("type", "slide")
|
|
1249
|
+
val direction = options.optString("direction", "left")
|
|
1250
|
+
val duration = options.optInt("duration", 300).coerceIn(0, 2000)
|
|
1251
|
+
val timeout = options.optInt("timeout", maxOf(duration * 3, 800))
|
|
1252
|
+
|
|
1253
|
+
mainActivity.runOnUiThread {
|
|
1254
|
+
transitionManager.startTransition(type, direction, duration, timeout)
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
@JavascriptInterface
|
|
1260
|
+
fun commitTransition(optionsRaw: String?) {
|
|
1261
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_TRANSITION_CANCELLED, "commit transition") {
|
|
1262
|
+
mainActivity.runOnUiThread {
|
|
1263
|
+
transitionManager.commitTransition()
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
@JavascriptInterface
|
|
1269
|
+
fun cancelTransition(optionsRaw: String?) {
|
|
1270
|
+
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_TRANSITION_CANCELLED, "cancel transition") {
|
|
1271
|
+
mainActivity.runOnUiThread {
|
|
1272
|
+
transitionManager.cancelTransition()
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1136
1277
|
@JavascriptInterface
|
|
1137
1278
|
fun getSafeArea(data: String? = null) {
|
|
1138
1279
|
BridgeUtils.safeExecute(webView, BridgeUtils.WebEvents.ON_SAFE_AREA_INSETS_UPDATED, "get safe area") {
|
|
@@ -1164,6 +1305,13 @@ class NativeBridge(
|
|
|
1164
1305
|
// Cleanup NotificationManager
|
|
1165
1306
|
notificationManager.cleanup()
|
|
1166
1307
|
|
|
1308
|
+
// Cancel any in-flight transition and release overlay
|
|
1309
|
+
transitionManager.cleanup()
|
|
1310
|
+
|
|
1311
|
+
// Stop video stream
|
|
1312
|
+
nativeCameraManager?.cleanup()
|
|
1313
|
+
nativeCameraManager = null
|
|
1314
|
+
|
|
1167
1315
|
// Stop network monitoring
|
|
1168
1316
|
networkMonitor?.stop()
|
|
1169
1317
|
networkMonitor = null
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
package io.yourname.androidproject
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import android.graphics.RectF
|
|
5
|
+
import android.os.Handler
|
|
6
|
+
import android.os.Looper
|
|
7
|
+
import android.util.Log
|
|
8
|
+
import android.view.MotionEvent
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.webkit.WebView
|
|
11
|
+
import android.widget.TextView
|
|
12
|
+
import androidx.camera.view.PreviewView
|
|
13
|
+
import com.google.mlkit.vision.barcode.common.Barcode
|
|
14
|
+
import io.yourname.androidproject.camera.*
|
|
15
|
+
import io.yourname.androidproject.utils.BridgeUtils
|
|
16
|
+
import org.json.JSONObject
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Thin facade — wires all camera sub-components and exposes the public API
|
|
20
|
+
* that NativeBridge.kt calls. All logic lives in the camera/ package.
|
|
21
|
+
*/
|
|
22
|
+
class NativeCameraManager(
|
|
23
|
+
private val activity: MainActivity,
|
|
24
|
+
private val previewView: PreviewView,
|
|
25
|
+
private val webView: WebView,
|
|
26
|
+
private val debugOverlay: View? = null,
|
|
27
|
+
private val debugQrStatus: TextView? = null,
|
|
28
|
+
private val debugBarcodeOverlay: View? = null
|
|
29
|
+
) {
|
|
30
|
+
|
|
31
|
+
companion object {
|
|
32
|
+
private const val TAG = "NativeCameraManager"
|
|
33
|
+
const val PERMISSION_REQUEST_CODE = CameraSessionManager.PERMISSION_REQUEST_CODE
|
|
34
|
+
|
|
35
|
+
// QR overlay colors — change here to update both states
|
|
36
|
+
private const val COLOR_QR_IN_FRAME = "#5500FF00" // green, semi-transparent
|
|
37
|
+
private const val COLOR_QR_DETECTED = "#55FF6600" // orange, semi-transparent
|
|
38
|
+
private const val OVERLAY_HIDE_DELAY_MS = 200L
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private val stateMachine = VideoStreamStateMachine()
|
|
42
|
+
|
|
43
|
+
private val zoomController = ZoomController(
|
|
44
|
+
context = activity,
|
|
45
|
+
stateMachine = stateMachine,
|
|
46
|
+
onZoomChanged = { zoomLevel, minZoom, maxZoom ->
|
|
47
|
+
val payload = JSONObject().apply {
|
|
48
|
+
// zoomLevel = raw CameraX zoomRatio (1.0=1x, 2.0=2x)
|
|
49
|
+
put("zoomLevel", Math.round(zoomLevel * 10.0) / 10.0)
|
|
50
|
+
put("minZoom", Math.round(minZoom * 10.0) / 10.0)
|
|
51
|
+
put("maxZoom", Math.round(maxZoom * 10.0) / 10.0)
|
|
52
|
+
}
|
|
53
|
+
BridgeUtils.notifyWebJson(webView, BridgeUtils.WebEvents.ON_ZOOM_CHANGED, payload)
|
|
54
|
+
// Repaint overlay at new zoom level — barcode rect shifts as camera zooms
|
|
55
|
+
if (showQrDetected && overlayVisible) {
|
|
56
|
+
lastBarcodeScreenRect?.let { repaintQrOverlay(it, overlayIsOrange) }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
private val torchController = TorchController(
|
|
62
|
+
context = activity,
|
|
63
|
+
onTorchChanged = { enabled ->
|
|
64
|
+
val payload = JSONObject().apply { put("enabled", enabled) }
|
|
65
|
+
BridgeUtils.notifyWebJson(webView, BridgeUtils.WebEvents.ON_TORCH_CHANGED, payload)
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
private val barcodeDetector = BarcodeDetector(
|
|
70
|
+
zoomController = zoomController,
|
|
71
|
+
onDetected = { barcode, imgW, imgH -> onBarcodeDetected(barcode, imgW, imgH) }
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
private val sessionManager = CameraSessionManager(
|
|
75
|
+
activity = activity,
|
|
76
|
+
previewView = previewView,
|
|
77
|
+
stateMachine = stateMachine,
|
|
78
|
+
zoomController = zoomController,
|
|
79
|
+
torchController = torchController,
|
|
80
|
+
barcodeDetector = barcodeDetector,
|
|
81
|
+
onReady = {
|
|
82
|
+
BridgeUtils.notifyWeb(webView, BridgeUtils.WebEvents.ON_VIDEO_STREAM_READY)
|
|
83
|
+
},
|
|
84
|
+
onStopped = {
|
|
85
|
+
debugOverlay?.let { activity.runOnUiThread { it.visibility = View.GONE } }
|
|
86
|
+
BridgeUtils.notifyWeb(webView, BridgeUtils.WebEvents.ON_VIDEO_STREAM_STOPPED)
|
|
87
|
+
},
|
|
88
|
+
onError = { message ->
|
|
89
|
+
BridgeUtils.notifyWebError(webView, BridgeUtils.WebEvents.ON_CAMERA_ERROR, message)
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
private val holdController = HoldController(
|
|
94
|
+
stateMachine = stateMachine,
|
|
95
|
+
barcodeDetector = barcodeDetector
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// Viewfinder state — written on main thread, read on analyzer thread
|
|
99
|
+
@Volatile private var viewfinderScreenRect: RectF? = null
|
|
100
|
+
|
|
101
|
+
// QR detected overlay state — written on analyzer thread, read on main thread and vice versa
|
|
102
|
+
@Volatile private var showQrDetected: Boolean = false
|
|
103
|
+
@Volatile private var overlayIsOrange: Boolean = false // logical state — set before showQrOverlay
|
|
104
|
+
@Volatile private var overlayPaintedOrange: Boolean = false // what color was last painted
|
|
105
|
+
@Volatile private var overlayVisible: Boolean = false
|
|
106
|
+
@Volatile private var lastBarcodeScreenRect: RectF? = null
|
|
107
|
+
private val overlayHideHandler = Handler(Looper.getMainLooper())
|
|
108
|
+
@Volatile private var overlayHideRunnable: Runnable? = null
|
|
109
|
+
|
|
110
|
+
init {
|
|
111
|
+
stateMachine.addListener(object : VideoStreamStateListener {
|
|
112
|
+
override fun onStateChanged(prev: VideoStreamState, next: VideoStreamState) {
|
|
113
|
+
// Reset overlay when hold ends (new scan cycle) or stream stops
|
|
114
|
+
if ((prev == VideoStreamState.HOLD && next == VideoStreamState.STREAMING) ||
|
|
115
|
+
next == VideoStreamState.IDLE || next == VideoStreamState.STOPPING
|
|
116
|
+
) {
|
|
117
|
+
hideQrOverlay()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- Public API (called from NativeBridge) ----
|
|
124
|
+
|
|
125
|
+
fun start(facing: String = "back", viewfinderRectJson: JSONObject? = null, zoomOptions: JSONObject? = null, scanFormat: String = "all", fpsMin: Int? = null, fpsMax: Int? = null, showQrDetected: Boolean = false) {
|
|
126
|
+
if (stateMachine.isActive) return
|
|
127
|
+
|
|
128
|
+
this.showQrDetected = showQrDetected
|
|
129
|
+
this.overlayIsOrange = false
|
|
130
|
+
this.overlayPaintedOrange = false
|
|
131
|
+
this.overlayVisible = false
|
|
132
|
+
this.lastBarcodeScreenRect = null
|
|
133
|
+
|
|
134
|
+
val autoZoom = zoomOptions?.optBoolean("auto", false) ?: false
|
|
135
|
+
val initialZoomPct = zoomOptions?.optDouble("initial", 1.0)?.toFloat() ?: 1.0f
|
|
136
|
+
|
|
137
|
+
// Parse viewfinder rect and position debug overlay
|
|
138
|
+
viewfinderRectJson?.let { json ->
|
|
139
|
+
val result = ViewfinderMapper.parseViewfinderRect(json, webView)
|
|
140
|
+
viewfinderScreenRect = result?.first
|
|
141
|
+
result?.second?.let { jsRect -> positionDebugOverlay(jsRect) }
|
|
142
|
+
} ?: run {
|
|
143
|
+
viewfinderScreenRect = null
|
|
144
|
+
Log.w(TAG, "No viewfinderRect — all QR codes will be reported")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
stateMachine.transition(VideoStreamState.STARTING)
|
|
148
|
+
sessionManager.start(
|
|
149
|
+
facing = facing,
|
|
150
|
+
autoZoom = autoZoom,
|
|
151
|
+
initialZoomPct = initialZoomPct, // now Float multiplier (1.0 = 1x)
|
|
152
|
+
scanFormat = scanFormat,
|
|
153
|
+
fpsMin = fpsMin,
|
|
154
|
+
fpsMax = fpsMax
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fun stop() {
|
|
159
|
+
if (!stateMachine.isActive) return
|
|
160
|
+
holdController.reset()
|
|
161
|
+
stateMachine.transition(VideoStreamState.STOPPING)
|
|
162
|
+
sessionManager.stop()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
fun flip() {
|
|
166
|
+
if (!stateMachine.isActive) return
|
|
167
|
+
holdController.reset()
|
|
168
|
+
stateMachine.transition(VideoStreamState.FLIPPING)
|
|
169
|
+
sessionManager.flip()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fun setZoom(multiplier: Float) = zoomController.setZoom(multiplier)
|
|
173
|
+
|
|
174
|
+
fun setTorch(on: Boolean) = torchController.setTorch(on)
|
|
175
|
+
|
|
176
|
+
fun setFps(min: Int?, max: Int?) {
|
|
177
|
+
if (!stateMachine.isActive) return
|
|
178
|
+
sessionManager.setFps(min, max)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fun onTouchEvent(event: MotionEvent): Boolean = zoomController.onTouchEvent(event)
|
|
182
|
+
|
|
183
|
+
fun onPermissionResult(granted: Boolean) = sessionManager.onPermissionResult(granted)
|
|
184
|
+
|
|
185
|
+
fun cleanup() {
|
|
186
|
+
overlayHideRunnable?.let { overlayHideHandler.removeCallbacks(it) }
|
|
187
|
+
overlayHideRunnable = null
|
|
188
|
+
sessionManager.cleanup()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---- Detection handler ----
|
|
192
|
+
|
|
193
|
+
private fun onBarcodeDetected(barcode: Barcode, imageWidth: Int, imageHeight: Int) {
|
|
194
|
+
val value = barcode.rawValue ?: return
|
|
195
|
+
|
|
196
|
+
// Hold gate — defensive check for in-flight frames
|
|
197
|
+
if (stateMachine.state == VideoStreamState.HOLD) {
|
|
198
|
+
Log.d(TAG, "onBarcodeDetected — in hold, skipping: $value")
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// QR overlay — show green immediately (QR in frame), update position each frame
|
|
203
|
+
val barcodeScreenRect = barcode.boundingBox?.let { box ->
|
|
204
|
+
ViewfinderMapper.mapBarcodeToScreen(
|
|
205
|
+
box.left, box.top, box.right, box.bottom,
|
|
206
|
+
imageWidth, imageHeight,
|
|
207
|
+
previewView.width.toFloat(), previewView.height.toFloat()
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
if (showQrDetected && barcodeScreenRect != null) {
|
|
211
|
+
showQrOverlay(barcodeScreenRect, orange = overlayIsOrange)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Viewfinder region filter
|
|
215
|
+
val regionRect = viewfinderScreenRect
|
|
216
|
+
if (regionRect != null) {
|
|
217
|
+
if (barcodeScreenRect != null) {
|
|
218
|
+
if (!regionRect.contains(barcodeScreenRect)) {
|
|
219
|
+
updateDebugQrStatus(inside = false)
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
updateDebugQrStatus(inside = true)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Same-value dedup — enter hold either way
|
|
227
|
+
if (!holdController.isNewValue(value)) {
|
|
228
|
+
Log.d(TAG, "onBarcodeDetected — same value, suppressing: $value")
|
|
229
|
+
holdController.startHold()
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// New value confirmed — flip overlay to orange before firing event
|
|
234
|
+
if (showQrDetected && barcodeScreenRect != null) {
|
|
235
|
+
overlayIsOrange = true
|
|
236
|
+
showQrOverlay(barcodeScreenRect, orange = true)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
holdController.startHold()
|
|
240
|
+
|
|
241
|
+
val payload = JSONObject().apply {
|
|
242
|
+
put("value", value)
|
|
243
|
+
put("format", BarcodeDetector.formatName(barcode.format))
|
|
244
|
+
}
|
|
245
|
+
BridgeUtils.notifyWebJson(webView, BridgeUtils.WebEvents.ON_QR_DETECTED, payload)
|
|
246
|
+
Log.d(TAG, "QR detected (new): $value")
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ---- QR detected overlay ----
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Called on every ML Kit frame that has a barcode.
|
|
253
|
+
* If overlay is already visible: only reschedule the hide — no layout/color update (prevents flicker).
|
|
254
|
+
* If overlay is not visible: position it, set color, show it.
|
|
255
|
+
* lastBarcodeScreenRect is always updated so zoom repaints use the freshest position.
|
|
256
|
+
*/
|
|
257
|
+
private fun showQrOverlay(screenRect: RectF, orange: Boolean) {
|
|
258
|
+
lastBarcodeScreenRect = screenRect
|
|
259
|
+
|
|
260
|
+
// Reschedule hide regardless
|
|
261
|
+
overlayHideRunnable?.let { overlayHideHandler.removeCallbacks(it) }
|
|
262
|
+
val hideRunnable = Runnable { hideQrOverlayOnUiThread() }
|
|
263
|
+
overlayHideRunnable = hideRunnable
|
|
264
|
+
overlayHideHandler.postDelayed(hideRunnable, OVERLAY_HIDE_DELAY_MS)
|
|
265
|
+
|
|
266
|
+
if (overlayVisible && orange == overlayPaintedOrange) return // already painted at correct color — skip to prevent flicker
|
|
267
|
+
|
|
268
|
+
repaintQrOverlay(screenRect, orange)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Force-reposition and repaint the overlay. Called on first show and on zoom change.
|
|
273
|
+
* screenRect is in PreviewView/RelativeLayout space — no offset adjustment needed.
|
|
274
|
+
*/
|
|
275
|
+
private fun repaintQrOverlay(screenRect: RectF, orange: Boolean) {
|
|
276
|
+
val color = Color.parseColor(if (orange) COLOR_QR_DETECTED else COLOR_QR_IN_FRAME)
|
|
277
|
+
overlayVisible = true
|
|
278
|
+
overlayPaintedOrange = orange
|
|
279
|
+
activity.runOnUiThread {
|
|
280
|
+
debugBarcodeOverlay?.let { overlay ->
|
|
281
|
+
val lp = overlay.layoutParams as? android.widget.RelativeLayout.LayoutParams ?: return@runOnUiThread
|
|
282
|
+
lp.leftMargin = screenRect.left.toInt()
|
|
283
|
+
lp.topMargin = screenRect.top.toInt()
|
|
284
|
+
lp.width = screenRect.width().toInt()
|
|
285
|
+
lp.height = screenRect.height().toInt()
|
|
286
|
+
overlay.layoutParams = lp
|
|
287
|
+
overlay.setBackgroundColor(color)
|
|
288
|
+
overlay.visibility = View.VISIBLE
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private fun hideQrOverlay() {
|
|
294
|
+
overlayHideRunnable?.let { overlayHideHandler.removeCallbacks(it) }
|
|
295
|
+
overlayHideRunnable = null
|
|
296
|
+
overlayIsOrange = false
|
|
297
|
+
overlayPaintedOrange = false
|
|
298
|
+
lastBarcodeScreenRect = null
|
|
299
|
+
hideQrOverlayOnUiThread()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private fun hideQrOverlayOnUiThread() {
|
|
303
|
+
overlayVisible = false
|
|
304
|
+
activity.runOnUiThread {
|
|
305
|
+
debugBarcodeOverlay?.visibility = View.GONE
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ---- Debug overlay helpers ----
|
|
310
|
+
|
|
311
|
+
private fun positionDebugOverlay(jsRect: RectF) {
|
|
312
|
+
activity.runOnUiThread {
|
|
313
|
+
debugOverlay?.let { overlay ->
|
|
314
|
+
val lp = overlay.layoutParams as? android.widget.RelativeLayout.LayoutParams ?: return@runOnUiThread
|
|
315
|
+
lp.leftMargin = jsRect.left.toInt()
|
|
316
|
+
lp.topMargin = jsRect.top.toInt()
|
|
317
|
+
lp.width = jsRect.width().toInt()
|
|
318
|
+
lp.height = jsRect.height().toInt()
|
|
319
|
+
overlay.layoutParams = lp
|
|
320
|
+
overlay.setBackgroundResource(R.drawable.debug_viewfinder_border)
|
|
321
|
+
overlay.visibility = View.VISIBLE
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private fun updateDebugQrStatus(inside: Boolean) {
|
|
327
|
+
activity.runOnUiThread {
|
|
328
|
+
if (inside) {
|
|
329
|
+
debugQrStatus?.setTextColor(Color.parseColor("#34C759"))
|
|
330
|
+
debugQrStatus?.text = "⬤ QR inside"
|
|
331
|
+
} else {
|
|
332
|
+
debugQrStatus?.setTextColor(Color.parseColor("#FF3B30"))
|
|
333
|
+
debugQrStatus?.text = "⬤ QR outside"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|