Skip to content

Commit 82e6b5c

Browse files
committed
Merge branch 'release/1.2.0-rc01' into main
2 parents 3613d96 + 9fddc17 commit 82e6b5c

File tree

101 files changed

+563
-228
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+563
-228
lines changed

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ There are two different flavors available on `mavenCentral()`:
1818
| V2 model is used (possibly faster, more accurate) | currently V1 model will be downloaded
1919
```kotlin
2020
// bundled:
21-
implementation("io.github.g00fy2.quickie:quickie-bundled:1.1.2")
21+
implementation("io.github.g00fy2.quickie:quickie-bundled:1.2.0-rc01")
2222

2323
// unbundled:
24-
implementation("io.github.g00fy2.quickie:quickie-unbundled:1.1.2")
24+
implementation("io.github.g00fy2.quickie:quickie-unbundled:1.2.0-rc01")
2525
```
2626

2727
## Quick Start
@@ -61,7 +61,7 @@ Currently, supported subtypes are:
6161
See the ML Kit [Barcode documentation](https://developers.google.com/android/reference/com/google/mlkit/vision/barcode/Barcode#nested-class-summary) for further details.
6262

6363
### Customization
64-
Use the `ScanCustomCode()` ActivityResultContract to create a configurable barcode scan. When launching the ActivityResultLauncher pass in a `ScannerConfig` object. You can set the supported `BarcodeFormat` list, `overlayStringRes` and `overlayDrawableRes` resource ID and control the `hapticSuccessFeedback`.
64+
Use the `ScanCustomCode()` ActivityResultContract to create a configurable barcode scan. When launching the ActivityResultLauncher pass in a `ScannerConfig` object:
6565

6666
<details>
6767
<summary>BarcodeFormat options</summary>
@@ -93,10 +93,11 @@ override fun onCreate(savedInstanceState: Bundle?) {
9393
binding.button.setOnClickListener {
9494
scanCustomCode.launch(
9595
ScannerConfig.build {
96-
setBarcodeFormats(listOf(BarcodeFormat.FORMAT_CODE_128))
97-
setOverlayStringRes(R.string.scan_barcode)
98-
setOverlayDrawableRes(R.drawable.ic_scan_barcode)
99-
setHapticSuccessFeedback(false)
96+
setBarcodeFormats(listOf(BarcodeFormat.FORMAT_CODE_128)) // set interested barcode formats
97+
setOverlayStringRes(R.string.scan_barcode) // string resource used for the scanner overlay
98+
setOverlayDrawableRes(R.drawable.ic_scan_barcode) // drawable resource used for the scanner overlay
99+
setHapticSuccessFeedback(false) // enable (default) or disable haptic feedback when a barcode was detected
100+
setShowTorchToggle(true) // show or hide (default) torch/flashlight toggle button
100101
}
101102
)
102103
}

buildSrc/src/main/kotlin/Deps.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ object Deps {
88
const val cameraPreview = "androidx.camera:camera-view:${Versions.cameraView}"
99
}
1010

11-
object UI {
12-
const val materialDesign = "com.google.android.material:material:${Versions.materialDesign}"
13-
}
14-
1511
object MLKit {
1612
const val barcodeScanning = "com.google.mlkit:barcode-scanning:${Versions.barcodeScanning}"
1713
const val barcodeScanningGms =
1814
"com.google.android.gms:play-services-mlkit-barcode-scanning:${Versions.barcodeScanningGms}"
1915
}
2016

17+
object UI {
18+
const val materialDesign = "com.google.android.material:material:${Versions.materialDesign}"
19+
}
20+
2121
object Test {
2222
const val junitApi = "org.junit.jupiter:junit-jupiter-api:${Versions.junit}"
2323
const val junitEngine = "org.junit.jupiter:junit-jupiter-engine:${Versions.junit}"

buildSrc/src/main/kotlin/Versions.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@ object Versions {
66
const val androidBuildTools = "30.0.3"
77

88
const val androidGradle = "4.2.1"
9-
const val kotlin = "1.5.10"
9+
const val kotlin = "1.5.20"
1010

1111
const val appcompat = "1.3.0"
1212

1313
const val cameraX = "1.0.0"
1414
const val cameraView = "1.0.0-alpha25"
1515

16-
const val materialDesign = "1.3.0"
17-
1816
const val barcodeScanning = "16.1.2"
1917
const val barcodeScanningGms = "16.1.5"
2018

19+
const val materialDesign = "1.3.0"
20+
2121
const val detekt = "1.17.1"
2222
const val gradleVersions = "0.39.0"
2323
const val dokka = "1.4.32"

quickie/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ dependencies {
4343
}
4444

4545
group = "io.github.g00fy2.quickie"
46-
version = "1.1.2"
46+
version = "1.2.0-rc01"
4747

4848
tasks.register<Jar>("androidJavadocJar") {
4949
archiveClassifier.set("javadoc")

quickie/config/detekt/baseline-bundledDebug.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<SmellBaseline>
33
<ManuallySuppressedIssues></ManuallySuppressedIssues>
44
<CurrentIssues>
5-
<ID>FunctionOnlyReturningConstant:PlayServicesValidator.kt$PlayServicesValidator$@Suppress("UNUSED_PARAMETER") fun handleGooglePlayServicesError(activity: Activity, exception: Exception)</ID>
5+
<ID>FunctionOnlyReturningConstant:MlKitErrorHandler.kt$MlKitErrorHandler$@Suppress("UNUSED_PARAMETER") fun isResolvableError(activity: QRScannerActivity, exception: Exception)</ID>
66
<ID>LongMethod:IntentExtensions.kt$private fun Intent.toQuickieContentType(rawValue: String): QRContent?</ID>
77
<ID>UnsafeCallOnNullableType:QRCodeAnalyzer.kt$QRCodeAnalyzer$image!!</ID>
88
<ID>UnsafeCallOnNullableType:QROverlayView.kt$QROverlayView$maskBitmap!!</ID>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.g00fy2.quickie.utils
2+
3+
import io.github.g00fy2.quickie.QRScannerActivity
4+
5+
internal object MlKitErrorHandler {
6+
7+
@Suppress("UNUSED_PARAMETER")
8+
fun isResolvableError(activity: QRScannerActivity, exception: Exception) = false // always false when bundled
9+
}

quickie/src/bundled/kotlin/io/github/g00fy2/quickie/utils/PlayServicesValidator.kt

Lines changed: 0 additions & 9 deletions
This file was deleted.

quickie/src/main/kotlin/io/github/g00fy2/quickie/QRCodeAnalyzer.kt

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import com.google.mlkit.vision.common.InputImage
1111
internal class QRCodeAnalyzer(
1212
private val barcodeFormats: IntArray,
1313
private val onSuccess: ((Barcode) -> Unit),
14-
private val onFailure: ((Exception) -> Unit)
14+
private val onFailure: ((Exception) -> Unit),
15+
private val onPassCompleted: ((Boolean) -> Unit)
1516
) : ImageAnalysis.Analyzer {
1617

1718
private val barcodeScanner by lazy {
@@ -23,14 +24,32 @@ internal class QRCodeAnalyzer(
2324
BarcodeScanning.getClient(optionsBuilder.build())
2425
}
2526

27+
@Volatile
28+
private var failureOccurred = false
29+
private var failureTimestamp = 0L
30+
2631
@ExperimentalGetImage
2732
override fun analyze(imageProxy: ImageProxy) {
2833
if (imageProxy.image == null) return
2934

35+
// throttle analysis if error occurred in previous pass
36+
if (failureOccurred && System.currentTimeMillis() - failureTimestamp < 1000L) {
37+
imageProxy.close()
38+
return
39+
}
40+
41+
failureOccurred = false
3042
barcodeScanner.process(imageProxy.toInputImage())
3143
.addOnSuccessListener { codes -> codes.mapNotNull { it }.firstOrNull()?.let { onSuccess(it) } }
32-
.addOnFailureListener { onFailure(it) }
33-
.addOnCompleteListener { imageProxy.close() }
44+
.addOnFailureListener {
45+
failureOccurred = true
46+
failureTimestamp = System.currentTimeMillis()
47+
onFailure(it)
48+
}
49+
.addOnCompleteListener {
50+
onPassCompleted(failureOccurred)
51+
imageProxy.close()
52+
}
3453
}
3554

3655
@ExperimentalGetImage

quickie/src/main/kotlin/io/github/g00fy2/quickie/QROverlayView.kt

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import android.widget.FrameLayout
1818
import androidx.core.content.ContextCompat
1919
import androidx.core.content.res.ResourcesCompat
2020
import androidx.core.graphics.ColorUtils
21+
import io.github.g00fy2.quickie.databinding.QuickieProgressViewBinding
2122
import io.github.g00fy2.quickie.databinding.QuickieTextviewBinding
23+
import io.github.g00fy2.quickie.databinding.QuickieTorchImageviewBinding
2224
import kotlin.math.min
2325
import kotlin.math.roundToInt
2426

@@ -33,22 +35,33 @@ internal class QROverlayView @JvmOverloads constructor(
3335
private val backgroundColor = ColorUtils.setAlphaComponent(Color.BLACK, BACKGROUND_ALPHA.roundToInt())
3436
private val alphaPaint = Paint().apply { alpha = BACKGROUND_ALPHA.roundToInt() }
3537
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG)
38+
private val loadingBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = backgroundColor }
3639
private val transparentPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
3740
color = Color.TRANSPARENT
3841
xfermode = PorterDuffXfermode(CLEAR)
3942
}
4043
private val outerRadius = OUT_RADIUS.toPx()
4144
private val innerRadius = (OUT_RADIUS - STROKE_WIDTH).toPx()
42-
private val titleTextView = QuickieTextviewBinding.inflate(LayoutInflater.from(context), this, true).root
4345
private val outerFrame = RectF()
4446
private val innerFrame = RectF()
47+
private val titleTextView = QuickieTextviewBinding.inflate(LayoutInflater.from(context), this, true).root
48+
private val progressLinearLayout = QuickieProgressViewBinding.inflate(LayoutInflater.from(context), this, true).root
49+
private val torchImageView = QuickieTorchImageviewBinding.inflate(LayoutInflater.from(context), this, true).root
4550
private var maskBitmap: Bitmap? = null
4651
private var maskCanvas: Canvas? = null
47-
4852
var isHighlighted = false
4953
set(value) {
50-
field = value
51-
invalidate()
54+
if (field != value) {
55+
field = value
56+
invalidate()
57+
}
58+
}
59+
var isLoading = false
60+
set(value) {
61+
if (field != value) {
62+
field = value
63+
progressLinearLayout.visibility = if (value) View.VISIBLE else View.GONE
64+
}
5265
}
5366

5467
init {
@@ -69,6 +82,7 @@ internal class QROverlayView @JvmOverloads constructor(
6982
maskCanvas!!.drawColor(backgroundColor)
7083
maskCanvas!!.drawRoundRect(outerFrame, outerRadius, outerRadius, strokePaint)
7184
maskCanvas!!.drawRoundRect(innerFrame, innerRadius, innerRadius, transparentPaint)
85+
if (isLoading) maskCanvas!!.drawRoundRect(innerFrame, innerRadius, innerRadius, loadingBackgroundPaint)
7286
canvas.drawBitmap(maskBitmap!!, 0f, 0f, alphaPaint)
7387
super.onDraw(canvas)
7488
}
@@ -92,6 +106,15 @@ internal class QROverlayView @JvmOverloads constructor(
92106
}
93107
}
94108

109+
fun setTorchVisibilityAndOnClick(visible: Boolean, action: (Boolean) -> Unit = {}) {
110+
torchImageView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
111+
torchImageView.setOnClickListener { action(!it.isSelected) }
112+
}
113+
114+
fun setTorchState(on: Boolean) {
115+
torchImageView.isSelected = on
116+
}
117+
95118
private fun calculateFrameAndTitlePos() {
96119
val centralX = width / 2
97120
val centralY = height / 2

quickie/src/main/kotlin/io/github/g00fy2/quickie/QRScannerActivity.kt

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@ package io.github.g00fy2.quickie
22

33
import android.Manifest.permission.CAMERA
44
import android.app.Activity
5+
import android.app.Dialog
56
import android.content.Intent
67
import android.content.pm.PackageManager
78
import android.os.Bundle
89
import android.util.Size
910
import android.view.HapticFeedbackConstants
11+
import android.view.KeyEvent
1012
import android.view.View
1113
import androidx.activity.result.contract.ActivityResultContracts
1214
import androidx.appcompat.app.AppCompatActivity
1315
import androidx.appcompat.view.ContextThemeWrapper
1416
import androidx.camera.core.CameraSelector
1517
import androidx.camera.core.ImageAnalysis
1618
import androidx.camera.core.Preview
19+
import androidx.camera.core.TorchState
1720
import androidx.camera.lifecycle.ProcessCameraProvider
1821
import androidx.core.content.ContextCompat
1922
import androidx.core.view.ViewCompat
@@ -23,7 +26,7 @@ import com.google.mlkit.vision.barcode.Barcode
2326
import io.github.g00fy2.quickie.config.ParcelableScannerConfig
2427
import io.github.g00fy2.quickie.databinding.QuickieScannerActivityBinding
2528
import io.github.g00fy2.quickie.extensions.toParcelableContentType
26-
import io.github.g00fy2.quickie.utils.PlayServicesValidator
29+
import io.github.g00fy2.quickie.utils.MlKitErrorHandler
2730
import java.util.concurrent.ExecutorService
2831
import java.util.concurrent.Executors
2932

@@ -33,6 +36,21 @@ internal class QRScannerActivity : AppCompatActivity() {
3336
private lateinit var analysisExecutor: ExecutorService
3437
private var barcodeFormats = intArrayOf(Barcode.FORMAT_QR_CODE)
3538
private var hapticFeedback = true
39+
private var showTorchToggle = false
40+
internal var errorDialog: Dialog? = null
41+
set(value) {
42+
field = value
43+
value?.show()
44+
value?.setOnKeyListener { dialog, keyCode, _ ->
45+
if (keyCode == KeyEvent.KEYCODE_BACK) {
46+
finish()
47+
dialog.dismiss()
48+
true
49+
} else {
50+
false
51+
}
52+
}
53+
}
3654

3755
override fun onCreate(savedInstanceState: Bundle?) {
3856
super.onCreate(savedInstanceState)
@@ -75,24 +93,29 @@ internal class QRScannerActivity : AppCompatActivity() {
7593
.also {
7694
it.setAnalyzer(analysisExecutor,
7795
QRCodeAnalyzer(
78-
barcodeFormats,
79-
{ barcode ->
96+
barcodeFormats = barcodeFormats,
97+
onSuccess = { barcode ->
8098
it.clearAnalyzer()
8199
onSuccess(barcode)
82100
},
83-
{ exception ->
84-
it.clearAnalyzer()
85-
onFailure(exception)
86-
}
101+
onFailure = { exception -> onFailure(exception) },
102+
onPassCompleted = { failureOccurred -> onPassCompleted(failureOccurred) }
87103
)
88104
)
89105
}
90106

91107
cameraProvider.unbindAll()
92108
try {
93-
cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
109+
val camera = cameraProvider.bindToLifecycle(this, CameraSelector.DEFAULT_BACK_CAMERA, preview, imageAnalysis)
94110
binding.overlayView.visibility = View.VISIBLE
111+
if (showTorchToggle && camera.cameraInfo.hasFlashUnit()) {
112+
binding.overlayView.setTorchVisibilityAndOnClick(true) { camera.cameraControl.enableTorch(it) }
113+
camera.cameraInfo.torchState.observe(this) { binding.overlayView.setTorchState(it == TorchState.ON) }
114+
} else {
115+
binding.overlayView.setTorchVisibilityAndOnClick(false)
116+
}
95117
} catch (e: Exception) {
118+
binding.overlayView.visibility = View.INVISIBLE
96119
onFailure(e)
97120
}
98121
}, ContextCompat.getMainExecutor(this))
@@ -119,7 +142,11 @@ internal class QRScannerActivity : AppCompatActivity() {
119142

120143
private fun onFailure(exception: Exception) {
121144
setResult(RESULT_ERROR, Intent().putExtra(EXTRA_RESULT_EXCEPTION, exception))
122-
if (!PlayServicesValidator.handleGooglePlayServicesError(this, exception)) finish()
145+
if (!MlKitErrorHandler.isResolvableError(this, exception)) finish()
146+
}
147+
148+
private fun onPassCompleted(failureOccurred: Boolean) {
149+
if (!isFinishing) binding.overlayView.isLoading = failureOccurred
123150
}
124151

125152
private fun setupEdgeToEdgeUI() {
@@ -135,6 +162,7 @@ internal class QRScannerActivity : AppCompatActivity() {
135162
barcodeFormats = it.formats
136163
binding.overlayView.setCustomTextAndIcon(it.stringRes, it.drawableRes)
137164
hapticFeedback = it.hapticFeedback
165+
showTorchToggle = it.showTorchToggle
138166
}
139167
}
140168

0 commit comments

Comments
 (0)