/* * This file is part of the Scandit Data Capture SDK * * Copyright (C) 2019- Scandit AG. All rights reserved. */ package com.scandit.datacapture.cordova.core.handlers import android.app.Activity import android.content.Context import android.graphics.Color import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import com.scandit.datacapture.cordova.core.data.ResizeAndMoveInfo import com.scandit.datacapture.cordova.core.utils.bringContainerToFront import com.scandit.datacapture.cordova.core.utils.pxFromDp import com.scandit.datacapture.cordova.core.utils.removeFromParent import com.scandit.datacapture.core.ui.DataCaptureView import java.lang.ref.WeakReference class DataCaptureViewHandler { private var latestInfo: ResizeAndMoveInfo = ResizeAndMoveInfo(0f, 0f, 0f, 0f, false) private var isVisible: Boolean = false private var dataCaptureViewReference: WeakReference? = null private var webViewReference: WeakReference? = null private var backgroundViewReference: WeakReference? = null val dataCaptureView: DataCaptureView? get() = dataCaptureViewReference?.get() private val webView: View? get() = webViewReference?.get() private val backgroundView: View? get() = backgroundViewReference?.get() fun attachDataCaptureView(dataCaptureView: DataCaptureView, activity: Activity) { addDataCaptureView(dataCaptureView, activity) } fun attachWebView(webView: View) { if (this.webView != webView) { webViewReference = WeakReference(webView) } } fun setVisible() { isVisible = true render() } fun setInvisible() { isVisible = false render() } fun setResizeAndMoveInfo(info: ResizeAndMoveInfo) { latestInfo = info render() } fun disposeCurrentWebView() { webViewReference = null val dcView = dataCaptureView ?: return removeDataCaptureView(dcView) } private fun disposeCurrentBackgroundView() { val backgroundView = backgroundView ?: return backgroundViewReference = null removeView(backgroundView) } private fun createBackgroundView(context: Context): View = View(context).apply { setBackgroundColor(Color.WHITE) } private fun addDataCaptureView(dataCaptureView: DataCaptureView, activity: Activity) { dataCaptureViewReference = WeakReference(dataCaptureView) activity.runOnUiThread { val backgroundView = createBackgroundView(activity) backgroundViewReference = WeakReference(backgroundView) activity.addContentView( backgroundView, ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) ) // Bring the WebView's content-frame container to the front so backgroundView // (added via addContentView) doesn't end up covering the whole WebView on // cordova-android 15+, where the WebView is wrapped in an intermediate // rootLayout. webView?.bringContainerToFront() webView?.setBackgroundColor(Color.TRANSPARENT) dataCaptureView.parent?.let { (it as ViewGroup).removeView(dataCaptureView) } // Add the DataCaptureView into the WebView's own parent so the two views // share a single coordinate frame. latestInfo positions come from the JS // getBoundingClientRect() and are therefore WebView-relative; placing the // overlay as a sibling of the WebView means those coordinates map directly // (offset only by the WebView's own position within that shared parent) // with no screen-space translation, and the overlay is clipped to the same // bounds as the WebView. Falls back to the activity content frame if the // WebView isn't attached yet. val layoutParams = ViewGroup.LayoutParams( latestInfo.width.pxFromDp(activity).toInt(), latestInfo.height.pxFromDp(activity).toInt() ) val container = webView?.parent as? ViewGroup if (container != null) { container.addView(dataCaptureView, layoutParams) } else { activity.addContentView(dataCaptureView, layoutParams) } render() } } fun removeDataCaptureView(dataCaptureView: DataCaptureView) { if (dataCaptureView == dataCaptureViewReference?.get()) { dataCaptureViewReference = null disposeCurrentBackgroundView() } removeView(dataCaptureView) } private fun removeView(view: View) { view.post { view.removeFromParent() } } // Update the view visibility, position and size. private fun render() { val view = dataCaptureView ?: return renderNoAnimate(view) } private fun renderNoAnimate(dataCaptureView: DataCaptureView) { dataCaptureView.post { dataCaptureView.visibility = if (isVisible) View.VISIBLE else View.GONE // DataCaptureView shares the WebView's parent, so latestInfo (WebView-relative, // from getBoundingClientRect()) maps directly, offset only by the WebView's own // position within that shared parent. No screen-space translation needed, and // this stays correct regardless of any edge-to-edge inset applied to the WebView. val webView = webView val context = dataCaptureView.context dataCaptureView.x = latestInfo.left.pxFromDp(context) + (webView?.x ?: 0f) dataCaptureView.y = latestInfo.top.pxFromDp(context) + (webView?.y ?: 0f) dataCaptureView.layoutParams.apply { width = latestInfo.width.pxFromDp(context).toInt() height = latestInfo.height.pxFromDp(context).toInt() } if (latestInfo.shouldBeUnderWebView) { webView?.bringToFront() } else { dataCaptureView.bringToFront() } dataCaptureView.requestLayout() } } }