33package to.bitkit.ui.screens.scanner
44
55import android.Manifest
6+ import android.content.Context
7+ import android.net.Uri
8+ import android.os.Build
69import android.view.View.LAYER_TYPE_HARDWARE
10+ import androidx.activity.compose.rememberLauncherForActivityResult
11+ import androidx.activity.result.PickVisualMediaRequest
12+ import androidx.activity.result.contract.ActivityResultContracts
713import androidx.camera.core.Camera
814import androidx.camera.core.CameraSelector
915import androidx.camera.core.ImageAnalysis
@@ -21,8 +27,8 @@ import androidx.compose.foundation.layout.padding
2127import androidx.compose.foundation.layout.size
2228import androidx.compose.foundation.shape.CircleShape
2329import androidx.compose.foundation.shape.RoundedCornerShape
24- import androidx.compose.material .Icon
25- import androidx.compose.material .IconButton
30+ import androidx.compose.material3 .Icon
31+ import androidx.compose.material3 .IconButton
2632import androidx.compose.runtime.Composable
2733import androidx.compose.runtime.DisposableEffect
2834import androidx.compose.runtime.LaunchedEffect
@@ -46,6 +52,10 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
4652import androidx.navigation.NavController
4753import com.google.accompanist.permissions.ExperimentalPermissionsApi
4854import com.google.accompanist.permissions.rememberPermissionState
55+ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
56+ import com.google.mlkit.vision.barcode.BarcodeScanning
57+ import com.google.mlkit.vision.barcode.common.Barcode
58+ import com.google.mlkit.vision.common.InputImage
4959import kotlinx.coroutines.Dispatchers
5060import kotlinx.coroutines.withContext
5161import to.bitkit.R
@@ -95,6 +105,17 @@ fun QrScanningScreen(
95105 )
96106 }
97107
108+ val galleryLauncher = rememberLauncherForActivityResult(
109+ contract = ActivityResultContracts .GetContent (),
110+ onResult = { uri ->
111+ uri?.let { processImageFromGallery(context, it, onScanSuccess, onError = { e -> app.toast(e) }) }
112+ }
113+ )
114+
115+ val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts .PickVisualMedia ()) { uri ->
116+ uri?.let { processImageFromGallery(context, it, onScanSuccess, onError = { e -> app.toast(e) }) }
117+ }
118+
98119 LaunchedEffect (lensFacing) {
99120 imageAnalysis.setAnalyzer(
100121 Executors .newSingleThreadExecutor(),
@@ -144,10 +165,20 @@ fun QrScanningScreen(
144165 grantedContent = {
145166 ScreenColumn (modifier = Modifier .gradientBackground()) {
146167 AppTopBar (stringResource(R .string.title_scan), onBackClick = { navController.popBackStack() })
147- Content (previewView = previewView, onClickCamera = {
148- isFlashlightOn = ! isFlashlightOn
149- camera?.cameraControl?.enableTorch(isFlashlightOn)
150- })
168+ Content (
169+ previewView = previewView,
170+ onClickFlashlight = {
171+ isFlashlightOn = ! isFlashlightOn
172+ camera?.cameraControl?.enableTorch(isFlashlightOn)
173+ },
174+ onClickGallery = {
175+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
176+ pickMedia.launch(PickVisualMediaRequest (ActivityResultContracts .PickVisualMedia .ImageOnly ))
177+ } else {
178+ galleryLauncher.launch(" image/*" )
179+ }
180+ }
181+ )
151182 }
152183 }
153184 )
@@ -156,7 +187,8 @@ fun QrScanningScreen(
156187@Composable
157188private fun Content (
158189 previewView : PreviewView ,
159- onClickCamera : () -> Unit ,
190+ onClickFlashlight : () -> Unit ,
191+ onClickGallery : () -> Unit ,
160192 modifier : Modifier = Modifier ,
161193) {
162194 Column (
@@ -178,7 +210,7 @@ private fun Content(
178210 )
179211
180212 IconButton (
181- onClick = {}, // TODO IMPLEMENT
213+ onClick = onClickGallery,
182214 modifier = Modifier
183215 .padding(16 .dp)
184216 .clip(CircleShape )
@@ -196,7 +228,7 @@ private fun Content(
196228 }
197229
198230 IconButton (
199- onClick = onClickCamera ,
231+ onClick = onClickFlashlight ,
200232 modifier = Modifier
201233 .padding(16 .dp)
202234 .clip(CircleShape )
@@ -228,3 +260,38 @@ private fun Content(
228260 Spacer (modifier = Modifier .height(16 .dp))
229261 }
230262}
263+
264+ private fun processImageFromGallery (
265+ context : Context ,
266+ uri : Uri ,
267+ onScanSuccess : (String ) -> Unit ,
268+ onError : (Exception ) -> Unit ,
269+ ) {
270+ try {
271+ val image = InputImage .fromFilePath(context, uri)
272+ val options = BarcodeScannerOptions .Builder ()
273+ .setBarcodeFormats(Barcode .FORMAT_QR_CODE )
274+ .build()
275+ val scanner = BarcodeScanning .getClient(options)
276+
277+ scanner.process(image)
278+ .addOnSuccessListener { barcodes ->
279+ for (barcode in barcodes) {
280+ barcode.rawValue?.let { qrCode ->
281+ onScanSuccess(qrCode)
282+ Logger .info(" QR code found $qrCode " )
283+ return @addOnSuccessListener
284+ }
285+ }
286+ Logger .error(" No QR code found in the image" )
287+ onError(Exception (" No QR code found in the image" ))
288+ }
289+ .addOnFailureListener { e ->
290+ Logger .error(" Failed to scan QR code from gallery" , e)
291+ onError(e)
292+ }
293+ } catch (e: Exception ) {
294+ Logger .error(" Failed to process image from gallery" , e)
295+ onError(e)
296+ }
297+ }
0 commit comments