Skip to content

Commit 0267a8e

Browse files
authored
Merge pull request #103 from synonymdev/feat/scan-gallery-access
Scan gallery access
2 parents 0405cab + 06e8e14 commit 0267a8e

File tree

2 files changed

+77
-9
lines changed

2 files changed

+77
-9
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<uses-permission android:name="android.permission.READ_CLIPBOARD" />
1111
<uses-permission android:name="android.permission.INTERNET" />
1212
<uses-permission android:name="android.permission.CAMERA" />
13+
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
1314

1415
<application
1516
android:name=".App"

app/src/main/java/to/bitkit/ui/screens/scanner/QrScanningScreen.kt

Lines changed: 76 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
package to.bitkit.ui.screens.scanner
44

55
import android.Manifest
6+
import android.content.Context
7+
import android.net.Uri
8+
import android.os.Build
69
import 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
713
import androidx.camera.core.Camera
814
import androidx.camera.core.CameraSelector
915
import androidx.camera.core.ImageAnalysis
@@ -21,8 +27,8 @@ import androidx.compose.foundation.layout.padding
2127
import androidx.compose.foundation.layout.size
2228
import androidx.compose.foundation.shape.CircleShape
2329
import 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
2632
import androidx.compose.runtime.Composable
2733
import androidx.compose.runtime.DisposableEffect
2834
import androidx.compose.runtime.LaunchedEffect
@@ -46,6 +52,10 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
4652
import androidx.navigation.NavController
4753
import com.google.accompanist.permissions.ExperimentalPermissionsApi
4854
import 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
4959
import kotlinx.coroutines.Dispatchers
5060
import kotlinx.coroutines.withContext
5161
import 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
157188
private 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

Comments
 (0)