package com.dynamsoft.reactnativelib.dce import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.util.Log import android.util.Size import androidx.lifecycle.Lifecycle import com.dynamsoft.dce.* import com.dynamsoft.dce.utils.PermissionUtil import com.dynamsoft.reactnativelib.basicutils.toPoint import com.dynamsoft.reactnativelib.basicutils.toScanRegion import com.dynamsoft.reactnativelib.basicutils.toWritableMap import com.dynamsoft.reactnativelib.core.ImageSourceAdapterModuleImpl.Companion.mapISA import com.facebook.react.bridge.* import com.facebook.react.uimanager.UIManagerHelper import kotlinx.coroutines.* class CameraEnhancerModuleImpl(private val reactContext: ReactApplicationContext) : LifecycleEventListener { companion object { const val NAME = "DynamsoftCameraView" const val TAG = "CameraEnhancerModule" } private var ifOpenCalled = false private var cameraLifecycleOwner = CameraLifeCycleOwner() private var camera: CameraEnhancer? = null private var currentCameraViewId: Int? = null init { reactContext.addLifecycleEventListener(this) } fun getName(): String = NAME override fun onHostResume() { cameraLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START) cameraLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) if(ifOpenCalled) { open() } } override fun onHostPause() { cameraLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE) cameraLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP) } override fun onHostDestroy() { //Don't handle ON_DESTROY, which can cause problems on some devices when some apps that have started background services return to the foreground //cameraLifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) } @ReactMethod fun requestCameraPermission() { reactContext.currentActivity?.apply { PermissionUtil.requestCameraPermission(this) } } @ReactMethod(isBlockingSynchronousMethod = true) fun createInstance(): String { if(camera == null) { camera = CameraEnhancer(cameraLifecycleOwner).apply { mapISA[this.toString()] = this } } return camera.toString() } @ReactMethod(isBlockingSynchronousMethod = true) fun destroyInstance(id: String): Boolean { if(camera?.toString() == id) { camera?.close() camera?.cameraView = null currentCameraViewId = null camera = null } mapISA.remove(id) return true } @ReactMethod fun open() { ifOpenCalled = true if (currentCameraViewId != null && (camera?.cameraView == null || camera?.cameraView?.id != currentCameraViewId)) { findAndSetCameraView(currentCameraViewId!!) { camera?.open() } } else { camera?.open() } } @ReactMethod fun close() { ifOpenCalled = false camera?.close() } @ReactMethod fun setCameraView(viewTag: Int) { if(viewTag == 0) { return } currentCameraViewId = viewTag findAndSetCameraView(viewTag) } @ReactMethod fun selectCamera(@EnumCameraPosition position: Int) { camera?.selectCamera(position) } @ReactMethod fun getCameraPosition(promise: Promise) { promise.resolve(camera?.cameraPosition) } @ReactMethod fun setFocus(x: Float, y: Float, @EnumFocusMode focusMode: Int) { camera?.setFocus(PointF(x, y), focusMode) } @ReactMethod fun getFocusMode(promise: Promise) { promise.resolve(camera?.focusMode) } @ReactMethod fun setZoomFactor(factor: Float) { camera?.zoomFactor = factor } @ReactMethod fun getZoomFactor(promise: Promise) { promise.resolve(camera?.zoomFactor) } @ReactMethod fun enableEnhancedFeatures(features: Int) { camera?.enableEnhancedFeatures(features) } @ReactMethod fun disableEnhancedFeatures(features: Int) { camera?.disableEnhancedFeatures(features) } @ReactMethod fun setScanRegion(region: ReadableMap?) { camera?.scanRegion = region?.toScanRegion() } @ReactMethod fun getScanRegion(promise: Promise) { promise.resolve(camera?.scanRegion?.toWritableMap()) } @ReactMethod fun turnOnTorch() { camera?.turnOnTorch() } @ReactMethod fun turnOffTorch() { camera?.turnOffTorch() } @ReactMethod fun setResolution(resolution: Int) { if(resolution == 4) { camera?.setResolution(EnumResolution.RESOLUTION_4K) } else if(resolution == 5) { camera?.setResolution(EnumResolution.RESOLUTION_MAX) } else { camera?.setResolution(resolution) } } @ReactMethod fun getResolution(promise: Promise) { val resolution = camera?.resolution ?: Size(0,0) promise.resolve(Arguments.createMap().apply { putInt("width", resolution.width) putInt("height", resolution.height) }) } @ReactMethod(isBlockingSynchronousMethod = true) fun convertPointToViewCoordinates(point: ReadableMap) : WritableMap { val nativePoint = point.toPoint() val viewPoint = camera?.convertPointToViewCoordinates(nativePoint) ?: Point(0,0) val density = reactContext.resources.displayMetrics.density val viewPointByDp = Point((viewPoint.x / density).toInt(), (viewPoint.y / density).toInt()) return viewPointByDp.toWritableMap() } @ReactMethod(isBlockingSynchronousMethod = true) fun convertRectToViewCoordinates(dsRect: ReadableMap): WritableMap { val nativeRect = dsRect.toScanRegion() val viewRect = camera?.convertRectToViewCoordinates(nativeRect) ?: Rect(0, 0, 0, 0) val density = reactContext.resources.displayMetrics.density val viewRectByDp = Rect( (viewRect.left / density).toInt(), (viewRect.top / density).toInt(), (viewRect.right / density).toInt(), (viewRect.bottom / density).toInt() ) return viewRectByDp.toWritableMap() } @ReactMethod fun beep() { Feedback.beep() } @ReactMethod fun vibrate() { Feedback.vibrate() } private fun findAndSetCameraView(viewTag: Int, afterSetCameraView: () -> Unit = {}) { CoroutineScope(Dispatchers.Default).launch(CoroutineName("setCameraView")) { val cameraView = findCameraView(viewTag) if (cameraView == null) { Log.e(TAG, "setCameraView: Can't not find cameraView!(viewId = $viewTag)") return@launch } MainScope().launch { Log.e(TAG, "findAndSetCameraView: before setCameraView $camera ${cameraView.id}", ) camera?.cameraView = cameraView afterSetCameraView() } } } private suspend fun findCameraView(viewId: Int): CameraView? = withTimeoutOrNull(300) { while (true) { val view = withContext(Dispatchers.Main) { try { UIManagerHelper.getUIManager(reactContext, viewId)?.resolveView(viewId) as? CameraView } catch (e: Exception) { null } } if (view != null) { return@withTimeoutOrNull view } if (!isActive) { break } } return@withTimeoutOrNull null } }