Implemented decoding EPC QR Code on Android
This commit is contained in:
parent
fbd9c9485a
commit
d47bc46cf8
|
@ -109,6 +109,12 @@ kotlin {
|
||||||
implementation(libs.androidx.biometric)
|
implementation(libs.androidx.biometric)
|
||||||
|
|
||||||
implementation(libs.favre.bcrypt)
|
implementation(libs.favre.bcrypt)
|
||||||
|
|
||||||
|
// for reading EPC QR Codes from camera
|
||||||
|
implementation(libs.zxing.core)
|
||||||
|
implementation(libs.camerax.camera2)
|
||||||
|
implementation(libs.camerax.view)
|
||||||
|
implementation(libs.camerax.lifecycle)
|
||||||
}
|
}
|
||||||
|
|
||||||
iosMain.dependencies {
|
iosMain.dependencies {
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
package net.codinux.banking.ui.service
|
||||||
|
|
||||||
|
import androidx.camera.core.*
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.camera.view.PreviewView
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.google.zxing.*
|
||||||
|
import com.google.zxing.common.HybridBinarizer
|
||||||
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
|
import net.codinux.banking.persistence.AndroidContext
|
||||||
|
import net.codinux.banking.ui.config.DI
|
||||||
|
import net.codinux.log.logger
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
actual object QrCodeService {
|
||||||
|
|
||||||
|
private val cameraExecutor = Executors.newCachedThreadPool()
|
||||||
|
|
||||||
|
private val log by logger()
|
||||||
|
|
||||||
|
|
||||||
|
actual val supportsReadingQrCodesFromCamera = true
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit) {
|
||||||
|
val context = AndroidContext.mainActivity
|
||||||
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||||
|
|
||||||
|
val localContext = LocalContext.current
|
||||||
|
log.info { "LocalContext.current = ${localContext.javaClass} ${localContext}" }
|
||||||
|
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
// val context = LocalContext.current
|
||||||
|
val previewView = remember {
|
||||||
|
PreviewView(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
// Used to bind the lifecycle of cameras to the lifecycle owner
|
||||||
|
val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
|
||||||
|
|
||||||
|
// Preview
|
||||||
|
val preview = Preview.Builder()
|
||||||
|
.build()
|
||||||
|
.also {
|
||||||
|
it.setSurfaceProvider(previewView.surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select back camera as a default
|
||||||
|
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
|
||||||
|
|
||||||
|
val imageAnalyzer = ImageAnalysis.Builder()
|
||||||
|
.build()
|
||||||
|
.also {
|
||||||
|
it.setAnalyzer(cameraExecutor, QrCodeImageAnalyzer(resultCallback))
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Unbind use cases before rebinding
|
||||||
|
cameraProvider.unbindAll()
|
||||||
|
|
||||||
|
// Bind use cases to camera
|
||||||
|
cameraProvider.bindToLifecycle(context as FragmentActivity, cameraSelector, preview, imageAnalyzer)
|
||||||
|
|
||||||
|
} catch(e: Exception) {
|
||||||
|
log.error(e) { "Use case binding failed" }
|
||||||
|
}
|
||||||
|
|
||||||
|
}, ContextCompat.getMainExecutor(context))
|
||||||
|
|
||||||
|
|
||||||
|
AndroidView(factory = { previewView }, modifier = Modifier.fillMaxSize())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class QrCodeImageAnalyzer(private val resultCallback: (QrCodeReadResult) -> Unit) : ImageAnalysis.Analyzer {
|
||||||
|
|
||||||
|
private val reader = QRCodeReader()
|
||||||
|
|
||||||
|
private val readerHints = readerHintsForCharset(Charsets.UTF_8.name())
|
||||||
|
|
||||||
|
private val log by logger()
|
||||||
|
|
||||||
|
|
||||||
|
override fun analyze(image: ImageProxy) {
|
||||||
|
try {
|
||||||
|
val bitmap = getBinaryBitmap(image)
|
||||||
|
|
||||||
|
val result = reader.decode(bitmap, readerHints)
|
||||||
|
|
||||||
|
if (result != null && result.text != null) {
|
||||||
|
val decodeResult = DI.epcQrCodeService.decode(result.text)
|
||||||
|
if (decodeResult.charset == null || decodeResult.charset == "UTF-8") {
|
||||||
|
this.resultCallback(QrCodeReadResult(result.text))
|
||||||
|
} else { // the charset for receiver name, reference, ... was not UTF-8 -> decode image in EPC QR Code's charset
|
||||||
|
val resultForEncoding = reader.decode(bitmap, readerHintsForCharset(decodeResult.charset))
|
||||||
|
|
||||||
|
this.resultCallback(QrCodeReadResult(resultForEncoding?.text ?: result.text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
if (e !is NotFoundException) {
|
||||||
|
log.error(e) { "Could not decode image to QR code" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image.close() // to continue image analysis / avoid blocking production of further images
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteBuffer.toIntArray(): IntArray {
|
||||||
|
val bytes = this.toByteArray()
|
||||||
|
|
||||||
|
val pixels = IntArray(bytes.size)
|
||||||
|
bytes.indices.forEach { index ->
|
||||||
|
pixels[index] = bytes[index].toInt() and 0xFF
|
||||||
|
}
|
||||||
|
|
||||||
|
return pixels
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteBuffer.toByteArray(): ByteArray {
|
||||||
|
rewind() // Rewind the buffer to zero
|
||||||
|
val data = ByteArray(remaining())
|
||||||
|
get(data) // Copy the buffer into a byte array
|
||||||
|
return data // Return the byte array
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBinaryBitmap(image: ImageProxy): BinaryBitmap {
|
||||||
|
val buffer = image.planes[0].buffer
|
||||||
|
val bitmapBuffer = buffer.toIntArray()
|
||||||
|
|
||||||
|
val luminanceSource = RGBLuminanceSource(image.width, image.height, bitmapBuffer)
|
||||||
|
return BinaryBitmap(HybridBinarizer(luminanceSource))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readerHintsForCharset(charset: String): Map<DecodeHintType, *> = buildMap {
|
||||||
|
// put(DecodeHintType.TRY_HARDER, true) // optimize for accuracy, not speed
|
||||||
|
put(DecodeHintType.CHARACTER_SET, charset)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import net.codinux.banking.ui.config.Style.FabMenuSpacing
|
||||||
import net.codinux.banking.ui.config.Style.FabSize
|
import net.codinux.banking.ui.config.Style.FabSize
|
||||||
import net.codinux.banking.ui.config.Style.SmallFabSize
|
import net.codinux.banking.ui.config.Style.SmallFabSize
|
||||||
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
|
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
|
||||||
|
import net.codinux.banking.ui.service.QrCodeService
|
||||||
|
|
||||||
|
|
||||||
private val uiState = DI.uiState
|
private val uiState = DI.uiState
|
||||||
|
@ -62,6 +63,14 @@ fun FloatingActionMenu(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (QrCodeService.supportsReadingQrCodesFromCamera) {
|
||||||
|
FloatingActionMenuItem("Überweisung aus QR-Code", "Neue Überweisung mit Daten aus EPC QR Code (GiroCode, scan2Code, Zahlen mit Code, ...)", enabled = accountsThatSupportMoneyTransfer.isNotEmpty()) {
|
||||||
|
handleClick {
|
||||||
|
uiState.showTransferMoneyFromEpcQrCodeScreen.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FloatingActionMenuItem("Überweisung", "Neue Überweisung", enabled = accountsThatSupportMoneyTransfer.isNotEmpty()) {
|
FloatingActionMenuItem("Überweisung", "Neue Überweisung", enabled = accountsThatSupportMoneyTransfer.isNotEmpty()) {
|
||||||
handleClick {
|
handleClick {
|
||||||
uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData()
|
uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData()
|
||||||
|
|
|
@ -15,6 +15,7 @@ private val formatUtil = DI.formatUtil
|
||||||
fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState()
|
||||||
val showTransferMoneyDialogData by uiState.showTransferMoneyDialogData.collectAsState()
|
val showTransferMoneyDialogData by uiState.showTransferMoneyDialogData.collectAsState()
|
||||||
|
val showTransferMoneyFromEpcQrCodeScreen by uiState.showTransferMoneyFromEpcQrCodeScreen.collectAsState()
|
||||||
val showCreateEpcQrCodeScreen by uiState.showCreateEpcQrCodeScreen.collectAsState()
|
val showCreateEpcQrCodeScreen by uiState.showCreateEpcQrCodeScreen.collectAsState()
|
||||||
|
|
||||||
val showAccountTransactionDetailsScreenForId by uiState.showAccountTransactionDetailsScreenForId.collectAsState()
|
val showAccountTransactionDetailsScreenForId by uiState.showAccountTransactionDetailsScreenForId.collectAsState()
|
||||||
|
@ -39,6 +40,10 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
TransferMoneyDialog(data) { uiState.showTransferMoneyDialogData.value = null }
|
TransferMoneyDialog(data) { uiState.showTransferMoneyDialogData.value = null }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showTransferMoneyFromEpcQrCodeScreen) {
|
||||||
|
TransferMoneyFromQrCodeScreen { uiState.showTransferMoneyFromEpcQrCodeScreen.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
if (showCreateEpcQrCodeScreen) {
|
if (showCreateEpcQrCodeScreen) {
|
||||||
CreateEpcQrCodeScreen { uiState.showCreateEpcQrCodeScreen.value = false }
|
CreateEpcQrCodeScreen { uiState.showCreateEpcQrCodeScreen.value = false }
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,9 @@ object Internationalization {
|
||||||
|
|
||||||
const val ErrorTransferMoney = "Überweisung konnte nicht ausgeführt werden"
|
const val ErrorTransferMoney = "Überweisung konnte nicht ausgeführt werden"
|
||||||
|
|
||||||
const val SaveToDatabase = "Daten konnten nicht in der Datenbank gespeichert werden"
|
const val ErrorReadEpcQrCode = "Überweisungsdaten konnten nicht aus dem QR Code ausgelesen werden"
|
||||||
|
|
||||||
|
const val ErrorSaveToDatabase = "Daten konnten nicht in der Datenbank gespeichert werden"
|
||||||
|
|
||||||
const val ErrorBiometricAuthentication = "Biometrische Authentifizierung fehlgeschlagen"
|
const val ErrorBiometricAuthentication = "Biometrische Authentifizierung fehlgeschlagen"
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,10 @@ fun ApplicationErrorDialog(error: ApplicationError, onDismiss: (() -> Unit)? = n
|
||||||
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
||||||
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
||||||
ErroneousAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
ErroneousAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
||||||
ErroneousAction.SaveToDatabase -> Internationalization.SaveToDatabase
|
ErroneousAction.ReadEpcQrCode -> Internationalization.ErrorReadEpcQrCode
|
||||||
|
ErroneousAction.SaveToDatabase -> Internationalization.ErrorSaveToDatabase
|
||||||
ErroneousAction.BiometricAuthentication -> Internationalization.ErrorBiometricAuthentication
|
ErroneousAction.BiometricAuthentication -> Internationalization.ErrorBiometricAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
// add exception stacktrace?
|
|
||||||
|
|
||||||
ErrorDialog(error.errorMessage, title, error.exception, onDismiss = onDismiss)
|
ErrorDialog(error.errorMessage, title, error.exception, onDismiss = onDismiss)
|
||||||
}
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package net.codinux.banking.ui.model
|
||||||
|
|
||||||
|
data class DecodeEpcQrCodeResult(
|
||||||
|
val data: ShowTransferMoneyDialogData?,
|
||||||
|
val error: String? = null,
|
||||||
|
val charset: String? = null
|
||||||
|
)
|
|
@ -7,6 +7,8 @@ enum class ErroneousAction {
|
||||||
|
|
||||||
TransferMoney,
|
TransferMoney,
|
||||||
|
|
||||||
|
ReadEpcQrCode,
|
||||||
|
|
||||||
SaveToDatabase,
|
SaveToDatabase,
|
||||||
|
|
||||||
BiometricAuthentication
|
BiometricAuthentication
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package net.codinux.banking.ui.screens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import net.codinux.banking.ui.config.DI
|
||||||
|
import net.codinux.banking.ui.model.error.ErroneousAction
|
||||||
|
import net.codinux.banking.ui.service.QrCodeService
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TransferMoneyFromQrCodeScreen(onClosed: () -> Unit) {
|
||||||
|
|
||||||
|
if (QrCodeService.supportsReadingQrCodesFromCamera) {
|
||||||
|
FullscreenViewBase("Überweisungsdaten aus QR Code lesen", "Abbrechen", onClosed = onClosed) {
|
||||||
|
QrCodeService.readQrCodeFromCamera { result ->
|
||||||
|
onClosed()
|
||||||
|
|
||||||
|
if (result.decodedQrCodeText != null) {
|
||||||
|
val decodingResult = DI.epcQrCodeService.decode(result.decodedQrCodeText)
|
||||||
|
|
||||||
|
if (decodingResult.data != null) {
|
||||||
|
DI.uiState.showTransferMoneyDialogData.value = decodingResult.data
|
||||||
|
} else if (decodingResult.error != null) {
|
||||||
|
DI.uiState.applicationErrorOccurred(ErroneousAction.ReadEpcQrCode, decodingResult.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,15 +1,36 @@
|
||||||
package net.codinux.banking.ui.service
|
package net.codinux.banking.ui.service
|
||||||
|
|
||||||
|
import net.codinux.banking.client.model.Amount
|
||||||
import net.codinux.banking.epcqrcode.EpcQrCode
|
import net.codinux.banking.epcqrcode.EpcQrCode
|
||||||
import net.codinux.banking.epcqrcode.EpcQrCodeConfig
|
import net.codinux.banking.epcqrcode.EpcQrCodeConfig
|
||||||
import net.codinux.banking.epcqrcode.EpcQrCodeGenerator
|
import net.codinux.banking.epcqrcode.EpcQrCodeGenerator
|
||||||
|
import net.codinux.banking.epcqrcode.parser.EpcQrCodeParser
|
||||||
|
import net.codinux.banking.ui.model.DecodeEpcQrCodeResult
|
||||||
|
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
|
||||||
|
|
||||||
class EpcQrCodeService {
|
class EpcQrCodeService {
|
||||||
|
|
||||||
fun generateEpcQrCode(receiverName: String, iban: String, bic: String?, amount: String?, reference: String?, informationForUser: String? = null, heightAndWidth: Int = EpcQrCode.DefaultHeightAndWidth): ByteArray {
|
private val generator = EpcQrCodeGenerator()
|
||||||
val generator = EpcQrCodeGenerator()
|
|
||||||
|
|
||||||
|
private val parser = EpcQrCodeParser()
|
||||||
|
|
||||||
|
|
||||||
|
fun generateEpcQrCode(receiverName: String, iban: String, bic: String?, amount: String?, reference: String?, informationForUser: String? = null, heightAndWidth: Int = EpcQrCode.DefaultHeightAndWidth): ByteArray {
|
||||||
return generator.generateEpcQrCode(EpcQrCodeConfig(receiverName, iban, bic, amount, reference, informationForUser, qrCodeHeightAndWidth = heightAndWidth)).bytes
|
return generator.generateEpcQrCode(EpcQrCodeConfig(receiverName, iban, bic, amount, reference, informationForUser, qrCodeHeightAndWidth = heightAndWidth)).bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun decode(decodedQrCodeText: String): DecodeEpcQrCodeResult {
|
||||||
|
val result = parser.parseEpcQrCode(decodedQrCodeText)
|
||||||
|
|
||||||
|
return if (result.epcQrCode != null) {
|
||||||
|
val code = result.epcQrCode!!
|
||||||
|
DecodeEpcQrCodeResult(
|
||||||
|
ShowTransferMoneyDialogData(null, code.receiverName, code.bic, code.iban, code.amount?.let { Amount(it) }, code.remittance),
|
||||||
|
charset = code.coding.charsetName
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
DecodeEpcQrCodeResult(null, result.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package net.codinux.banking.ui.service
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
expect object QrCodeService {
|
||||||
|
|
||||||
|
val supportsReadingQrCodesFromCamera: Boolean
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class QrCodeReadResult(
|
||||||
|
val decodedQrCodeText: String?
|
||||||
|
)
|
|
@ -67,6 +67,8 @@ class UiState : ViewModel() {
|
||||||
|
|
||||||
val showTransferMoneyDialogData = MutableStateFlow<ShowTransferMoneyDialogData?>(null)
|
val showTransferMoneyDialogData = MutableStateFlow<ShowTransferMoneyDialogData?>(null)
|
||||||
|
|
||||||
|
val showTransferMoneyFromEpcQrCodeScreen = MutableStateFlow(false)
|
||||||
|
|
||||||
val showCreateEpcQrCodeScreen = MutableStateFlow(false)
|
val showCreateEpcQrCodeScreen = MutableStateFlow(false)
|
||||||
|
|
||||||
val showAccountTransactionDetailsScreenForId = MutableStateFlow<Long?>(null)
|
val showAccountTransactionDetailsScreenForId = MutableStateFlow<Long?>(null)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.codinux.banking.ui.service
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
actual object QrCodeService {
|
||||||
|
|
||||||
|
actual val supportsReadingQrCodesFromCamera = false
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.codinux.banking.ui.service
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
actual object QrCodeService {
|
||||||
|
|
||||||
|
actual val supportsReadingQrCodesFromCamera = false
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package net.codinux.banking.ui.service
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
actual object QrCodeService {
|
||||||
|
|
||||||
|
actual val supportsReadingQrCodesFromCamera = false
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -32,6 +32,9 @@ androidx-biometric = "1.1.0"
|
||||||
androidx-test-junit = "1.2.1"
|
androidx-test-junit = "1.2.1"
|
||||||
compose-plugin = "1.6.11"
|
compose-plugin = "1.6.11"
|
||||||
|
|
||||||
|
zxing = "3.5.3"
|
||||||
|
camerax = "1.3.4"
|
||||||
|
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
@ -69,6 +72,11 @@ androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle
|
||||||
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidx-biometric" }
|
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidx-biometric" }
|
||||||
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
|
||||||
|
|
||||||
|
zxing-core = { group = "com.google.zxing", name = "core", version.ref = "zxing" }
|
||||||
|
camerax-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
|
||||||
|
camerax-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" }
|
||||||
|
camerax-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
|
||||||
|
|
||||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||||
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
|
Loading…
Reference in New Issue