diff --git a/feature/login/build.gradle.kts b/feature/login/build.gradle.kts index e18cba5e66..7a1a03fa2e 100644 --- a/feature/login/build.gradle.kts +++ b/feature/login/build.gradle.kts @@ -12,13 +12,8 @@ dependencies { implementation(project(":infra:auth-store")) implementation(project(":infra:auth-logic")) + implementation(project(":infra:camera")) // Integrity check related implementation(libs.playServices.base) - - // QR scanner related - implementation(libs.androidX.cameraX.core) - implementation(libs.androidX.cameraX.lifecycle) - implementation(libs.androidX.cameraX.view) - implementation(libs.playServices.barcode) } diff --git a/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrScannerFragment.kt b/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrScannerFragment.kt index 143ff37797..48167fd49a 100644 --- a/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrScannerFragment.kt +++ b/feature/login/src/main/java/com/simprints/feature/login/screens/qrscanner/QrScannerFragment.kt @@ -13,16 +13,17 @@ import androidx.navigation.fragment.findNavController import com.simprints.core.tools.extensions.hasPermission import com.simprints.feature.login.R import com.simprints.feature.login.databinding.FragmentQrScannerBinding +import com.simprints.infra.camera.CameraFrameProvider +import com.simprints.infra.camera.postprocess.DetectQrCodeUseCase +import com.simprints.infra.camera.usecase.GetBoundsRelativeToParentUseCase import com.simprints.infra.logging.LoggingConstants -import com.simprints.infra.uibase.camera.qrscan.CameraHelper -import com.simprints.infra.uibase.camera.qrscan.QrCodeAnalyzer import com.simprints.infra.logging.Simber -import com.simprints.infra.uibase.view.applySystemBarInsets import com.simprints.infra.uibase.navigation.finishWithResult +import com.simprints.infra.uibase.view.applySystemBarInsets +import com.simprints.infra.uibase.view.awaitLayout import com.simprints.infra.uibase.viewbinding.viewBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.catch -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @@ -30,16 +31,17 @@ import javax.inject.Inject internal class QrScannerFragment : Fragment(R.layout.fragment_qr_scanner) { private val binding by viewBinding(FragmentQrScannerBinding::bind) private val crashReportTag = LoggingConstants.CrashReportTag.LOGIN + @Inject - lateinit var cameraHelperFactory: CameraHelper.Factory + lateinit var cameraFrameProvider: CameraFrameProvider + @Inject - lateinit var qrCodeAnalyzerFactory: QrCodeAnalyzer.Factory + lateinit var getBoundsRelativeToParentUseCase: GetBoundsRelativeToParentUseCase - private val cameraHelper: CameraHelper by lazy { - cameraHelperFactory.create(crashReportTag) - } - private val qrCodeAnalyzer by lazy { - qrCodeAnalyzerFactory.create(cropConfig = null, crashReportTag = crashReportTag) + @Inject + lateinit var detectQrCodeUseCaseFactory: DetectQrCodeUseCase.Factory + private val detectQrCodeUseCase: DetectQrCodeUseCase by lazy { + detectQrCodeUseCaseFactory.create(crashReportTag = crashReportTag) } private val launchPermissionRequest = registerForActivityResult( @@ -59,20 +61,7 @@ internal class QrScannerFragment : Fragment(R.layout.fragment_qr_scanner) { super.onViewCreated(view, savedInstanceState) applySystemBarInsets(view) - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - qrCodeAnalyzer.scannedCode - .catch { e -> - Simber.e("Camera not available for QR scanning", e, tag = crashReportTag) - finishWithError(QrScannerResult.QrScannerError.CameraNotAvailable) - }.collectLatest { qrCode -> - if (qrCode.isNotEmpty()) { - finishWithContent(qrCode) - } - } - } - } - + startAnalyzer() if (requireActivity().hasPermission(CAMERA)) { startCamera() } else { @@ -80,15 +69,40 @@ internal class QrScannerFragment : Fragment(R.layout.fragment_qr_scanner) { } } - private fun startCamera() { - binding.qrScannerArea.isVisible = true - cameraHelper.startCamera( - viewLifecycleOwner, - binding.qrScannerPreview, - qrCodeAnalyzer, + override fun onDestroyView() { + cameraFrameProvider.release() + super.onDestroyView() + } + + private fun startAnalyzer() = viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.RESUMED) { + cameraFrameProvider.frames + .catch { e -> + Simber.e("Camera not available for QR scanning", e, tag = crashReportTag) + finishWithError(QrScannerResult.QrScannerError.CameraNotAvailable) + }.collect { frame -> + detectQrCodeUseCase(frame)?.takeIf { it.isNotEmpty() }?.let { qrCode -> finishWithContent(qrCode) } + } + } + } + + private fun startCamera() = viewLifecycleOwner.lifecycleScope.launch { + // Wait for the views to be properly laid out + binding.qrScannerPreview.awaitLayout() + binding.qrScannerArea.awaitLayout() + + val targetRect = getBoundsRelativeToParentUseCase( + parent = binding.qrScannerPreview, + child = binding.qrScannerArea, + ) + cameraFrameProvider.initialiseCamera( + lifecycleOwner = viewLifecycleOwner, + previewView = binding.qrScannerPreview, + target = targetRect, ) { finishWithError(QrScannerResult.QrScannerError.CameraNotAvailable) } + binding.qrScannerArea.isVisible = true } private fun finishWithContent(content: String) { diff --git a/feature/login/src/main/res/layout/fragment_qr_scanner.xml b/feature/login/src/main/res/layout/fragment_qr_scanner.xml index 3fa0e59bb0..d8da6df7fd 100644 --- a/feature/login/src/main/res/layout/fragment_qr_scanner.xml +++ b/feature/login/src/main/res/layout/fragment_qr_scanner.xml @@ -15,7 +15,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_camera_focus" - android:visibility="gone" + android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"