Compare commits

...

6 Commits

17 changed files with 203 additions and 51 deletions

View File

@ -13,6 +13,7 @@ import net.codinux.banking.persistence.entities.BankAccessEntity
import net.codinux.banking.persistence.entities.UiSettingsEntity import net.codinux.banking.persistence.entities.UiSettingsEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.settings.AppSettings import net.codinux.banking.ui.model.settings.AppSettings
import net.codinux.banking.ui.model.settings.ImageSettings
interface BankingRepository { interface BankingRepository {
@ -25,6 +26,11 @@ interface BankingRepository {
suspend fun saveUiSettings(settings: UiSettingsEntity) suspend fun saveUiSettings(settings: UiSettingsEntity)
fun getImageSettings(id: String): ImageSettings?
suspend fun saveImageSettings(settings: ImageSettings)
fun getAllBanks(): List<BankAccessEntity> fun getAllBanks(): List<BankAccessEntity>
suspend fun persistBank(bank: BankAccess): BankAccessEntity suspend fun persistBank(bank: BankAccess): BankAccessEntity

View File

@ -13,6 +13,7 @@ import net.codinux.banking.persistence.entities.BankAccessEntity
import net.codinux.banking.persistence.entities.UiSettingsEntity import net.codinux.banking.persistence.entities.UiSettingsEntity
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.settings.AppSettings import net.codinux.banking.ui.model.settings.AppSettings
import net.codinux.banking.ui.model.settings.ImageSettings
import net.codinux.banking.ui.model.settings.TransactionsGrouping import net.codinux.banking.ui.model.settings.TransactionsGrouping
class InMemoryBankingRepository( class InMemoryBankingRepository(
@ -29,6 +30,8 @@ class InMemoryBankingRepository(
private var uiSettings: UiSettingsEntity = UiSettingsEntity(true, TransactionsGrouping.Month, true, true, true) private var uiSettings: UiSettingsEntity = UiSettingsEntity(true, TransactionsGrouping.Month, true, true, true)
private var imageSettings = mutableMapOf<String, ImageSettings>()
override fun getAppSettings(): AppSettings? = appSettings override fun getAppSettings(): AppSettings? = appSettings
@ -43,6 +46,13 @@ class InMemoryBankingRepository(
} }
override fun getImageSettings(id: String) = imageSettings[id]
override suspend fun saveImageSettings(settings: ImageSettings) {
imageSettings[settings.id] = settings
}
override fun getAllBanks(): List<BankAccessEntity> = banks.toList() override fun getAllBanks(): List<BankAccessEntity> = banks.toList()
override suspend fun persistBank(bank: BankAccess): BankAccessEntity { override suspend fun persistBank(bank: BankAccess): BankAccessEntity {

View File

@ -23,9 +23,9 @@ import net.codinux.banking.client.model.tan.TanMethod
import net.codinux.banking.client.model.tan.TanMethodType import net.codinux.banking.client.model.tan.TanMethodType
import net.codinux.banking.persistence.entities.* import net.codinux.banking.persistence.entities.*
import net.codinux.banking.ui.model.AccountTransactionViewModel import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.settings.AppAuthenticationMethod import net.codinux.banking.ui.model.settings.*
import net.codinux.banking.ui.model.settings.AppSettings import net.codinux.banking.ui.model.settings.AppSettings
import net.codinux.banking.ui.model.settings.TransactionsGrouping import net.codinux.banking.ui.model.settings.ImageSettings
import net.codinux.log.logger import net.codinux.log.logger
import kotlin.enums.EnumEntries import kotlin.enums.EnumEntries
import kotlin.js.JsName import kotlin.js.JsName
@ -39,7 +39,7 @@ open class SqliteBankingRepository : BankingRepository {
private val schema = BankmeisterDb.Schema private val schema = BankmeisterDb.Schema
private val sqlDriver = createSqlDriverDriver("Bankmeister.db", schema, 1L) private val sqlDriver = createSqlDriverDriver("Bankmeister.db", schema, 2L)
private val database = BankmeisterDb(sqlDriver) private val database = BankmeisterDb(sqlDriver)
@ -89,6 +89,16 @@ open class SqliteBankingRepository : BankingRepository {
} }
override fun getImageSettings(id: String): ImageSettings? =
settingsQueries.getImageSettings(id) { height, frequency ->
ImageSettings(id, mapToInt(height), mapToInt(frequency))
}.executeAsOneOrNull()
override suspend fun saveImageSettings(settings: ImageSettings) {
settingsQueries.upsertImageSettings(settings.id, mapInt(settings.height), mapInt(settings.frequency))
}
override fun getAllBanks(): List<BankAccessEntity> { override fun getAllBanks(): List<BankAccessEntity> {
val bankAccounts = getAllBankAccounts().groupBy { it.bankId } val bankAccounts = getAllBankAccounts().groupBy { it.bankId }
val tanMethods = getAllTanMethods().groupBy { it.bankId }.mapValues { it.value.toMutableList() } val tanMethods = getAllTanMethods().groupBy { it.bankId }.mapValues { it.value.toMutableList() }

View File

@ -0,0 +1,11 @@
package net.codinux.banking.ui.model.settings
class ImageSettings(
val id: String,
var height: Int,
var frequency: Int? = null // only needed for flicker code
) {
override fun toString() = "$id $height${if (frequency != null) " (frequency = $frequency)" else ""}"
}

View File

@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS ImageSettings (
id TEXT PRIMARY KEY,
height INTEGER NOT NULL,
width INTEGER, -- not used right now, add it just in case
frequency INTEGER
);
ALTER TABLE Holding DROP COLUMN quantity;
ALTER TABLE Holding ADD COLUMN quantity REAL;

View File

@ -70,3 +70,22 @@ SELECT * FROM UiSettings WHERE id = 1;
upsertUiSettings: upsertUiSettings:
INSERT OR REPLACE INTO UiSettings(id, transactionsGrouping, showBalance, showBankIcons, showColoredAmounts, showTransactionsInAlternatingColors) INSERT OR REPLACE INTO UiSettings(id, transactionsGrouping, showBalance, showBankIcons, showColoredAmounts, showTransactionsInAlternatingColors)
VALUES (1, ?, ?, ?, ?, ?); VALUES (1, ?, ?, ?, ?, ?);
CREATE TABLE IF NOT EXISTS ImageSettings (
id TEXT PRIMARY KEY,
height INTEGER NOT NULL,
width INTEGER, -- not used right now, add it just in case
frequency INTEGER
);
getImageSettings:
SELECT height, frequency FROM ImageSettings WHERE id = ?;
upsertImageSettings:
INSERT OR REPLACE INTO ImageSettings(id, height, frequency)
VALUES (?, ?, ?);

View File

@ -29,6 +29,10 @@ actual fun createSqlDriverDriver(dbName: String, schema: SqlSchema<QueryResult.A
} }
private fun setSchemaVersion(driver: SqlDriver, schemaVersion: Int) {
driver.execute(null, "PRAGMA schema_version=$schemaVersion;", 0)
}
private fun determineDataDirectory(): File { private fun determineDataDirectory(): File {
val currentDir = Path(System.getProperty("user.dir")) val currentDir = Path(System.getProperty("user.dir"))

View File

@ -151,7 +151,7 @@ android {
applicationId = "net.codinux.banking.android" // the appId of the old Bankmeister app to be able to use the old PlayStore entry applicationId = "net.codinux.banking.android" // the appId of the old Bankmeister app to be able to use the old PlayStore entry
minSdk = libs.versions.android.minSdk.get().toInt() minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 17 versionCode = 18
versionName = "1.0.0-Alpha-15" versionName = "1.0.0-Alpha-15"
} }
packaging { packaging {

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import net.codinux.banking.client.model.tan.FlickerCode import net.codinux.banking.client.model.tan.FlickerCode
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.model.Config.NewLine import net.codinux.banking.ui.model.Config.NewLine
import net.codinux.banking.ui.service.tan.Bit import net.codinux.banking.ui.service.tan.Bit
import net.codinux.banking.ui.service.tan.FlickerCodeAnimator import net.codinux.banking.ui.service.tan.FlickerCodeAnimator
@ -22,23 +23,38 @@ import net.codinux.banking.ui.service.tan.Step
private const val FrequencyStepSize = 2 private const val FrequencyStepSize = 2
private val DefaultStripesHeight = 240.dp
private val StripesHeightStepSize = 7.dp private val StripesHeightStepSize = 7.dp
private val StripesWidthStepSize = 2.dp private val StripesWidthStepSize = 2.dp
private val SpaceBetweenStripesStepSize = 1.dp private val SpaceBetweenStripesStepSize = 1.dp
private val bankingService = DI.bankingService
@Composable @Composable
fun ChipTanFlickerCodeView(flickerCode: FlickerCode, textColor: Color = Color.Black) { fun ChipTanFlickerCodeView(flickerCode: FlickerCode, textColor: Color = Color.Black) {
val animator = remember { FlickerCodeAnimator() } val animator = remember { FlickerCodeAnimator() }
var stripesHeight by remember { mutableStateOf(240.dp) } val flickerCodeSettings by remember { mutableStateOf(bankingService.getImageSettingsOrCreateDefault("FlickerCode", 240)) }
var stripesWidth by remember { mutableStateOf(45.dp) } val sizeFactor by remember { mutableStateOf(
if (flickerCodeSettings.height == 240) {
1
} else {
val diff = flickerCodeSettings.height.dp - DefaultStripesHeight
var spaceBetweenStripes by remember { mutableStateOf(15.dp) } (diff / StripesHeightStepSize).toInt()
}
) }
var frequency by remember { mutableStateOf(FlickerCodeAnimator.DefaultFrequency) } var stripesHeight by remember { mutableStateOf(DefaultStripesHeight + StripesHeightStepSize.times(sizeFactor)) }
var stripesWidth by remember { mutableStateOf(45.dp + StripesWidthStepSize.times(sizeFactor)) }
var spaceBetweenStripes by remember { mutableStateOf(15.dp + SpaceBetweenStripesStepSize.times(sizeFactor)) }
var frequency by remember { mutableStateOf(flickerCodeSettings.frequency ?: FlickerCodeAnimator.DefaultFrequency) }
var isPaused by remember { mutableStateOf(false) } var isPaused by remember { mutableStateOf(false) }
@ -51,6 +67,9 @@ fun ChipTanFlickerCodeView(flickerCode: FlickerCode, textColor: Color = Color.Bl
stripesWidth = width stripesWidth = width
stripesHeight = height stripesHeight = height
spaceBetweenStripes = spaceBetween spaceBetweenStripes = spaceBetween
flickerCodeSettings.height = height.value.toInt()
bankingService.saveImageSettingsDebounced(flickerCodeSettings, coroutineScope)
} }
fun decreaseSize() { fun decreaseSize() {
@ -76,6 +95,9 @@ fun ChipTanFlickerCodeView(flickerCode: FlickerCode, textColor: Color = Color.Bl
frequency = newFrequency frequency = newFrequency
animator.setFrequency(newFrequency) animator.setFrequency(newFrequency)
flickerCodeSettings.frequency = newFrequency
bankingService.saveImageSettingsDebounced(flickerCodeSettings, coroutineScope)
} }
fun decreaseFrequency() { fun decreaseFrequency() {

View File

@ -15,7 +15,7 @@ import androidx.compose.ui.unit.dp
fun ImageSizeControls(decreaseEnabled: Boolean, increaseEnabled: Boolean, textColor: Color = Color.Black, onDecreaseImageSize: () -> Unit, onIncreaseImageSize: () -> Unit) { fun ImageSizeControls(decreaseEnabled: Boolean, increaseEnabled: Boolean, textColor: Color = Color.Black, onDecreaseImageSize: () -> Unit, onIncreaseImageSize: () -> Unit) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Text("Größe", color = textColor) Text("Größe", color = textColor, modifier = Modifier.padding(end = 6.dp))
TextButton({ onDecreaseImageSize() }, enabled = decreaseEnabled, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) { TextButton({ onDecreaseImageSize() }, enabled = decreaseEnabled, modifier = Modifier.width(48.dp), colors = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)) {
Icon(Icons.Filled.ZoomOut, contentDescription = "Bild verkleinern", Modifier.size(28.dp), tint = textColor) Icon(Icons.Filled.ZoomOut, contentDescription = "Bild verkleinern", Modifier.size(28.dp), tint = textColor)

View File

@ -0,0 +1,54 @@
package net.codinux.banking.ui.composables.tan
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.service.createImageBitmap
private val bankingService = DI.bankingService
@Composable
fun ImageView(
imageBytes: ByteArray,
imageSettingsId: String,
contentDescription: String,
initialImageHeight: Int = 300,
minImageHeight: Int = 0,
maxImageHeight: Int? = null,
changeImageSizeStep: Int = 25,
textColor: Color = Colors.MaterialThemeTextColor,
) {
val imageSettings = bankingService.getImageSettingsOrCreateDefault(imageSettingsId, initialImageHeight)
var imageHeight by remember { mutableStateOf(imageSettings.height) }
val coroutineScope = rememberCoroutineScope()
fun changeImageSize(by: Int) {
imageHeight += by
imageSettings.height = imageHeight
bankingService.saveImageSettingsDebounced(imageSettings, coroutineScope)
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
ImageSizeControls(imageHeight > minImageHeight, maxImageHeight == null || imageHeight < maxImageHeight, textColor, { changeImageSize(-changeImageSizeStep) }) { changeImageSize(changeImageSizeStep) }
}
Row(Modifier.fillMaxWidth().padding(top = 6.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
Image(createImageBitmap(imageBytes), contentDescription, Modifier.height(imageHeight.dp), contentScale = ContentScale.FillHeight)
}
}

View File

@ -36,7 +36,7 @@ object DI {
var bankingRepository: BankingRepository = InMemoryBankingRepository(emptyList()) var bankingRepository: BankingRepository = InMemoryBankingRepository(emptyList())
val bankingService by lazy { BankingService(uiState, uiSettings, bankingRepository, bankFinder) } val bankingService by lazy { BankingService(uiState, uiSettings, uiService, bankingRepository, bankFinder) }
fun setRepository(repository: BankingRepository) { fun setRepository(repository: BankingRepository) {

View File

@ -1,33 +1,34 @@
package net.codinux.banking.ui.dialogs package net.codinux.banking.ui.dialogs
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.* import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import net.codinux.banking.client.model.tan.* import net.codinux.banking.client.model.tan.ActionRequiringTan
import net.codinux.banking.client.model.tan.AllowedTanFormat
import net.codinux.banking.client.model.tan.EnterTanResult
import net.codinux.banking.ui.composables.BankIcon import net.codinux.banking.ui.composables.BankIcon
import net.codinux.banking.ui.composables.tan.ChipTanFlickerCodeView import net.codinux.banking.ui.composables.tan.ChipTanFlickerCodeView
import net.codinux.banking.ui.composables.tan.ImageSizeControls import net.codinux.banking.ui.composables.tan.ImageView
import net.codinux.banking.ui.config.*
import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.config.Internationalization
import net.codinux.banking.ui.forms.CaptionText import net.codinux.banking.ui.forms.CaptionText
import net.codinux.banking.ui.forms.OutlinedTextField import net.codinux.banking.ui.forms.OutlinedTextField
import net.codinux.banking.ui.forms.Select import net.codinux.banking.ui.forms.Select
import net.codinux.banking.ui.model.Config.NewLine import net.codinux.banking.ui.model.Config.NewLine
import net.codinux.banking.ui.model.TanChallengeReceived import net.codinux.banking.ui.model.TanChallengeReceived
import net.codinux.banking.ui.model.error.ErroneousAction import net.codinux.banking.ui.model.error.ErroneousAction
import net.codinux.banking.ui.service.createImageBitmap
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
@ -41,10 +42,6 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
val isNotADecoupledTanMethod = !!!isDecoupledMethod val isNotADecoupledTanMethod = !!!isDecoupledMethod
var tanImageHeight by remember { mutableStateOf(250) }
val minTanImageHeight = 100
val maxTanImageHeight = 500
var showSelectingTanMediumNotImplementedWarning by remember { mutableStateOf(false) } var showSelectingTanMediumNotImplementedWarning by remember { mutableStateOf(false) }
val textFieldFocus = remember { FocusRequester() } val textFieldFocus = remember { FocusRequester() }
@ -150,13 +147,8 @@ fun EnterTanDialog(tanChallengeReceived: TanChallengeReceived, onDismiss: () ->
tanImage.imageBytesBase64?.let { imageBytesBase64 -> tanImage.imageBytesBase64?.let { imageBytesBase64 ->
val imageBytes = Base64.decode(imageBytesBase64) val imageBytes = Base64.decode(imageBytesBase64)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { // if it becomes necessary may also add the bank to ImageSettings.id to make ImageSettings bank specific
ImageSizeControls(tanImageHeight > minTanImageHeight, tanImageHeight < maxTanImageHeight, textColor, { tanImageHeight -= 25 }) { tanImageHeight += 25 } ImageView(imageBytes, challenge.selectedTanMethod.type.toString(), "Bild mit enkodierter TAN", 250, 100, 500, textColor = textColor)
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
Image(createImageBitmap(imageBytes), "Bild mit enkodierter TAN", Modifier.height(tanImageHeight.dp), contentScale = ContentScale.FillHeight)
}
} }
} }
} }

View File

@ -1,6 +1,5 @@
package net.codinux.banking.ui.screens package net.codinux.banking.ui.screens
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
@ -10,13 +9,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import net.codinux.banking.persistence.entities.BankAccountEntity import net.codinux.banking.persistence.entities.BankAccountEntity
import net.codinux.banking.ui.composables.BankIcon import net.codinux.banking.ui.composables.BankIcon
import net.codinux.banking.ui.composables.tan.ImageSizeControls import net.codinux.banking.ui.composables.tan.ImageView
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.extensions.ImeNext import net.codinux.banking.ui.extensions.ImeNext
import net.codinux.banking.ui.extensions.rememberVerticalScroll import net.codinux.banking.ui.extensions.rememberVerticalScroll
@ -24,7 +21,6 @@ import net.codinux.banking.ui.forms.CaptionText
import net.codinux.banking.ui.forms.OutlinedTextField import net.codinux.banking.ui.forms.OutlinedTextField
import net.codinux.banking.ui.forms.Select import net.codinux.banking.ui.forms.Select
import net.codinux.banking.ui.model.Config.NewLine import net.codinux.banking.ui.model.Config.NewLine
import net.codinux.banking.ui.service.createImageBitmap
import net.codinux.log.Log import net.codinux.log.Log
private val epcQrCodeService = DI.epcQrCodeService private val epcQrCodeService = DI.epcQrCodeService
@ -81,10 +77,6 @@ fun CreateEpcQrCodeScreen(onClosed: () -> Unit) {
} }
} }
var imageHeight by remember { mutableStateOf(350) }
val minImageHeight = 100
val maxImageHeight = 700
FullscreenViewBase("EPC QR Code erstellen", "Schließen", onClosed = onClosed) { FullscreenViewBase("EPC QR Code erstellen", "Schließen", onClosed = onClosed) {
Column(Modifier.fillMaxWidth().rememberVerticalScroll()) { Column(Modifier.fillMaxWidth().rememberVerticalScroll()) {
@ -94,15 +86,9 @@ fun CreateEpcQrCodeScreen(onClosed: () -> Unit) {
Text("Mit EPC QR Codes, welche als GiroCode, scan2code, ... vermarktet werden, können Überweisungsdaten ganz einfach von Banking Apps eingelesen werden.") 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.") Text("Hier können Sie Ihren eigenen erstellen, so dass jemand Ihre Überweisungsdaten einlesen und Ihnen ganz schnell Geld überweisen kann.")
} else { } else {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { ImageView(epcQrCodeBytes!!, "EpcQrCode", "Erzeugter EPC QR Code", 350, 100, 700)
ImageSizeControls(imageHeight > minImageHeight, imageHeight < maxImageHeight, Colors.MaterialThemeTextColor, { imageHeight -= 25 }) { imageHeight += 25 }
}
Row(Modifier.fillMaxWidth().padding(bottom = 8.dp), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) { Row(Modifier.fillMaxWidth().padding(top = 8.dp), horizontalArrangement = Arrangement.Center) {
Image(createImageBitmap(epcQrCodeBytes!!), "Erzeugter EPC QR Code", Modifier.height(imageHeight.dp), contentScale = ContentScale.FillHeight)
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Text("Scannen Sie diesen Code auf einem anderen Gerät mit einer Banking App, z. B. Bankmeister", textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp).padding(top = 8.dp)) Text("Scannen Sie diesen Code auf einem anderen Gerät mit einer Banking App, z. B. Bankmeister", textAlign = TextAlign.Center, modifier = Modifier.padding(horizontal = 16.dp).padding(top = 8.dp))
} }
} }

View File

@ -15,6 +15,7 @@ import net.codinux.banking.client.model.request.GetAccountDataRequest
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
import net.codinux.banking.client.model.response.* import net.codinux.banking.client.model.response.*
import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.model.securitiesaccount.Holding
import net.codinux.banking.client.model.tan.TanMethodType
import net.codinux.banking.client.service.BankingModelService import net.codinux.banking.client.service.BankingModelService
import net.codinux.banking.persistence.BankingRepository import net.codinux.banking.persistence.BankingRepository
import net.codinux.banking.persistence.entities.AccountTransactionEntity import net.codinux.banking.persistence.entities.AccountTransactionEntity
@ -28,6 +29,7 @@ import net.codinux.banking.ui.model.BankInfo
import net.codinux.banking.ui.model.error.* import net.codinux.banking.ui.model.error.*
import net.codinux.banking.ui.model.events.AccountTransactionsRetrievedEvent import net.codinux.banking.ui.model.events.AccountTransactionsRetrievedEvent
import net.codinux.banking.ui.model.events.TransferredMoneyEvent import net.codinux.banking.ui.model.events.TransferredMoneyEvent
import net.codinux.banking.ui.model.settings.ImageSettings
import net.codinux.banking.ui.model.settings.AppSettings import net.codinux.banking.ui.model.settings.AppSettings
import net.codinux.banking.ui.settings.UiSettings import net.codinux.banking.ui.settings.UiSettings
import net.codinux.banking.ui.state.UiState import net.codinux.banking.ui.state.UiState
@ -39,10 +41,13 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi
class BankingService( class BankingService(
private val uiState: UiState, private val uiState: UiState,
private val uiSettings: UiSettings, private val uiSettings: UiSettings,
private val uiService: UiService,
private val bankingRepository: BankingRepository, private val bankingRepository: BankingRepository,
private val bankFinder: BankFinder private val bankFinder: BankFinder
) { ) {
private val preferredTanMethods = TanMethodType.NonVisualOrImageBasedOrFlickerCode
private val client = FinTs4kBankingClient(FinTsClientOptions(true, closeDialogs = false), SimpleBankingClientCallback { tanChallenge, callback -> private val client = FinTs4kBankingClient(FinTsClientOptions(true, closeDialogs = false), SimpleBankingClientCallback { tanChallenge, callback ->
uiState.receivedTanChallenge(tanChallenge, callback) uiState.receivedTanChallenge(tanChallenge, callback)
}) })
@ -93,6 +98,22 @@ class BankingService(
)) ))
fun getImageSettingsOrCreateDefault(id: String, defaultHeight: Int): ImageSettings =
bankingRepository.getImageSettings(id) ?: ImageSettings(id, defaultHeight)
fun saveImageSettingsDebounced(settings: ImageSettings, coroutineScope: CoroutineScope) {
uiService.debounce(coroutineScope, 1_000) {
saveImageSettings(settings)
}
}
suspend fun saveImageSettings(settings: ImageSettings) = try {
bankingRepository.saveImageSettings(settings)
} catch (e: Throwable) {
log.error(e) { "Could not save ImageSettings ${settings.id}"}
}
fun getAllBanks() = bankingRepository.getAllBanks() fun getAllBanks() = bankingRepository.getAllBanks()
suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?) { suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?) {
@ -167,7 +188,9 @@ class BankingService(
suspend fun addAccount(bank: BankInfo, loginName: String, password: String, retrieveAllTransactions: Boolean = false): Boolean { suspend fun addAccount(bank: BankInfo, loginName: String, password: String, retrieveAllTransactions: Boolean = false): Boolean {
try { try {
val retrieveTransactions = if (retrieveAllTransactions) RetrieveTransactions.All else RetrieveTransactions.OfLast90Days val retrieveTransactions = if (retrieveAllTransactions) RetrieveTransactions.All else RetrieveTransactions.OfLast90Days
val response = client.getAccountDataAsync(GetAccountDataRequest(bank.domesticBankCode, loginName, password, GetAccountDataOptions(retrieveTransactions), mapBankInfo(bank))) val options = GetAccountDataOptions(retrieveTransactions, preferredTanMethods = preferredTanMethods)
val response = client.getAccountDataAsync(GetAccountDataRequest(bank.domesticBankCode, loginName, password, options, mapBankInfo(bank)))
if (response.type == ResponseType.Success && response.data != null) { if (response.type == ResponseType.Success && response.data != null) {
handleSuccessfulGetAccountDataResponse(response.data!!) handleSuccessfulGetAccountDataResponse(response.data!!)
@ -231,7 +254,7 @@ class BankingService(
private suspend fun updateAccountTransactions(bank: BankAccessEntity, bankAccount: BankAccountEntity? = null) { private suspend fun updateAccountTransactions(bank: BankAccessEntity, bankAccount: BankAccountEntity? = null) {
try { try {
val response = client.updateAccountTransactionsAsync(bank, bankAccount?.let { listOf(it) }) val response = client.updateAccountTransactionsAsync(bank, bankAccount?.let { listOf(it) }, preferredTanMethods)
handleCommonBankingClientResponseValues(response) handleCommonBankingClientResponseValues(response)
@ -401,7 +424,8 @@ class BankingService(
bank, account, bank, account,
recipientName, recipientAccountIdentifier, recipientBankIdentifier, recipientName, recipientAccountIdentifier, recipientBankIdentifier,
amount, "EUR", amount, "EUR",
paymentReference, instantTransfer paymentReference, instantTransfer,
preferredTanMethods
)) ))
handleCommonBankingClientResponseValues(response) handleCommonBankingClientResponseValues(response)

View File

@ -4,9 +4,9 @@ import kotlinx.coroutines.*
class UiService { class UiService {
fun debounce(coroutineScope: CoroutineScope, action: () -> Unit): Job { fun debounce(coroutineScope: CoroutineScope, delayMillis: Long = 250, action: suspend () -> Unit): Job {
return coroutineScope.launch { return coroutineScope.launch {
delay(250) delay(delayMillis)
action() action()
} }
} }

View File

@ -2,7 +2,7 @@
kotlin = "2.0.10" kotlin = "2.0.10"
kotlinx-coroutines = "1.8.1" kotlinx-coroutines = "1.8.1"
banking-client = "0.7.0" banking-client = "0.7.1"
epcqrcode = "0.5.0" epcqrcode = "0.5.0"
kcsv = "2.2.0" kcsv = "2.2.0"