diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt index 3190b8ced9..69f1d0959c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt @@ -138,6 +138,7 @@ class CameraXFragment : ComposeFragment(), CameraFragment { controller = controller, isVideoEnabled = isVideoEnabled && Build.VERSION.SDK_INT >= 26, isQrScanEnabled = isQrScanEnabled, + isVideoCaptureBindingEnabled = cameraXModePolicy is CameraXModePolicy.Mixed, controlsVisible = controlsVisible.value, selectedMediaCount = selectedMediaCount.intValue, onCheckPermissions = { checkPermissions(isVideoEnabled) }, @@ -298,6 +299,7 @@ private fun CameraXScreen( controller: CameraFragment.Controller?, isVideoEnabled: Boolean, isQrScanEnabled: Boolean, + isVideoCaptureBindingEnabled: Boolean, controlsVisible: Boolean, selectedMediaCount: Int, onCheckPermissions: () -> Unit, @@ -404,6 +406,8 @@ private fun CameraXScreen( emitter = { event -> cameraViewModel.onEvent(event) }, roundCorners = cameraDisplay.roundViewFinderCorners, contentAlignment = cameraAlignment, + enableVideoCapture = isVideoCaptureBindingEnabled, + enableQrScanning = isQrScanEnabled, modifier = Modifier.padding(bottom = viewportBottomMargin) ) { AnimatedVisibility( @@ -611,6 +615,7 @@ private fun CameraXScreenPreview_20_9() { controller = null, isVideoEnabled = true, isQrScanEnabled = false, + isVideoCaptureBindingEnabled = true, controlsVisible = true, selectedMediaCount = 0, onCheckPermissions = {}, @@ -638,6 +643,7 @@ private fun CameraXScreenPreview_19_9() { controller = null, isVideoEnabled = true, isQrScanEnabled = false, + isVideoCaptureBindingEnabled = true, controlsVisible = true, selectedMediaCount = 0, onCheckPermissions = {}, @@ -665,6 +671,7 @@ private fun CameraXScreenPreview_18_9() { controller = null, isVideoEnabled = true, isQrScanEnabled = false, + isVideoCaptureBindingEnabled = true, controlsVisible = true, selectedMediaCount = 0, onCheckPermissions = {}, @@ -692,6 +699,7 @@ private fun CameraXScreenPreview_16_9() { controller = null, isVideoEnabled = true, isQrScanEnabled = false, + isVideoCaptureBindingEnabled = true, controlsVisible = true, selectedMediaCount = 0, onCheckPermissions = {}, @@ -719,6 +727,7 @@ private fun CameraXScreenPreview_6_5() { controller = null, isVideoEnabled = true, isQrScanEnabled = false, + isVideoCaptureBindingEnabled = true, controlsVisible = true, selectedMediaCount = 0, onCheckPermissions = {}, diff --git a/feature/camera/src/main/java/org/signal/camera/CameraScreen.kt b/feature/camera/src/main/java/org/signal/camera/CameraScreen.kt index 40e0da45dc..65b966ed1b 100644 --- a/feature/camera/src/main/java/org/signal/camera/CameraScreen.kt +++ b/feature/camera/src/main/java/org/signal/camera/CameraScreen.kt @@ -76,6 +76,8 @@ fun CameraScreen( modifier: Modifier = Modifier, roundCorners: Boolean = true, contentAlignment: Alignment = Alignment.Center, + enableVideoCapture: Boolean = true, + enableQrScanning: Boolean = false, content: @Composable BoxScope.() -> Unit = {} ) { val context = LocalContext.current @@ -103,7 +105,9 @@ fun CameraScreen( lifecycleOwner = lifecycleOwner, cameraProvider = cameraProvider, surfaceProvider = surfaceProvider, - context = context + context = context, + enableVideoCapture = enableVideoCapture, + enableQrScanning = enableQrScanning ) ) } diff --git a/feature/camera/src/main/java/org/signal/camera/CameraScreenEvents.kt b/feature/camera/src/main/java/org/signal/camera/CameraScreenEvents.kt index 5615f35540..b29a2df02c 100644 --- a/feature/camera/src/main/java/org/signal/camera/CameraScreenEvents.kt +++ b/feature/camera/src/main/java/org/signal/camera/CameraScreenEvents.kt @@ -8,12 +8,14 @@ import androidx.lifecycle.LifecycleOwner sealed interface CameraScreenEvents { - /** Binds a camera to a sruface provider. */ + /** Binds a camera to a surface provider. */ data class BindCamera( val lifecycleOwner: LifecycleOwner, val cameraProvider: ProcessCameraProvider, val surfaceProvider: Preview.SurfaceProvider, - val context: Context + val context: Context, + val enableVideoCapture: Boolean = true, + val enableQrScanning: Boolean = false ) : CameraScreenEvents /** Focuses the camera on a point. */ diff --git a/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt b/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt index c19c72d80b..0f551a1a80 100644 --- a/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt +++ b/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt @@ -344,27 +344,35 @@ class CameraScreenViewModel : ViewModel() { .setResolutionSelector(resolutionSelector) .build() - // Video capture (16:9 is default for video) - val recorder = Recorder.Builder() - .setAspectRatio(AspectRatio.RATIO_16_9) - .setQualitySelector( - androidx.camera.video.QualitySelector.from( - androidx.camera.video.Quality.HIGHEST, - androidx.camera.video.FallbackStrategy.higherQualityOrLowerThan(androidx.camera.video.Quality.HD) - ) - ) - .build() - val videoCaptureUseCase = VideoCapture.withOutput(recorder) + // Build the list of use cases based on device capabilities + val useCases = mutableListOf(preview, imageCaptureUseCase) - // Image analysis for QR code detection - val imageAnalysisUseCase = ImageAnalysis.Builder() - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - .also { - it.setAnalyzer(imageAnalysisExecutor) { imageProxy -> - processImageForQrCode(imageProxy) + var videoCaptureUseCase: VideoCapture? = null + if (event.enableVideoCapture) { + val recorder = Recorder.Builder() + .setAspectRatio(AspectRatio.RATIO_16_9) + .setQualitySelector( + androidx.camera.video.QualitySelector.from( + androidx.camera.video.Quality.HIGHEST, + androidx.camera.video.FallbackStrategy.higherQualityOrLowerThan(androidx.camera.video.Quality.HD) + ) + ) + .build() + videoCaptureUseCase = VideoCapture.withOutput(recorder) + useCases += videoCaptureUseCase + } + + if (event.enableQrScanning) { + val imageAnalysisUseCase = ImageAnalysis.Builder() + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + .also { + it.setAnalyzer(imageAnalysisExecutor) { imageProxy -> + processImageForQrCode(imageProxy) + } } - } + useCases += imageAnalysisUseCase + } // Select camera based on lensFacing val cameraSelector = CameraSelector.Builder() @@ -379,10 +387,7 @@ class CameraScreenViewModel : ViewModel() { camera = event.cameraProvider.bindToLifecycle( event.lifecycleOwner, cameraSelector, - preview, - imageCaptureUseCase, - videoCaptureUseCase, - imageAnalysisUseCase + *useCases.toTypedArray() ) lifecycleOwner = event.lifecycleOwner