QR codes scanning improvements (#1722)
Introduce Mlkit Barcodes library Added `MlkitQrCodeAnalyzer` component Changelogs update
This commit is contained in:
parent
9916a346d6
commit
805a1b26b7
|
@ -8,6 +8,11 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Send Confirmation & Send Progress screens have been refactored
|
- Send Confirmation & Send Progress screens have been refactored
|
||||||
|
- ZXing QR codes scanning library has been replaced with a more recent MLkit Barcodes scanning library, which gives
|
||||||
|
us better results in testing
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- The way how Zashi treats ZIP 321 single address within URIs results has been fixed
|
||||||
|
|
||||||
## [1.3.1 (822)] - 2025-01-07
|
## [1.3.1 (822)] - 2025-01-07
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,11 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Send Confirmation & Send Progress screens have been refactored with bugfixes and optimizations
|
- Send Confirmation & Send Progress screens have been refactored with bugfixes and optimizations
|
||||||
|
- ZXing QR codes scanning library has been replaced with a more recent MLkit Barcodes scanning library, which gives
|
||||||
|
us better results in testing
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- The way how Zashi treats ZIP 321 single address within URIs results has been fixed
|
||||||
|
|
||||||
## [1.3.1 (822)] - 2025-01-07
|
## [1.3.1 (822)] - 2025-01-07
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,11 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Send Confirmation & Send Progress screens have been refactored with bugfixes and optimizations
|
- Send Confirmation & Send Progress screens have been refactored with bugfixes and optimizations
|
||||||
|
- ZXing QR codes scanning library has been replaced with a more recent MLkit Barcodes scanning library, which gives
|
||||||
|
us better results in testing
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- The way how Zashi treats ZIP 321 single address within URIs results has been fixed
|
||||||
|
|
||||||
## [1.3.1 (822)] - 2025-01-07
|
## [1.3.1 (822)] - 2025-01-07
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,7 @@ KOTLINX_SERIALIZABLE_JSON_VERSION=1.6.3
|
||||||
KOVER_VERSION=0.7.3
|
KOVER_VERSION=0.7.3
|
||||||
LOTTIE_VERSION=6.5.0
|
LOTTIE_VERSION=6.5.0
|
||||||
MARKDOWN_VERSION=0.7.3
|
MARKDOWN_VERSION=0.7.3
|
||||||
|
MLKIT_SCANNING_VERSION=17.3.0
|
||||||
PLAY_APP_UPDATE_VERSION=2.1.0
|
PLAY_APP_UPDATE_VERSION=2.1.0
|
||||||
PLAY_APP_UPDATE_KTX_VERSION=2.1.0
|
PLAY_APP_UPDATE_KTX_VERSION=2.1.0
|
||||||
PLAY_PUBLISHER_API_VERSION=v3-rev20231030-2.0.0
|
PLAY_PUBLISHER_API_VERSION=v3-rev20231030-2.0.0
|
||||||
|
|
|
@ -80,13 +80,15 @@ dependencyResolutionManagement {
|
||||||
"androidx.benchmark",
|
"androidx.benchmark",
|
||||||
"androidx.navigation",
|
"androidx.navigation",
|
||||||
"com.android",
|
"com.android",
|
||||||
|
"com.google.android.apps.common.testing.accessibility.framework",
|
||||||
"com.google.android.datatransport",
|
"com.google.android.datatransport",
|
||||||
"com.google.android.gms",
|
"com.google.android.gms",
|
||||||
"com.google.android.material",
|
"com.google.android.material",
|
||||||
|
"com.google.android.odml",
|
||||||
"com.google.android.play",
|
"com.google.android.play",
|
||||||
"com.google.firebase",
|
"com.google.firebase",
|
||||||
|
"com.google.mlkit",
|
||||||
"com.google.testing.platform",
|
"com.google.testing.platform",
|
||||||
"com.google.android.apps.common.testing.accessibility.framework"
|
|
||||||
)
|
)
|
||||||
val googleRegexes = listOf(
|
val googleRegexes = listOf(
|
||||||
"androidx\\..*",
|
"androidx\\..*",
|
||||||
|
@ -181,6 +183,7 @@ dependencyResolutionManagement {
|
||||||
val kotlinxSerializableJsonVersion = extra["KOTLINX_SERIALIZABLE_JSON_VERSION"].toString()
|
val kotlinxSerializableJsonVersion = extra["KOTLINX_SERIALIZABLE_JSON_VERSION"].toString()
|
||||||
val lottieVersion = extra["LOTTIE_VERSION"].toString()
|
val lottieVersion = extra["LOTTIE_VERSION"].toString()
|
||||||
val markdownVersion = extra["MARKDOWN_VERSION"].toString()
|
val markdownVersion = extra["MARKDOWN_VERSION"].toString()
|
||||||
|
val mlkitScanningVersion = extra["MLKIT_SCANNING_VERSION"].toString()
|
||||||
val playAppUpdateVersion = extra["PLAY_APP_UPDATE_VERSION"].toString()
|
val playAppUpdateVersion = extra["PLAY_APP_UPDATE_VERSION"].toString()
|
||||||
val playAppUpdateKtxVersion = extra["PLAY_APP_UPDATE_KTX_VERSION"].toString()
|
val playAppUpdateKtxVersion = extra["PLAY_APP_UPDATE_KTX_VERSION"].toString()
|
||||||
val tinkVersion = extra["TINK_VERSION"].toString()
|
val tinkVersion = extra["TINK_VERSION"].toString()
|
||||||
|
@ -245,6 +248,7 @@ dependencyResolutionManagement {
|
||||||
library("kotlinx-serializable-json", "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializableJsonVersion")
|
library("kotlinx-serializable-json", "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlinxSerializableJsonVersion")
|
||||||
library("lottie", "com.airbnb.android:lottie-compose:$lottieVersion")
|
library("lottie", "com.airbnb.android:lottie-compose:$lottieVersion")
|
||||||
library("markdown", "org.jetbrains:markdown:$markdownVersion")
|
library("markdown", "org.jetbrains:markdown:$markdownVersion")
|
||||||
|
library("mlkit-scanning", "com.google.mlkit:barcode-scanning:$mlkitScanningVersion")
|
||||||
library("play-update", "com.google.android.play:app-update:$playAppUpdateVersion")
|
library("play-update", "com.google.android.play:app-update:$playAppUpdateVersion")
|
||||||
library("play-update-ktx", "com.google.android.play:app-update-ktx:$playAppUpdateKtxVersion")
|
library("play-update-ktx", "com.google.android.play:app-update-ktx:$playAppUpdateKtxVersion")
|
||||||
library("tink", "com.google.crypto.tink:tink-android:$tinkVersion")
|
library("tink", "com.google.crypto.tink:tink-android:$tinkVersion")
|
||||||
|
|
|
@ -140,6 +140,7 @@ dependencies {
|
||||||
implementation(libs.kotlinx.datetime)
|
implementation(libs.kotlinx.datetime)
|
||||||
implementation(libs.kotlinx.immutable)
|
implementation(libs.kotlinx.immutable)
|
||||||
implementation(libs.kotlinx.serializable.json)
|
implementation(libs.kotlinx.serializable.json)
|
||||||
|
implementation(libs.mlkit.scanning)
|
||||||
implementation(libs.zcash.sdk)
|
implementation(libs.zcash.sdk)
|
||||||
implementation(libs.zcash.sdk.incubator)
|
implementation(libs.zcash.sdk.incubator)
|
||||||
implementation(libs.zcash.bip39)
|
implementation(libs.zcash.bip39)
|
||||||
|
|
|
@ -42,7 +42,8 @@ internal class Zip321ParseUriValidationUseCase(
|
||||||
|
|
||||||
return when (paymentRequest) {
|
return when (paymentRequest) {
|
||||||
is ZIP321.ParserResult.Request -> Zip321ParseUriValidation.Valid(zip321Uri)
|
is ZIP321.ParserResult.Request -> Zip321ParseUriValidation.Valid(zip321Uri)
|
||||||
// null or [ZIP321.ParserResult.SingleAddress] is not valid for our ZIP 321 Uri to Proposal use case
|
is ZIP321.ParserResult.SingleAddress ->
|
||||||
|
Zip321ParseUriValidation.SingleAddress(paymentRequest.singleRecipient.value)
|
||||||
else -> Zip321ParseUriValidation.Invalid
|
else -> Zip321ParseUriValidation.Invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +51,8 @@ internal class Zip321ParseUriValidationUseCase(
|
||||||
internal sealed class Zip321ParseUriValidation {
|
internal sealed class Zip321ParseUriValidation {
|
||||||
data class Valid(val zip321Uri: String) : Zip321ParseUriValidation()
|
data class Valid(val zip321Uri: String) : Zip321ParseUriValidation()
|
||||||
|
|
||||||
|
data class SingleAddress(val address: String) : Zip321ParseUriValidation()
|
||||||
|
|
||||||
data object Invalid : Zip321ParseUriValidation()
|
data object Invalid : Zip321ParseUriValidation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.scan.util
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Matrix
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.camera.core.ExperimentalGetImage
|
||||||
|
import androidx.camera.core.ImageAnalysis
|
||||||
|
import androidx.camera.core.ImageProxy
|
||||||
|
import co.electriccoin.zcash.spackle.Twig
|
||||||
|
import co.electriccoin.zcash.ui.screen.scankeystone.view.FramePosition
|
||||||
|
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
|
||||||
|
import com.google.mlkit.vision.barcode.BarcodeScanning
|
||||||
|
import com.google.mlkit.vision.barcode.common.Barcode
|
||||||
|
import com.google.mlkit.vision.common.InputImage
|
||||||
|
|
||||||
|
class MlkitQrCodeAnalyzer(
|
||||||
|
private val framePosition: FramePosition,
|
||||||
|
private val onQrCodeScanned: (String) -> Unit,
|
||||||
|
) : ImageAnalysis.Analyzer {
|
||||||
|
private val supportedImageFormat = Barcode.FORMAT_QR_CODE
|
||||||
|
|
||||||
|
@OptIn(ExperimentalGetImage::class)
|
||||||
|
override fun analyze(imageProxy: ImageProxy) {
|
||||||
|
Twig.verbose { "Mlkit image proxy: ${imageProxy.imageInfo}" }
|
||||||
|
|
||||||
|
val mediaImage = imageProxy.image
|
||||||
|
if (mediaImage != null) {
|
||||||
|
val bitmap = imageProxy.toBitmap()
|
||||||
|
|
||||||
|
val rotatedBitmap = bitmap.rotate(imageProxy.imageInfo.rotationDegrees)
|
||||||
|
val croppedBitmap = rotatedBitmap.crop(framePosition)
|
||||||
|
|
||||||
|
// No rotation for cropped Bitmap
|
||||||
|
val image = InputImage.fromBitmap(croppedBitmap, 0)
|
||||||
|
|
||||||
|
Twig.verbose {
|
||||||
|
"Scan result: " +
|
||||||
|
"Frame: $framePosition, "
|
||||||
|
"Format: ${mediaImage.format}, " +
|
||||||
|
"Image width: ${mediaImage.width}, " +
|
||||||
|
"Image height: ${mediaImage.height}"
|
||||||
|
"Rotation: ${imageProxy.imageInfo.rotationDegrees}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure Barcode Scanner Options
|
||||||
|
val options =
|
||||||
|
BarcodeScannerOptions.Builder()
|
||||||
|
.setBarcodeFormats(supportedImageFormat)
|
||||||
|
// We could optionally use this to enhance scan success ratio. If it's specified, then the library
|
||||||
|
// will suggest zooming the camera if the barcode is too far away or too small to be detected.
|
||||||
|
// .setZoomSuggestionOptions()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Initialize Barcode Scanner
|
||||||
|
val scanner = BarcodeScanning.getClient(options)
|
||||||
|
|
||||||
|
scanner.process(image)
|
||||||
|
.addOnSuccessListener { barcodes ->
|
||||||
|
for (barcode in barcodes) {
|
||||||
|
barcode.rawValue?.let { value ->
|
||||||
|
Twig.debug { "Mlkit barcode value: $value" }
|
||||||
|
onQrCodeScanned(value)
|
||||||
|
// Note that we only take the first code from the list of discovered codes
|
||||||
|
return@addOnSuccessListener
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.addOnFailureListener { e ->
|
||||||
|
Twig.error(e) { "Barcode detection failed" }
|
||||||
|
}
|
||||||
|
.addOnCompleteListener {
|
||||||
|
// Close the image proxy
|
||||||
|
imageProxy.close()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
imageProxy.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Bitmap.rotate(rotationDegrees: Int): Bitmap {
|
||||||
|
// Rotate the matrix by the specified degrees
|
||||||
|
val matrix =
|
||||||
|
Matrix().also {
|
||||||
|
it.postRotate(rotationDegrees.toFloat())
|
||||||
|
}
|
||||||
|
return Bitmap.createBitmap(
|
||||||
|
// source
|
||||||
|
this,
|
||||||
|
// x
|
||||||
|
0,
|
||||||
|
// y
|
||||||
|
0,
|
||||||
|
// width
|
||||||
|
width,
|
||||||
|
// height
|
||||||
|
height,
|
||||||
|
// m
|
||||||
|
matrix,
|
||||||
|
// filter (Filter for better quality)
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Crop Bitmap to the specified dimensions given by [FramePosition]
|
||||||
|
*/
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
private fun Bitmap.crop(framePosition: FramePosition): Bitmap {
|
||||||
|
// TODO [#1380]: Leverage FramePosition in QrCodeAnalyzer
|
||||||
|
// TODO [#1380]: https://github.com/Electric-Coin-Company/zashi-android/issues/1380
|
||||||
|
return Bitmap.createBitmap(
|
||||||
|
this,
|
||||||
|
// left
|
||||||
|
(width * LEFT_OFFSET).toInt(),
|
||||||
|
// top
|
||||||
|
(height * TOP_OFFSET).toInt(),
|
||||||
|
// width
|
||||||
|
(width * WIDTH_OFFSET).toInt(),
|
||||||
|
// height
|
||||||
|
(height * HEIGHT_OFFSET).toInt(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val LEFT_OFFSET = .15
|
||||||
|
private const val TOP_OFFSET = .25
|
||||||
|
private const val WIDTH_OFFSET = .7
|
||||||
|
private const val HEIGHT_OFFSET = .45
|
|
@ -88,7 +88,7 @@ import co.electriccoin.zcash.ui.screen.scan.ScanTag
|
||||||
import co.electriccoin.zcash.ui.screen.scan.model.ScanScreenState
|
import co.electriccoin.zcash.ui.screen.scan.model.ScanScreenState
|
||||||
import co.electriccoin.zcash.ui.screen.scan.model.ScanValidationState
|
import co.electriccoin.zcash.ui.screen.scan.model.ScanValidationState
|
||||||
import co.electriccoin.zcash.ui.screen.scan.util.ImageUriToQrCodeConverter
|
import co.electriccoin.zcash.ui.screen.scan.util.ImageUriToQrCodeConverter
|
||||||
import co.electriccoin.zcash.ui.screen.scan.util.QrCodeAnalyzer
|
import co.electriccoin.zcash.ui.screen.scan.util.MlkitQrCodeAnalyzer
|
||||||
import co.electriccoin.zcash.ui.screen.scankeystone.view.CAMERA_TRANSLUCENT_BORDER
|
import co.electriccoin.zcash.ui.screen.scankeystone.view.CAMERA_TRANSLUCENT_BORDER
|
||||||
import co.electriccoin.zcash.ui.screen.scankeystone.view.FramePosition
|
import co.electriccoin.zcash.ui.screen.scankeystone.view.FramePosition
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
|
@ -709,7 +709,7 @@ fun ImageAnalysis.qrCodeFlow(framePosition: FramePosition): Flow<String> {
|
||||||
callbackFlow {
|
callbackFlow {
|
||||||
setAnalyzer(
|
setAnalyzer(
|
||||||
ContextCompat.getMainExecutor(context),
|
ContextCompat.getMainExecutor(context),
|
||||||
QrCodeAnalyzer(
|
MlkitQrCodeAnalyzer(
|
||||||
framePosition = framePosition,
|
framePosition = framePosition,
|
||||||
onQrCodeScanned = { result ->
|
onQrCodeScanned = { result ->
|
||||||
Twig.debug { "Scan result onQrCodeScanned: $result" }
|
Twig.debug { "Scan result onQrCodeScanned: $result" }
|
||||||
|
|
|
@ -41,48 +41,76 @@ internal class ScanViewModel(
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
if (!hasBeenScannedSuccessfully) {
|
if (!hasBeenScannedSuccessfully) {
|
||||||
val addressValidationResult = getSynchronizer().validateAddress(result)
|
val addressValidationResult = getSynchronizer().validateAddress(result)
|
||||||
|
|
||||||
val zip321ValidationResult = zip321ParseUriValidationUseCase(result)
|
val zip321ValidationResult = zip321ParseUriValidationUseCase(result)
|
||||||
|
|
||||||
state.update {
|
when {
|
||||||
if (addressValidationResult is AddressType.Valid) {
|
zip321ValidationResult is Zip321ParseUriValidation.Valid ->
|
||||||
ScanValidationState.INVALID
|
{
|
||||||
} else if (zip321ValidationResult is Zip321ParseUriValidation.Valid) {
|
hasBeenScannedSuccessfully = true
|
||||||
ScanValidationState.INVALID
|
state.update { ScanValidationState.VALID }
|
||||||
} else {
|
navigateBack.emit(ScanResultState.Zip321Uri(zip321ValidationResult.zip321Uri))
|
||||||
ScanValidationState.NONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (zip321ValidationResult is Zip321ParseUriValidation.Valid) {
|
|
||||||
hasBeenScannedSuccessfully = true
|
|
||||||
navigateBack.emit(ScanResultState.Zip321Uri(zip321ValidationResult.zip321Uri))
|
|
||||||
} else if (addressValidationResult is AddressType.Valid) {
|
|
||||||
hasBeenScannedSuccessfully = true
|
|
||||||
|
|
||||||
val serializableAddress = SerializableAddress(result, addressValidationResult)
|
|
||||||
|
|
||||||
when (args) {
|
|
||||||
DEFAULT -> {
|
|
||||||
navigateBack.emit(
|
|
||||||
ScanResultState.Address(
|
|
||||||
Json.encodeToString(
|
|
||||||
SerializableAddress.serializer(),
|
|
||||||
serializableAddress
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
zip321ValidationResult is Zip321ParseUriValidation.SingleAddress ->
|
||||||
ADDRESS_BOOK -> {
|
{
|
||||||
navigateCommand.emit(AddContactArgs(serializableAddress.address))
|
hasBeenScannedSuccessfully = true
|
||||||
|
val singleAddressValidation =
|
||||||
|
getSynchronizer()
|
||||||
|
.validateAddress(zip321ValidationResult.address)
|
||||||
|
when (singleAddressValidation) {
|
||||||
|
is AddressType.Invalid -> {
|
||||||
|
state.update { ScanValidationState.INVALID }
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
state.update { ScanValidationState.VALID }
|
||||||
|
processAddress(zip321ValidationResult.address, singleAddressValidation)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
addressValidationResult is AddressType.Valid ->
|
||||||
|
{
|
||||||
|
hasBeenScannedSuccessfully = true
|
||||||
|
state.update { ScanValidationState.VALID }
|
||||||
|
processAddress(result, addressValidationResult)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
hasBeenScannedSuccessfully = false
|
||||||
|
state.update { ScanValidationState.INVALID }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun processAddress(
|
||||||
|
address: String,
|
||||||
|
addressType: AddressType
|
||||||
|
) {
|
||||||
|
require(addressType is AddressType.Valid)
|
||||||
|
|
||||||
|
val serializableAddress =
|
||||||
|
SerializableAddress(
|
||||||
|
address = address,
|
||||||
|
type = addressType
|
||||||
|
)
|
||||||
|
|
||||||
|
when (args) {
|
||||||
|
DEFAULT -> {
|
||||||
|
navigateBack.emit(
|
||||||
|
ScanResultState.Address(
|
||||||
|
Json.encodeToString(
|
||||||
|
SerializableAddress.serializer(),
|
||||||
|
serializableAddress
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ADDRESS_BOOK -> {
|
||||||
|
navigateCommand.emit(AddContactArgs(serializableAddress.address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onScannedError() =
|
fun onScannedError() =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
|
|
|
@ -88,7 +88,7 @@ import co.electriccoin.zcash.ui.design.util.stringRes
|
||||||
import co.electriccoin.zcash.ui.screen.scan.ScanTag
|
import co.electriccoin.zcash.ui.screen.scan.ScanTag
|
||||||
import co.electriccoin.zcash.ui.screen.scan.model.ScanScreenState
|
import co.electriccoin.zcash.ui.screen.scan.model.ScanScreenState
|
||||||
import co.electriccoin.zcash.ui.screen.scan.model.ScanValidationState
|
import co.electriccoin.zcash.ui.screen.scan.model.ScanValidationState
|
||||||
import co.electriccoin.zcash.ui.screen.scan.util.QrCodeAnalyzer
|
import co.electriccoin.zcash.ui.screen.scan.util.MlkitQrCodeAnalyzer
|
||||||
import co.electriccoin.zcash.ui.screen.scankeystone.model.ScanKeystoneState
|
import co.electriccoin.zcash.ui.screen.scankeystone.model.ScanKeystoneState
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.PermissionState
|
import com.google.accompanist.permissions.PermissionState
|
||||||
|
@ -730,7 +730,7 @@ fun ImageAnalysis.qrCodeFlow(framePosition: FramePosition): Flow<String> {
|
||||||
callbackFlow {
|
callbackFlow {
|
||||||
setAnalyzer(
|
setAnalyzer(
|
||||||
ContextCompat.getMainExecutor(context),
|
ContextCompat.getMainExecutor(context),
|
||||||
QrCodeAnalyzer(
|
MlkitQrCodeAnalyzer(
|
||||||
framePosition = framePosition,
|
framePosition = framePosition,
|
||||||
onQrCodeScanned = { result ->
|
onQrCodeScanned = { result ->
|
||||||
Twig.debug { "Scan result onQrCodeScanned: $result" }
|
Twig.debug { "Scan result onQrCodeScanned: $result" }
|
||||||
|
|
Loading…
Reference in New Issue