package com.docvcapture.module import android.util.Log import com.docvcapture.TruliooCaptureObject import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.modules.core.DeviceEventManagerModule import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch class TruliooCaptureSdkModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), ITruliooCaptureSdk { private val truliooCapture = TruliooCaptureObject.getInstance() private val supervisorJob = Dispatchers.Default + SupervisorJob() + CoroutineExceptionHandler { coroutineContext, throwable -> Log.e("TruliooCaptureSdkModule", "Coroutine Error occurred: $coroutineContext - $throwable") } private val coroutineScope = CoroutineScope(supervisorJob) private val jobMap = mutableMapOf() override fun getName() = NAME @ReactMethod override fun initialize(shortCode: String, previewMode: Boolean, promise: Promise) { coroutineScope.launch { truliooCapture.initialize(shortCode, previewMode) .then { promise.resolve(it.substringAfter("Success:")) }.onFail { promise.reject(Error(it.message)) } } } @ReactMethod override fun createCameraComponent(cameraConfigJson: String, promise: Promise) { val cameraConfig = TruliooCaptureObject.createTruliooCameraConfig(cameraConfigJson) truliooCapture.createCameraComponent(cameraConfig) promise.resolve(true) } @ReactMethod override fun startFeedbackCallback(cameraId: String, eventName: String) { val jobIdentifier = "${START_FEEDBACK_CALLBACK_JOB_NAME}-${cameraId}" jobMap[jobIdentifier]?.cancel() val startFeedbackCallbackJob = coroutineScope.launch { truliooCapture.startFeedbackCallback(cameraId)?.collect { sendEvent(eventName, it.toJsonString()) } } jobMap[jobIdentifier] = startFeedbackCallbackJob } @ReactMethod override fun startFeedbackPromise(cameraId: String, promise: Promise) { coroutineScope.launch { truliooCapture.startFeedbackPromise(cameraId).then { if (it != null) { promise.resolve(it.toJsonString()) cancelJob(ON_FEEDBACK_STATE_JOB_NAME) } else { promise.reject("1337", "Async operation cancelled.") } }.onFail { promise.reject(Error("Error in startFeedback: ${it.message}")) } } } @ReactMethod override fun stopFeedback(cameraId: String, promise: Promise) { coroutineScope.launch { val result = truliooCapture.stopFeedback(cameraId) cancelJob(ON_FEEDBACK_STATE_JOB_NAME) cancelJob("${START_FEEDBACK_CALLBACK_JOB_NAME}-${cameraId}") promise.resolve(result) } } @ReactMethod override fun resumeCamera(cameraId: String) { truliooCapture.resumeCamera(cameraId) } @ReactMethod override fun onFeedbackState(eventName: String) { jobMap[ON_FEEDBACK_STATE_JOB_NAME]?.cancel() val onFeedbackStateJob: Job = coroutineScope.launch { truliooCapture.onFeedbackState().collectEvents { sendEvent(eventName, it.toString()) } } jobMap[ON_FEEDBACK_STATE_JOB_NAME] = onFeedbackStateJob } @ReactMethod override fun verifyImage(imageId: String, promise: Promise) { coroutineScope.launch { truliooCapture.verifyImage(imageId).then { promise.resolve(it.toJsonString()) }.onFail { promise.reject(Error("Error in verifyImage: ${it.message}")) } } } @ReactMethod override fun acceptImage(imageId: String, promise: Promise) { coroutineScope.launch { truliooCapture.acceptImage(imageId).then { promise.resolve(it) }.onFail { promise.reject(Error("Error in acceptImage: ${it.message}")) } } } @ReactMethod override fun submitTransaction(promise: Promise) { coroutineScope.launch { truliooCapture.submitTransaction().then { promise.resolve(true) }.onFail { promise.reject(Error(it.message)) } } } @ReactMethod override fun captureLatestFrame(cameraId: String, promise: Promise) { coroutineScope.launch { truliooCapture.captureLatestFrame(cameraId).then { promise.resolve(it.toJsonString()) cancelJob(ON_FEEDBACK_STATE_JOB_NAME) cancelJob(START_FEEDBACK_CALLBACK_JOB_NAME) }.onFail { promise.reject(Error("Error in captureLatestFrame: ${it.message}")) } } } @ReactMethod fun addListener(type: String?) { // Required for RN built in Event Emitter Calls. } @ReactMethod fun removeListeners(type: Int?) { // Required for RN built in Event Emitter Calls. } private fun sendEvent(eventName: String, eventData: Any?) { reactApplicationContext .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) .emit(eventName, eventData) } private fun cancelJob(jobName: String) { // Cancel any job that also has the same alias as the jobName if // not a specific job name is given. val jobsToRemove = jobMap.keys.filter { it.contains(jobName) } for (jobName in jobsToRemove) { jobMap.remove(jobName)?.cancel() } } companion object { const val NAME = "TruliooCaptureSdkModule" const val ON_FEEDBACK_STATE_JOB_NAME = "OnFeedbackStateJob" const val START_FEEDBACK_CALLBACK_JOB_NAME = "StartFeedbackCallbackJob" } }