From ccb6cf3bbf3fec3fe112d496ca856822bd8d1445 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Feb 2026 16:22:26 +0200 Subject: [PATCH 1/2] [MS-1341] Adding on tap focus for the OCR scanner --- .../ExternalCredentialScanOcrFragment.kt | 27 ++++++++++++++++--- .../usecase/ProvideCameraListenerUseCase.kt | 5 +++- 2 files changed, 28 insertions(+), 4 deletions(-) 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 6a55b6e4a2..9b8c3594ca 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 @@ -7,6 +7,7 @@ import android.provider.Settings import android.view.View import android.view.ViewPropertyAnimator import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.Camera import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException @@ -38,6 +39,7 @@ import com.simprints.feature.externalcredential.screens.scanocr.usecase.ProvideC import com.simprints.feature.externalcredential.screens.search.model.ScannedCredential import com.simprints.infra.logging.LoggingConstants.CrashReportTag.MULTI_FACTOR_ID import com.simprints.infra.logging.Simber +import com.simprints.infra.uibase.camera.qrscan.CameraFocusManager import com.simprints.infra.uibase.navigation.navigateSafely import com.simprints.infra.uibase.view.applySystemBarInsets import com.simprints.infra.uibase.view.fadeIn @@ -87,6 +89,7 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex private var isAnimatingCompletion: Boolean = false private var pendingFinishAction: (() -> Unit)? = null private var ocrPreProcessingJob: Job? = null + private var camera: Camera? = null @Inject lateinit var viewModelFactory: ExternalCredentialScanOcrViewModel.Factory @@ -97,6 +100,9 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex @Inject lateinit var provideCameraListenerUseCase: ProvideCameraListenerUseCase + @Inject + lateinit var cameraFocusManagerFactory: CameraFocusManager.Factory + @Inject @DispatcherBG lateinit var bgDispatcher: CoroutineDispatcher @@ -114,7 +120,10 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex super.onResume() val currentPermission = requireActivity().getCurrentPermissionStatus(CAMERA) when (currentPermission) { - PermissionStatus.Granted -> initializeFragment() + PermissionStatus.Granted -> { + initializeFragment() + } + PermissionStatus.Denied -> { // Permission dialog was already displayed, and user denied permissions. Showing rationale so to avoid constantly-appearing // system dialog. @@ -167,8 +176,13 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex } } - ScanOcrState.NotScanning -> renderInitialState() - ScanOcrState.Complete -> animateCompletionState() + ScanOcrState.NotScanning -> { + renderInitialState() + } + + ScanOcrState.Complete -> { + animateCompletionState() + } } } @@ -198,6 +212,12 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex onImageCaptureReady = { capture -> imageCapture = capture }, + onCameraReady = { camera -> + this.camera = camera + val cameraFocusManager = cameraFocusManagerFactory.create(MULTI_FACTOR_ID) + cameraFocusManager.setUpFocusOnTap(binding.preview, camera) + cameraFocusManager.setUpAutoFocus(binding.preview, camera) + }, ) cameraProviderFuture.addListener(cameraListener, ContextCompat.getMainExecutor(requireContext())) } @@ -353,6 +373,7 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex if (::cameraExecutor.isInitialized) { cameraExecutor.shutdown() } + camera = null } private fun stopOcr() { 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 8e0f1c09aa..fb9d8ceb22 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 @@ -1,6 +1,7 @@ package com.simprints.feature.externalcredential.screens.scanocr.usecase import androidx.camera.core.AspectRatio +import androidx.camera.core.Camera import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture @@ -21,6 +22,7 @@ internal class ProvideCameraListenerUseCase @Inject constructor() { viewLifecycleOwner: LifecycleOwner, onImageAnalysisReady: (ImageAnalysis) -> Unit, onImageCaptureReady: (ImageCapture) -> Unit, + onCameraReady: (Camera) -> Unit, ) = Runnable { val cameraProvider = cameraProviderFuture.get() val aspectRatio = AspectRatio.RATIO_16_9 @@ -48,9 +50,10 @@ internal class ProvideCameraListenerUseCase @Inject constructor() { try { cameraProvider.unbindAll() - cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis) + val camera = cameraProvider.bindToLifecycle(viewLifecycleOwner, cameraSelector, preview, imageCapture, imageAnalysis) onImageAnalysisReady(imageAnalysis) onImageCaptureReady(imageCapture) + onCameraReady(camera) } catch (e: Exception) { Simber.e("Camera binding failed in OCR", e, MULTI_FACTOR_ID) } From 590ae624fa4c603f6e1610ec63267b9604cd8d11 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 10 Feb 2026 17:02:56 +0200 Subject: [PATCH 2/2] [MS-1341] Removing 'camera' field and verifying lifecycle state before using binding --- .../scanocr/ExternalCredentialScanOcrFragment.kt | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 9b8c3594ca..162bf50e8b 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 @@ -7,7 +7,6 @@ import android.provider.Settings import android.view.View import android.view.ViewPropertyAnimator import androidx.activity.result.contract.ActivityResultContracts -import androidx.camera.core.Camera import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageCapture import androidx.camera.core.ImageCaptureException @@ -20,6 +19,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope @@ -89,7 +89,6 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex private var isAnimatingCompletion: Boolean = false private var pendingFinishAction: (() -> Unit)? = null private var ocrPreProcessingJob: Job? = null - private var camera: Camera? = null @Inject lateinit var viewModelFactory: ExternalCredentialScanOcrViewModel.Factory @@ -213,10 +212,11 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex imageCapture = capture }, onCameraReady = { camera -> - this.camera = camera - val cameraFocusManager = cameraFocusManagerFactory.create(MULTI_FACTOR_ID) - cameraFocusManager.setUpFocusOnTap(binding.preview, camera) - cameraFocusManager.setUpAutoFocus(binding.preview, camera) + if (lifecycle.currentState == Lifecycle.State.RESUMED) { + val cameraFocusManager = cameraFocusManagerFactory.create(MULTI_FACTOR_ID) + cameraFocusManager.setUpFocusOnTap(binding.preview, camera) + cameraFocusManager.setUpAutoFocus(binding.preview, camera) + } }, ) cameraProviderFuture.addListener(cameraListener, ContextCompat.getMainExecutor(requireContext())) @@ -373,7 +373,6 @@ internal class ExternalCredentialScanOcrFragment : Fragment(R.layout.fragment_ex if (::cameraExecutor.isInitialized) { cameraExecutor.shutdown() } - camera = null } private fun stopOcr() {