From 3d474f38ae61c015253436e5042078033ef0e01b Mon Sep 17 00:00:00 2001 From: dankito Date: Tue, 1 Oct 2024 21:48:19 +0200 Subject: [PATCH] Implemented generating EQP QR Code --- composeApp/build.gradle.kts | 1 + .../ui/appskeleton/FloatingActionMenu.kt | 6 + .../banking/ui/composables/StateHandler.kt | 5 + .../net/codinux/banking/ui/config/Colors.kt | 3 + .../net/codinux/banking/ui/config/DI.kt | 2 + .../banking/ui/dialogs/EnterTanDialog.kt | 2 +- .../ui/screens/CreateEpcQrCodeScreen.kt | 109 ++++++++++++++++++ .../banking/ui/service/EpcQrCodeService.kt | 15 +++ .../net/codinux/banking/ui/state/UiState.kt | 2 + gradle/libs.versions.toml | 2 + 10 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/CreateEpcQrCodeScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/EpcQrCodeService.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 1e55730..ce1063c 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -78,6 +78,7 @@ kotlin { implementation(libs.banking.client.model) implementation(libs.fints4k.banking.client) + implementation(libs.epcqrcode) implementation(libs.kcsv) implementation(libs.klf) diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FloatingActionMenu.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FloatingActionMenu.kt index 3e9d953..3fd0fc8 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FloatingActionMenu.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FloatingActionMenu.kt @@ -56,6 +56,12 @@ fun FloatingActionMenu( if (fabVisibilityAnimation.value > 0) { Box(Modifier.fillMaxSize().padding(bottom = bottomPadding, end = 12.dp), contentAlignment = Alignment.BottomEnd) { Column(Modifier, horizontalAlignment = Alignment.End) { + FloatingActionMenuItem("QR Code erstellen", "EPC QR Code erstellen") { + handleClick { + uiState.showCreateEpcQrCodeScreen.value = true + } + } + FloatingActionMenuItem("Überweisung", "Neue Überweisung", enabled = accountsThatSupportMoneyTransfer.isNotEmpty()) { handleClick { uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData() diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt index e6ce246..3ff4206 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt @@ -15,6 +15,7 @@ private val formatUtil = DI.formatUtil fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { val showAddAccountDialog by uiState.showAddAccountDialog.collectAsState() val showTransferMoneyDialogData by uiState.showTransferMoneyDialogData.collectAsState() + val showCreateEpcQrCodeScreen by uiState.showCreateEpcQrCodeScreen.collectAsState() val showAccountTransactionDetailsScreenForId by uiState.showAccountTransactionDetailsScreenForId.collectAsState() val showBankSettingsScreenForBank by uiState.showBankSettingsScreenForBank.collectAsState() @@ -38,6 +39,10 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { TransferMoneyDialog(data) { uiState.showTransferMoneyDialogData.value = null } } + if (showCreateEpcQrCodeScreen) { + CreateEpcQrCodeScreen { uiState.showCreateEpcQrCodeScreen.value = false } + } + showAccountTransactionDetailsScreenForId?.let { transactionId -> DI.bankingService.getTransaction(transactionId)?.let { transaction -> diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt index d6fa56b..2c78fd7 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt @@ -22,6 +22,9 @@ object Colors { val BackgroundColorLight = Color("#FFFFFF") + val MaterialThemeTextColor = Color(0xFF4F4F4F) // to match dialog's text color of Material theme + + val DrawerContentBackground = BackgroundColorDark val DrawerPrimaryText = PrimaryTextColorDark diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt index 666559f..ac4ac62 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/DI.kt @@ -29,6 +29,8 @@ object DI { val accountTransactionsFilterService = AccountTransactionsFilterService() + val epcQrCodeService = EpcQrCodeService() + val uiService = UiService() diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/EnterTanDialog.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/EnterTanDialog.kt index 9c7f589..c1b1f55 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/EnterTanDialog.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/dialogs/EnterTanDialog.kt @@ -136,7 +136,7 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () -> if (challenge.tanImage != null || challenge.flickerCode != null) { Column(Modifier.fillMaxWidth().padding(top = 6.dp)) { - val textColor = Color(0xFF4F4F4F) // to match dialog's text color of Material theme + val textColor = Colors.MaterialThemeTextColor // to match dialog's text color of Material theme challenge.flickerCode?.let { flickerCode -> ChipTanFlickerCodeView(flickerCode, textColor) diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/CreateEpcQrCodeScreen.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/CreateEpcQrCodeScreen.kt new file mode 100644 index 0000000..fce88e7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/screens/CreateEpcQrCodeScreen.kt @@ -0,0 +1,109 @@ +package net.codinux.banking.ui.screens + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import net.codinux.banking.ui.composables.tan.ImageSizeControls +import net.codinux.banking.ui.config.Colors +import net.codinux.banking.ui.config.DI +import net.codinux.banking.ui.extensions.ImeNext +import net.codinux.banking.ui.forms.OutlinedTextField +import net.codinux.banking.ui.service.createImageBitmap + +private val epcQrCodeService = DI.epcQrCodeService + +@Composable +fun CreateEpcQrCodeScreen(onClosed: () -> Unit) { + + var receiverName by remember { mutableStateOf("") } + + var iban by remember { mutableStateOf("") } + + var bic by remember { mutableStateOf("") } + + var amount by remember { mutableStateOf("") } + + var reference by remember { mutableStateOf("") } + + + val epcQrCodeBytes by remember(receiverName, iban, bic, amount, reference) { + derivedStateOf { + if (receiverName.isNotBlank() && iban.isNotBlank()) { + epcQrCodeService.generateEpcQrCode(receiverName, iban, bic.takeUnless { it.isBlank() }, amount.takeUnless { it.isBlank() }, reference.takeUnless { it.isBlank() }) + } else { + null + } + } + } + + var imageHeight by remember { mutableStateOf(350) } + val minImageHeight = 100 + val maxImageHeight = 700 + + + FullscreenViewBase("EPC QR Code erstellen", "Schließen", onClosed = onClosed) { + Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + if (epcQrCodeBytes == null) { + Text("Mit EPC QR Codes, welche als GiroCode, scan2code, ... vermarktet werden, können Überweisungsdaten ganz einfach von Banking Apps eingelesen werden.") + Text("Hier können Sie Ihren eigenen erstellen, so dass jemand Ihre Überweisungsdaten einlesen und Ihnen ganz schnell Geld überweisen kann.") + } else { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { + ImageSizeControls(imageHeight > minImageHeight, imageHeight < maxImageHeight, Colors.MaterialThemeTextColor, { imageHeight -= 25 }) { imageHeight += 25 } + } + + Row(Modifier.fillMaxWidth().padding(bottom = 8.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { + Image(createImageBitmap(epcQrCodeBytes!!), "Erzeugter EPC QR Code", Modifier.height(imageHeight.dp), contentScale = ContentScale.FillHeight) + } + } + + + OutlinedTextField( + label = { Text("Empfänger*in") }, + value = receiverName, + onValueChange = { receiverName = it }, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + keyboardOptions = KeyboardOptions.ImeNext + ) + + OutlinedTextField( + label = { Text("IBAN") }, + value = iban, + onValueChange = { iban = it }, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + keyboardOptions = KeyboardOptions.ImeNext + ) + + OutlinedTextField( + label = { Text("BIC (optional)") }, + value = bic, + onValueChange = { bic = it }, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + keyboardOptions = KeyboardOptions.ImeNext + ) + + OutlinedTextField( + label = { Text("Betrag (optional)") }, + value = amount, + onValueChange = { amount = it }, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + keyboardOptions = KeyboardOptions.ImeNext + ) + + OutlinedTextField( + label = { Text("Verwendungszweck (optional)") }, + value = reference, + onValueChange = { reference = it }, + modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp), + keyboardOptions = KeyboardOptions.ImeNext + ) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/EpcQrCodeService.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/EpcQrCodeService.kt new file mode 100644 index 0000000..d3fa4ae --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/EpcQrCodeService.kt @@ -0,0 +1,15 @@ +package net.codinux.banking.ui.service + +import net.codinux.banking.epcqrcode.EpcQrCode +import net.codinux.banking.epcqrcode.EpcQrCodeConfig +import net.codinux.banking.epcqrcode.EpcQrCodeGenerator + +class EpcQrCodeService { + + fun generateEpcQrCode(receiverName: String, iban: String, bic: String?, amount: String?, reference: String?, heightAndWidth: Int = EpcQrCode.DefaultHeightAndWidth): ByteArray { + val generator = EpcQrCodeGenerator() + + return generator.generateEpcQrCode(EpcQrCodeConfig(receiverName, iban, bic, amount, reference, qrCodeHeightAndWidth = heightAndWidth)).bytes + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt index f5a53c1..e2fe157 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt @@ -67,6 +67,8 @@ class UiState : ViewModel() { val showTransferMoneyDialogData = MutableStateFlow(null) + val showCreateEpcQrCodeScreen = MutableStateFlow(false) + val showAccountTransactionDetailsScreenForId = MutableStateFlow(null) val showBankSettingsScreenForBank = MutableStateFlow(null) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1c512de..0014f23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ kotlin = "2.0.10" kotlinx-coroutines = "1.8.1" banking-client = "0.6.2-SNAPSHOT" +epcqrcode = "1.0.0-SNAPSHOT" kcsv = "2.2.0" kotlinx-serializable = "1.7.1" @@ -36,6 +37,7 @@ junit = "4.13.2" [libraries] banking-client-model = { group = "net.codinux.banking.client", name = "banking-client-model", version.ref = "banking-client" } fints4k-banking-client = { group = "net.codinux.banking.client", name = "fints4k-banking-client", version.ref = "banking-client" } +epcqrcode = { group = "net.codinux.banking.epcqrcode", name = "epc-qr-code", version.ref = "epcqrcode" } kcsv = { group = "net.codinux.csv", name = "kcsv", version.ref = "kcsv" } coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }