1
1
package io.github.g00fy2.quickie
2
2
3
+ import android.Manifest
3
4
import android.Manifest.permission.CAMERA
4
5
import android.app.Activity
5
6
import android.app.Dialog
6
7
import android.content.Intent
7
8
import android.content.pm.PackageManager
9
+ import android.net.Uri
10
+ import android.os.Build
8
11
import android.os.Bundle
9
12
import android.util.Size
10
13
import android.view.HapticFeedbackConstants
@@ -26,7 +29,10 @@ import androidx.core.content.IntentCompat
26
29
import androidx.core.view.ViewCompat
27
30
import androidx.core.view.WindowCompat
28
31
import androidx.core.view.WindowInsetsCompat
32
+ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
33
+ import com.google.mlkit.vision.barcode.BarcodeScanning
29
34
import com.google.mlkit.vision.barcode.common.Barcode
35
+ import com.google.mlkit.vision.common.InputImage
30
36
import io.github.g00fy2.quickie.config.ParcelableScannerConfig
31
37
import io.github.g00fy2.quickie.databinding.QuickieScannerActivityBinding
32
38
import io.github.g00fy2.quickie.extensions.toParcelableContentType
@@ -43,6 +49,9 @@ internal class QRScannerActivity : AppCompatActivity() {
43
49
private var showTorchToggle = false
44
50
private var showCloseButton = false
45
51
private var useFrontCamera = false
52
+ private val galleryLauncher= registerForActivityResult(ActivityResultContracts .GetContent ()) { uri: Uri ? ->
53
+ uri?.let { analyzeImageFromGallery(it) }
54
+ }
46
55
internal var errorDialog: Dialog ? = null
47
56
set(value) {
48
57
field = value
@@ -68,6 +77,7 @@ internal class QRScannerActivity : AppCompatActivity() {
68
77
69
78
setupEdgeToEdgeUI()
70
79
applyScannerConfig()
80
+ setupGalleryButton()
71
81
72
82
analysisExecutor = Executors .newSingleThreadExecutor()
73
83
@@ -81,6 +91,64 @@ internal class QRScannerActivity : AppCompatActivity() {
81
91
}
82
92
}
83
93
94
+ private fun setupGalleryButton () {
95
+ binding.selectFromGalleryButton.setOnClickListener {
96
+ launchGalleryPicker()
97
+ }
98
+ }
99
+ private val storagePermissionLauncher = registerForActivityResult(ActivityResultContracts .RequestPermission ()) { granted ->
100
+ if (granted) {
101
+ galleryLauncher.launch(IMAGE_MIME_TYPE )
102
+ } else {
103
+ onFailure(Exception (" Storage permission required to select images" ))
104
+ }
105
+ }
106
+ private fun launchGalleryPicker () {
107
+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .TIRAMISU ) {
108
+ if (ContextCompat .checkSelfPermission(this , Manifest .permission.READ_MEDIA_IMAGES ) == PackageManager .PERMISSION_GRANTED ) {
109
+ galleryLauncher.launch(IMAGE_MIME_TYPE )
110
+ } else {
111
+ storagePermissionLauncher.launch(Manifest .permission.READ_MEDIA_IMAGES )
112
+ }
113
+ } else {
114
+ if (ContextCompat .checkSelfPermission(this , Manifest .permission.READ_EXTERNAL_STORAGE ) == PackageManager .PERMISSION_GRANTED ) {
115
+ galleryLauncher.launch(IMAGE_MIME_TYPE )
116
+ } else {
117
+ storagePermissionLauncher.launch(Manifest .permission.READ_EXTERNAL_STORAGE )
118
+ }
119
+ }
120
+ }
121
+
122
+ private fun analyzeImageFromGallery (imageUri : Uri ) {
123
+ try {
124
+ val image = InputImage .fromFilePath(this , imageUri)
125
+ val options = BarcodeScannerOptions .Builder ()
126
+ .setBarcodeFormats(barcodeFormats.sum())
127
+ .build()
128
+
129
+ val scanner = BarcodeScanning .getClient(options)
130
+
131
+ binding.overlayView.isLoading = true
132
+
133
+ scanner.process(image)
134
+ .addOnSuccessListener { barcodes ->
135
+ binding.overlayView.isLoading = false
136
+ if (barcodes.isNullOrEmpty()) {
137
+ onFailure(Exception (" No QR code found in the image" ))
138
+ } else {
139
+ onSuccess(barcodes.first())
140
+ }
141
+ }
142
+ .addOnFailureListener { e ->
143
+ binding.overlayView.isLoading = false
144
+ onFailure(e)
145
+ }
146
+ } catch (e: Exception ) {
147
+ binding.overlayView.isLoading = false
148
+ onFailure(e)
149
+ }
150
+ }
151
+
84
152
override fun onDestroy () {
85
153
super .onDestroy()
86
154
analysisExecutor.shutdown()
@@ -216,6 +284,7 @@ internal class QRScannerActivity : AppCompatActivity() {
216
284
const val EXTRA_RESULT_TYPE = " quickie-type"
217
285
const val EXTRA_RESULT_PARCELABLE = " quickie-parcelable"
218
286
const val EXTRA_RESULT_EXCEPTION = " quickie-exception"
287
+ const val IMAGE_MIME_TYPE = " image/*"
219
288
const val RESULT_MISSING_PERMISSION = RESULT_FIRST_USER + 1
220
289
const val RESULT_ERROR = RESULT_FIRST_USER + 2
221
290
}
0 commit comments