From 81214935d1d8df778c9fa31a09be99380999214c Mon Sep 17 00:00:00 2001 From: retanar Date: Fri, 6 Dec 2024 10:40:36 +0200 Subject: [PATCH 1/4] Added camera with barcode scanning --- feature/homeImpl/build.gradle.kts | 2 + .../featuremodule/homeImpl/HomeGraphEntry.kt | 32 +++++ .../homeImpl/barcode/BarcodeCameraScreen.kt | 118 ++++++++++++++++++ .../homeImpl/barcode/BarcodeContract.kt | 12 ++ .../homeImpl/barcode/BarcodeResultScreen.kt | 7 ++ .../homeImpl/barcode/BarcodeVM.kt | 31 +++++ .../featuremodule/homeImpl/ui/HomeContract.kt | 1 + .../featuremodule/homeImpl/ui/HomeScreen.kt | 1 + .../com/featuremodule/homeImpl/ui/HomeVM.kt | 6 + gradle/libs.versions.toml | 3 + 10 files changed, 213 insertions(+) create mode 100644 feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt create mode 100644 feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeContract.kt create mode 100644 feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt create mode 100644 feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt diff --git a/feature/homeImpl/build.gradle.kts b/feature/homeImpl/build.gradle.kts index 4314ace..55eb848 100644 --- a/feature/homeImpl/build.gradle.kts +++ b/feature/homeImpl/build.gradle.kts @@ -12,6 +12,8 @@ dependencies { implementation(libs.bundles.exoplayer) implementation(libs.bundles.camerax) + implementation(libs.camerax.mlkit) + implementation(libs.gms.mlkit.barcode) implementation(libs.glide.compose) } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt index 62b9464..dc5c144 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt @@ -4,9 +4,13 @@ import android.graphics.Bitmap import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavType import androidx.navigation.compose.composable +import androidx.navigation.navArgument import com.featuremodule.core.navigation.HIDE_NAV_BAR import com.featuremodule.homeApi.HomeDestination +import com.featuremodule.homeImpl.barcode.BarcodeCameraScreen +import com.featuremodule.homeImpl.barcode.BarcodeResultScreen import com.featuremodule.homeImpl.camera.TakePhotoScreen import com.featuremodule.homeImpl.exoplayer.ExoplayerScreen import com.featuremodule.homeImpl.imageUpload.ImageUploadScreen @@ -31,6 +35,17 @@ fun NavGraphBuilder.registerHome() { composable(InternalRoutes.TakePhotoDestination.ROUTE) { TakePhotoScreen() } + + composable(InternalRoutes.BarcodeCameraDestination.ROUTE) { + BarcodeCameraScreen() + } + + composable( + InternalRoutes.BarcodeResultDestination.ROUTE, + InternalRoutes.BarcodeResultDestination.arguments, + ) { + BarcodeResultScreen() + } } internal class InternalRoutes { @@ -52,4 +67,21 @@ internal class InternalRoutes { fun constructRoute() = ROUTE } + + object BarcodeCameraDestination { + const val ROUTE = HIDE_NAV_BAR + "barcode" + + fun constructRoute() = ROUTE + } + + object BarcodeResultDestination { + const val ARG_BARCODE = "barcode" + const val ROUTE = "barcode_result/{$ARG_BARCODE}" + + val arguments = listOf( + navArgument(ARG_BARCODE) { type = NavType.StringType }, + ) + + fun constructRoute(barcodeValue: String) = "barcode_result/$barcodeValue" + } } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt new file mode 100644 index 0000000..7812a00 --- /dev/null +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt @@ -0,0 +1,118 @@ +package com.featuremodule.homeImpl.barcode + +import android.Manifest +import android.content.pm.PackageManager +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.mlkit.vision.MlKitAnalyzer +import androidx.camera.view.CameraController +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.hilt.navigation.compose.hiltViewModel +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode + +@Composable +internal fun BarcodeCameraScreen(viewModel: BarcodeVM = hiltViewModel()) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + + var cameraViewVisibility by remember { mutableStateOf(false) } + val launchInAppCameraPermissionRequest = + rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + if (isGranted) { + cameraViewVisibility = true + } else { + viewModel.postEvent(Event.PopBack) + } + } + + LaunchedEffect(Unit) { + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA, + ) != PackageManager.PERMISSION_GRANTED + ) { + launchInAppCameraPermissionRequest.launch(Manifest.permission.CAMERA) + } + } + + val options = BarcodeScannerOptions.Builder() + .setBarcodeFormats( + Barcode.FORMAT_QR_CODE, + Barcode.FORMAT_DATA_MATRIX, + Barcode.FORMAT_EAN_13, + Barcode.FORMAT_EAN_8, + ) + .build() + val barcodeScanner = BarcodeScanning.getClient(options) + + val previewView = remember { + PreviewView(context).apply { + scaleType = PreviewView.ScaleType.FIT_CENTER + } + } + val cameraController = remember { + LifecycleCameraController(context).apply { + setEnabledUseCases(CameraController.IMAGE_ANALYSIS) + setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(context), + MlKitAnalyzer( + listOf(barcodeScanner), + ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED, + ContextCompat.getMainExecutor(context), + ) { result -> + result?.getValue(barcodeScanner)?.firstOrNull()?.let { + viewModel.postEvent(Event.BarcodeReceived(it)) + } + }, + ) + bindToLifecycle(lifecycleOwner) + cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + } + } + + LaunchedEffect(previewView, cameraController) { + previewView.controller = cameraController + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black) + .windowInsetsPadding(WindowInsets.navigationBars), + ) { + if (cameraViewVisibility) { + AndroidView( + factory = { previewView }, + Modifier + .align(Alignment.Center) + .aspectRatio(1f) + .fillMaxSize(), + ) + } + } +} diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeContract.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeContract.kt new file mode 100644 index 0000000..a003cca --- /dev/null +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeContract.kt @@ -0,0 +1,12 @@ +package com.featuremodule.homeImpl.barcode + +import com.featuremodule.core.ui.UiEvent +import com.featuremodule.core.ui.UiState +import com.google.mlkit.vision.barcode.common.Barcode + +internal class State : UiState + +internal sealed interface Event : UiEvent { + data object PopBack : Event + data class BarcodeReceived(val barcode: Barcode) : Event +} diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt new file mode 100644 index 0000000..96da9bd --- /dev/null +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt @@ -0,0 +1,7 @@ +package com.featuremodule.homeImpl.barcode + +import androidx.compose.runtime.Composable + +@Composable +internal fun BarcodeResultScreen() { +} diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt new file mode 100644 index 0000000..c476036 --- /dev/null +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt @@ -0,0 +1,31 @@ +package com.featuremodule.homeImpl.barcode + +import com.featuremodule.core.navigation.NavCommand +import com.featuremodule.core.navigation.NavManager +import com.featuremodule.core.ui.BaseVM +import com.featuremodule.homeImpl.InternalRoutes +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +internal class BarcodeVM @Inject constructor( + private val navManager: NavManager, +) : BaseVM() { + override fun initialState() = State() + + override fun handleEvent(event: Event) { + when (event) { + Event.PopBack -> launch { navManager.navigate(NavCommand.PopBack) } + + is Event.BarcodeReceived -> launch { + navManager.navigate( + NavCommand.Forward( + InternalRoutes.BarcodeResultDestination.constructRoute( + event.barcode.rawValue.toString(), + ), + ), + ) + } + } + } +} diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeContract.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeContract.kt index 3892fac..6cdbe4c 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeContract.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeContract.kt @@ -9,4 +9,5 @@ internal sealed interface Event : UiEvent { data object NavigateToFeatureA : Event data object NavigateToExoplayer : Event data object NavigateToCamera : Event + data object NavigateToBarcode : Event } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeScreen.kt index ec64729..e52ca50 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeScreen.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeScreen.kt @@ -39,6 +39,7 @@ internal fun HomeScreen(route: String?, viewModel: HomeVM = hiltViewModel()) { GenericButton(text = "Pass number") { viewModel.postEvent(Event.NavigateToFeatureA) } GenericButton(text = "Exoplayer") { viewModel.postEvent(Event.NavigateToExoplayer) } GenericButton(text = "Camera") { viewModel.postEvent(Event.NavigateToCamera) } + GenericButton(text = "Barcode") { viewModel.postEvent(Event.NavigateToBarcode) } } } } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeVM.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeVM.kt index afe3d54..af6c8bc 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeVM.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/ui/HomeVM.kt @@ -39,6 +39,12 @@ internal class HomeVM @Inject constructor( NavCommand.Forward(InternalRoutes.ImageUploadDestination.constructRoute()), ) } + + Event.NavigateToBarcode -> launch { + navManager.navigate( + NavCommand.Forward(InternalRoutes.BarcodeCameraDestination.constructRoute()), + ) + } } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b240345..05443fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ glide-compose = "1.0.0-beta01" leakcanary = "2.14" media3 = "1.4.1" camerax = "1.4.0" +gms-mlkit = "18.3.1" # Versions used for android{} setup sdk-compile = "34" @@ -77,6 +78,8 @@ camerax-core = { module = "androidx.camera:camera-core", version.ref = "camerax" camerax-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camerax" } camerax-view = { module = "androidx.camera:camera-view", version.ref = "camerax" } camerax-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camerax" } +camerax-mlkit = { module = "androidx.camera:camera-mlkit-vision", version.ref = "camerax" } +gms-mlkit-barcode = { module = "com.google.android.gms:play-services-mlkit-barcode-scanning", version.ref = "gms-mlkit" } # Testing junit = { module = "junit:junit", version.ref = "junit" } From a1f0c3c1587d32d7638a3c5e2b2a89d3c2447684 Mon Sep 17 00:00:00 2001 From: retanar Date: Wed, 11 Dec 2024 10:55:11 +0200 Subject: [PATCH 2/4] Camera and permission fixes, throttling of barcode results --- feature/homeImpl/src/main/AndroidManifest.xml | 6 ++ .../featuremodule/homeImpl/HomeGraphEntry.kt | 5 +- .../homeImpl/barcode/BarcodeCameraScreen.kt | 96 +++++++++++-------- .../homeImpl/barcode/BarcodeResultScreen.kt | 16 +++- .../homeImpl/barcode/BarcodeVM.kt | 12 ++- 5 files changed, 92 insertions(+), 43 deletions(-) diff --git a/feature/homeImpl/src/main/AndroidManifest.xml b/feature/homeImpl/src/main/AndroidManifest.xml index f9618a5..15d7e8a 100644 --- a/feature/homeImpl/src/main/AndroidManifest.xml +++ b/feature/homeImpl/src/main/AndroidManifest.xml @@ -4,4 +4,10 @@ android:name="android.hardware.camera" android:required="false" /> + + + + diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt index dc5c144..d52fb5d 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/HomeGraphEntry.kt @@ -44,7 +44,10 @@ fun NavGraphBuilder.registerHome() { InternalRoutes.BarcodeResultDestination.ROUTE, InternalRoutes.BarcodeResultDestination.arguments, ) { - BarcodeResultScreen() + val barcode = it.arguments + ?.getString(InternalRoutes.BarcodeResultDestination.ARG_BARCODE) + ?: "NONE" + BarcodeResultScreen(barcode) } } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt index 7812a00..04e8810 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt @@ -1,6 +1,7 @@ package com.featuremodule.homeImpl.barcode import android.Manifest +import android.content.Context import android.content.pm.PackageManager import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts @@ -31,6 +32,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.LifecycleOwner import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.barcode.common.Barcode @@ -50,53 +52,31 @@ internal fun BarcodeCameraScreen(viewModel: BarcodeVM = hiltViewModel()) { } } - LaunchedEffect(Unit) { - if (ContextCompat.checkSelfPermission( - context, - Manifest.permission.CAMERA, - ) != PackageManager.PERMISSION_GRANTED - ) { - launchInAppCameraPermissionRequest.launch(Manifest.permission.CAMERA) - } - } - - val options = BarcodeScannerOptions.Builder() - .setBarcodeFormats( - Barcode.FORMAT_QR_CODE, - Barcode.FORMAT_DATA_MATRIX, - Barcode.FORMAT_EAN_13, - Barcode.FORMAT_EAN_8, + val cameraController = remember { + createCameraController( + context = context, + lifecycleOwner = lifecycleOwner, + onBarcodeReceived = { viewModel.postEvent(Event.BarcodeReceived(it)) }, ) - .build() - val barcodeScanner = BarcodeScanning.getClient(options) + } val previewView = remember { PreviewView(context).apply { scaleType = PreviewView.ScaleType.FIT_CENTER - } - } - val cameraController = remember { - LifecycleCameraController(context).apply { - setEnabledUseCases(CameraController.IMAGE_ANALYSIS) - setImageAnalysisAnalyzer( - ContextCompat.getMainExecutor(context), - MlKitAnalyzer( - listOf(barcodeScanner), - ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED, - ContextCompat.getMainExecutor(context), - ) { result -> - result?.getValue(barcodeScanner)?.firstOrNull()?.let { - viewModel.postEvent(Event.BarcodeReceived(it)) - } - }, - ) - bindToLifecycle(lifecycleOwner) - cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + controller = cameraController } } - LaunchedEffect(previewView, cameraController) { - previewView.controller = cameraController + LaunchedEffect(context) { + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.CAMERA, + ) != PackageManager.PERMISSION_GRANTED + ) { + launchInAppCameraPermissionRequest.launch(Manifest.permission.CAMERA) + } else { + cameraViewVisibility = true + } } Box( @@ -108,7 +88,7 @@ internal fun BarcodeCameraScreen(viewModel: BarcodeVM = hiltViewModel()) { if (cameraViewVisibility) { AndroidView( factory = { previewView }, - Modifier + modifier = Modifier .align(Alignment.Center) .aspectRatio(1f) .fillMaxSize(), @@ -116,3 +96,39 @@ internal fun BarcodeCameraScreen(viewModel: BarcodeVM = hiltViewModel()) { } } } + +private fun createCameraController( + context: Context, + lifecycleOwner: LifecycleOwner, + onBarcodeReceived: (Barcode) -> Unit, +): LifecycleCameraController { + val barcodeScanner = BarcodeScanning.getClient( + BarcodeScannerOptions.Builder() + .setBarcodeFormats( + Barcode.FORMAT_QR_CODE, + Barcode.FORMAT_DATA_MATRIX, + Barcode.FORMAT_EAN_13, + Barcode.FORMAT_EAN_8, + ) + .build(), + ) + + return LifecycleCameraController(context).apply { + bindToLifecycle(lifecycleOwner) + setEnabledUseCases(CameraController.IMAGE_ANALYSIS) + cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(context), + MlKitAnalyzer( + listOf(barcodeScanner), + ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED, + ContextCompat.getMainExecutor(context), + ) { result -> + result?.getValue(barcodeScanner)?.firstOrNull()?.let { + onBarcodeReceived(it) + } + }, + ) + } +} diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt index 96da9bd..27d66c2 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt @@ -1,7 +1,21 @@ package com.featuremodule.homeImpl.barcode +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier @Composable -internal fun BarcodeResultScreen() { +internal fun BarcodeResultScreen(barcode: String) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + SelectionContainer { + Text(text = barcode) + } + } } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt index c476036..67892be 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt @@ -5,6 +5,8 @@ import com.featuremodule.core.navigation.NavManager import com.featuremodule.core.ui.BaseVM import com.featuremodule.homeImpl.InternalRoutes import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay +import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @HiltViewModel @@ -13,18 +15,26 @@ internal class BarcodeVM @Inject constructor( ) : BaseVM() { override fun initialState() = State() + // Throttling due to barcode reader sending multiple results before closing camera + private val isBarcodeProcessing = AtomicBoolean(false) + override fun handleEvent(event: Event) { when (event) { Event.PopBack -> launch { navManager.navigate(NavCommand.PopBack) } is Event.BarcodeReceived -> launch { + if (isBarcodeProcessing.getAndSet(true)) return@launch + navManager.navigate( NavCommand.Forward( InternalRoutes.BarcodeResultDestination.constructRoute( - event.barcode.rawValue.toString(), + event.barcode.displayValue.toString(), ), ), ) + + delay(5000L) + isBarcodeProcessing.set(false) } } } From 2a0553f2dafafe1e21e0ba964fe2080937f61b93 Mon Sep 17 00:00:00 2001 From: retanar Date: Wed, 11 Dec 2024 13:45:16 +0200 Subject: [PATCH 3/4] Clipboard copying and sharing --- .../homeImpl/barcode/BarcodeResultScreen.kt | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt index 27d66c2..c0d32d4 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeResultScreen.kt @@ -1,21 +1,60 @@ package com.featuremodule.homeImpl.barcode -import androidx.compose.foundation.layout.Box +import android.content.Intent +import android.os.Build +import android.widget.Toast +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp @Composable internal fun BarcodeResultScreen(barcode: String) { - Box( + val context = LocalContext.current + val clipboard = LocalClipboardManager.current + + Column( modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { SelectionContainer { Text(text = barcode) } + + Button( + onClick = { + clipboard.setText(AnnotatedString(barcode)) + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) { + Toast.makeText(context, "Copied $barcode", Toast.LENGTH_SHORT).show() + } + }, + modifier = Modifier.defaultMinSize(minWidth = 100.dp), + ) { + Text(text = "Copy") + } + + Button( + onClick = { + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, barcode) + } + context.startActivity(Intent.createChooser(shareIntent, null)) + }, + modifier = Modifier.defaultMinSize(minWidth = 100.dp), + ) { + Text(text = "Share") + } } } From 42b9328b2546604d3e63772b4e7c3747b2af2dfb Mon Sep 17 00:00:00 2001 From: retanar Date: Fri, 13 Dec 2024 10:46:27 +0200 Subject: [PATCH 4/4] Linter reformat --- .../featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt | 8 +++++--- .../java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt index 04e8810..794b712 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeCameraScreen.kt @@ -43,8 +43,10 @@ internal fun BarcodeCameraScreen(viewModel: BarcodeVM = hiltViewModel()) { val lifecycleOwner = LocalLifecycleOwner.current var cameraViewVisibility by remember { mutableStateOf(false) } - val launchInAppCameraPermissionRequest = - rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> + val launchCameraPermissionRequest = + rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission(), + ) { isGranted -> if (isGranted) { cameraViewVisibility = true } else { @@ -73,7 +75,7 @@ internal fun BarcodeCameraScreen(viewModel: BarcodeVM = hiltViewModel()) { Manifest.permission.CAMERA, ) != PackageManager.PERMISSION_GRANTED ) { - launchInAppCameraPermissionRequest.launch(Manifest.permission.CAMERA) + launchCameraPermissionRequest.launch(Manifest.permission.CAMERA) } else { cameraViewVisibility = true } diff --git a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt index 67892be..059290d 100644 --- a/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt +++ b/feature/homeImpl/src/main/java/com/featuremodule/homeImpl/barcode/BarcodeVM.kt @@ -33,7 +33,8 @@ internal class BarcodeVM @Inject constructor( ), ) - delay(5000L) + // Throttle time, can be adjusted as needed + delay(timeMillis = 5000L) isBarcodeProcessing.set(false) } }