package expo.modules.camera.analyzers import android.graphics.Bitmap import android.util.Log import com.google.android.gms.tasks.Task import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode import com.google.mlkit.vision.common.InputImage import expo.modules.camera.utils.BarCodeScannerResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException class MLKitBarCodeScanner { private var barCodeTypes: List? = null private var barcodeScannerOptions = BarcodeScannerOptions.Builder() .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS) .build() private var barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions) suspend fun scan(bitmap: Bitmap): List = withContext(Dispatchers.IO) { val inputImage = InputImage.fromBitmap(bitmap, 0) try { val result: List = barcodeScanner.process(inputImage).await() val results = mutableListOf() if (result.isEmpty()) { return@withContext results } for (barcode in result) { results.add(BarCodeScannerResultSerializer.parseBarcodeScanningResult(barcode, inputImage)) } return@withContext results } catch (e: Exception) { Log.e(TAG, "Failed to detect barcode: " + e.message) return@withContext emptyList() } } fun setSettings(formats: List) { if (areNewAndOldBarCodeTypesEqual(formats)) { return } val barcodeFormats = formats.reduce { acc, it -> acc or it } barCodeTypes = formats barcodeScannerOptions = BarcodeScannerOptions.Builder() .setBarcodeFormats(barcodeFormats) .build() barcodeScanner = BarcodeScanning.getClient(barcodeScannerOptions) } private fun areNewAndOldBarCodeTypesEqual(newBarCodeTypes: List): Boolean { return barCodeTypes?.toSet() == newBarCodeTypes.toSet() } companion object { private val TAG = MLKitBarCodeScanner::class.java.simpleName } } suspend fun Task.await(): T = suspendCancellableCoroutine { continuation -> addOnSuccessListener { result -> continuation.resume(result) } addOnFailureListener { exception -> continuation.resumeWithException(exception) } addOnCanceledListener { continuation.cancel() } }