diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrFragment.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrFragment.kt index 33957a011c..78cdb8efb6 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrFragment.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrFragment.kt @@ -8,6 +8,9 @@ import android.view.View import android.view.ViewPropertyAnimator import androidx.activity.result.contract.ActivityResultContracts import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.ImageProxy import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.content.ContextCompat import androidx.core.net.toUri @@ -77,6 +80,7 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex private var previousPermissionStatus: PermissionStatus? = null private lateinit var cameraExecutor: ExecutorService private lateinit var imageAnalysis: ImageAnalysis + private lateinit var imageCapture: ImageCapture private var progressAnimator: ViewPropertyAnimator? = null private var checkAnimator: ViewPropertyAnimator? = null private var isAnimatingCompletion: Boolean = false @@ -186,10 +190,13 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex cameraProviderFuture = cameraProviderFuture, surfaceProvider = binding.preview.surfaceProvider, viewLifecycleOwner = viewLifecycleOwner, - onImageAnalysisReady = { - imageAnalysis = it + onImageAnalysisReady = { analysis -> + imageAnalysis = analysis onComplete() }, + onImageCaptureReady = { capture -> + imageCapture = capture + }, ) cameraProviderFuture.addListener(cameraListener, ContextCompat.getMainExecutor(requireContext())) } @@ -278,22 +285,39 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex return@setAnalyzer } viewModel.ocrOnFrameStarted() - lifecycleScope.launch(bgDispatcher) { - try { - val (bitmap, imageInfo) = imageProxy.toBitmap() to imageProxy.imageInfo - val cropConfig: OcrCropConfig = buildOcrCropConfigUseCase( - rotationDegrees = imageInfo.rotationDegrees, - cameraPreview = binding.preview, - documentScannerArea = binding.documentScannerArea, - ) - viewModel.runOcrOnFrame(frame = bitmap, cropConfig) - } finally { - imageProxy.close() - } - } + // Running OCR as often as we can while camera feedback is displayed to the user + captureFullResImageForOcr() + imageProxy.close() } } + private fun captureFullResImageForOcr() { + imageCapture.takePicture( + cameraExecutor, + object : ImageCapture.OnImageCapturedCallback() { + override fun onCaptureSuccess(imageProxy: ImageProxy) { + lifecycleScope.launch(bgDispatcher) { + try { + val (bitmap, imageInfo) = imageProxy.toBitmap() to imageProxy.imageInfo + val cropConfig: OcrCropConfig = buildOcrCropConfigUseCase( + rotationDegrees = imageInfo.rotationDegrees, + cameraPreview = binding.preview, + documentScannerArea = binding.documentScannerArea, + ) + viewModel.runOcrOnFrame(frame = bitmap, cropConfig) + } finally { + imageProxy.close() + } + } + } + + override fun onError(e: ImageCaptureException) { + Simber.e("Photo capture failed in OCR", e, MULTI_FACTOR_ID) + } + }, + ) + } + private fun stopCamera() { if (::cameraExecutor.isInitialized) { cameraExecutor.shutdown() diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt index 72e4f155b1..eca7949b36 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModel.kt @@ -52,7 +52,7 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor( @DispatcherBG private val bgDispatcher: CoroutineDispatcher, ) : ViewModel() { @AssistedFactory - interface Factory { + fun interface Factory { fun create(ocrDocumentType: OcrDocumentType): ExternalCredentialScanOcrViewModel } @@ -168,6 +168,6 @@ internal class ExternalCredentialScanOcrViewModel @AssistedInject constructor( } companion object { - private const val SUCCESSFUL_SCANS_REQUIRED = 5 + private const val SUCCESSFUL_SCANS_REQUIRED = 3 } } diff --git a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/usecase/ProvideCameraListenerUseCase.kt b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/usecase/ProvideCameraListenerUseCase.kt index 6a0e4a9269..8e0f1c09aa 100644 --- a/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/usecase/ProvideCameraListenerUseCase.kt +++ b/feature/external-credential/src/main/java/com/simprints/feature/externalcredential/screens/scanocr/usecase/ProvideCameraListenerUseCase.kt @@ -20,6 +20,7 @@ internal class ProvideCameraListenerUseCase @Inject constructor() { surfaceProvider: Preview.SurfaceProvider, viewLifecycleOwner: LifecycleOwner, onImageAnalysisReady: (ImageAnalysis) -> Unit, + onImageCaptureReady: (ImageCapture) -> Unit, ) = Runnable { val cameraProvider = cameraProviderFuture.get() val aspectRatio = AspectRatio.RATIO_16_9 @@ -49,6 +50,7 @@ internal class ProvideCameraListenerUseCase @Inject constructor() { cameraProvider.unbindAll() cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis) onImageAnalysisReady(imageAnalysis) + onImageCaptureReady(imageCapture) } catch (e: Exception) { Simber.e("Camera binding failed in OCR", e, MULTI_FACTOR_ID) } diff --git a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt index bf70d7fc5c..74624f2de2 100644 --- a/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt +++ b/feature/external-credential/src/test/java/com/simprints/feature/externalcredential/screens/scanocr/ExternalCredentialScanOcrViewModelTest.kt @@ -110,7 +110,7 @@ internal class ExternalCredentialScanOcrViewModelTest { val state = observer.value() as ScanOcrState.ScanningInProgress assertThat(state.ocrDocumentType).isEqualTo(documentType) assertThat(state.successfulCaptures).isEqualTo(0) - assertThat(state.scansRequired).isEqualTo(5) + assertThat(state.scansRequired).isEqualTo(3) } @Test