Compare commits

...

4 Commits

10 changed files with 86 additions and 40 deletions

View File

@ -31,7 +31,7 @@ interface BankingRepository {
suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?) suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?)
suspend fun updateBank(bank: BankAccessEntity, clientData: String?) suspend fun updateBank(bank: BankAccessEntity, serializedClientData: String?)
suspend fun updateAccount(account: BankAccountEntity, userSetDisplayName: String?, hideAccount: Boolean, includeInAutomaticAccountsUpdate: Boolean) suspend fun updateAccount(account: BankAccountEntity, userSetDisplayName: String?, hideAccount: Boolean, includeInAutomaticAccountsUpdate: Boolean)

View File

@ -55,7 +55,7 @@ class InMemoryBankingRepository(
// no-op // no-op
} }
override suspend fun updateBank(bank: BankAccessEntity, clientData: String?) { override suspend fun updateBank(bank: BankAccessEntity, serializedClientData: String?) {
// no-op // no-op
} }

View File

@ -95,9 +95,9 @@ open class SqliteBankingRepository : BankingRepository {
val tanMedia = getAllTanMedia().groupBy { it.bankId }.mapValues { it.value.toMutableList() } val tanMedia = getAllTanMedia().groupBy { it.bankId }.mapValues { it.value.toMutableList() }
val holdings = getAllHoldings().groupBy { it.accountId } val holdings = getAllHoldings().groupBy { it.accountId }
return bankQueries.getAllBanks { id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, countryCode, clientData, userSetDisplayName, displayIndex, iconUrl, wrongCredentialsEntered -> return bankQueries.getAllBanks { id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, countryCode, serializedClientData, userSetDisplayName, displayIndex, iconUrl, wrongCredentialsEntered ->
BankAccessEntity(id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, getAccountsOfBank(id, bankAccounts, holdings), selectedTanMethodIdentifier, tanMethods[id] ?: mutableListOf(), selectedTanMediumIdentifier, tanMedia[id] ?: mutableListOf(), BankAccessEntity(id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, getAccountsOfBank(id, bankAccounts, holdings), selectedTanMethodIdentifier, tanMethods[id] ?: mutableListOf(), selectedTanMediumIdentifier, tanMedia[id] ?: mutableListOf(),
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, countryCode, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered, clientData) bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, countryCode, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered, null, serializedClientData)
}.executeAsList() }.executeAsList()
} }
@ -111,7 +111,7 @@ open class SqliteBankingRepository : BankingRepository {
return bankQueries.transactionWithResult { return bankQueries.transactionWithResult {
bankQueries.insertBank(bank.domesticBankCode, bank.loginName, bank.password, bank.bankName, bank.bic, bankQueries.insertBank(bank.domesticBankCode, bank.loginName, bank.password, bank.bankName, bank.bic,
bank.customerName, bank.userId, bank.selectedTanMethodIdentifier, bank.selectedTanMediumIdentifier, bank.customerName, bank.userId, bank.selectedTanMethodIdentifier, bank.selectedTanMediumIdentifier,
bank.bankingGroup?.name, bank.serverAddress, bank.countryCode, bank.clientData, bank.userSetDisplayName, bank.displayIndex.toLong(), bank.iconUrl, bank.wrongCredentialsEntered bank.bankingGroup?.name, bank.serverAddress, bank.countryCode, bank.serializedClientData, bank.userSetDisplayName, bank.displayIndex.toLong(), bank.iconUrl, bank.wrongCredentialsEntered
) )
val bankId = getLastInsertedId() // getLastInsertedId() / last_insert_rowid() has to be called in a transaction with the insert operation, otherwise it will not work val bankId = getLastInsertedId() // getLastInsertedId() / last_insert_rowid() has to be called in a transaction with the insert operation, otherwise it will not work
@ -141,10 +141,10 @@ open class SqliteBankingRepository : BankingRepository {
} }
} }
override suspend fun updateBank(bank: BankAccessEntity, clientData: String?) { override suspend fun updateBank(bank: BankAccessEntity, serializedClientData: String?) {
bankQueries.transaction { bankQueries.transaction {
if (clientData != null) { if (serializedClientData != null) {
bankQueries.updateBankClientData(clientData, bank.id) bankQueries.updateBankClientData(serializedClientData, bank.id)
} }
} }
} }

View File

@ -34,7 +34,8 @@ class BankAccessEntity(
iconUrl: String? = null, iconUrl: String? = null,
wrongCredentialsEntered: Boolean = false, wrongCredentialsEntered: Boolean = false,
clientData: String? = null clientData: Any? = null,
serializedClientData: String? = null
) : BankAccess(domesticBankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, bankingGroup, serverAddress, countryCode) { ) : BankAccess(domesticBankCode, loginName, password, bankName, bic, customerName, userId, accounts, selectedTanMethodIdentifier, tanMethods, selectedTanMediumIdentifier, tanMedia, bankingGroup, serverAddress, countryCode) {
init { init {
@ -45,6 +46,7 @@ class BankAccessEntity(
this.wrongCredentialsEntered = wrongCredentialsEntered this.wrongCredentialsEntered = wrongCredentialsEntered
this.clientData = clientData this.clientData = clientData
this.serializedClientData = serializedClientData
} }

View File

@ -25,7 +25,7 @@ import net.codinux.banking.ui.composables.settings.UiSettings
import net.codinux.banking.ui.composables.text.ItemDivider import net.codinux.banking.ui.composables.text.ItemDivider
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.DI
import net.codinux.banking.ui.extensions.verticalScroll import net.codinux.banking.ui.extensions.rememberVerticalScroll
import org.jetbrains.compose.resources.imageResource import org.jetbrains.compose.resources.imageResource
private val uiState = DI.uiState private val uiState = DI.uiState
@ -60,7 +60,7 @@ fun SideMenuContent() {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
Column(Modifier.fillMaxSize().background(Colors.DrawerContentBackground).verticalScroll()) { Column(Modifier.fillMaxSize().background(Colors.DrawerContentBackground).rememberVerticalScroll()) {
Column(Modifier.fillMaxWidth().height(HeaderHeight.dp).background(HeaderBackground).padding(16.dp)) { Column(Modifier.fillMaxWidth().height(HeaderHeight.dp).background(HeaderBackground).padding(16.dp)) {
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))

View File

@ -1,9 +1,7 @@
package net.codinux.banking.ui.extensions package net.codinux.banking.ui.extensions
import androidx.compose.foundation.ScrollState import androidx.compose.foundation.*
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
@ -14,8 +12,14 @@ import net.codinux.log.Log
fun Modifier.verticalScroll() = this.verticalScroll(ScrollState(0), enabled = true) fun Modifier.verticalScroll() = this.verticalScroll(ScrollState(0), enabled = true)
@Composable
fun Modifier.rememberVerticalScroll() = this.verticalScroll(rememberScrollState())
fun Modifier.horizontalScroll() = this.horizontalScroll(ScrollState(0), enabled = true) fun Modifier.horizontalScroll() = this.horizontalScroll(ScrollState(0), enabled = true)
@Composable
fun Modifier.rememberHorizontalScroll() = this.horizontalScroll(rememberScrollState())
@Composable @Composable
// we need to support three different cases: // we need to support three different cases:

View File

@ -1,9 +1,7 @@
package net.codinux.banking.ui.screens package net.codinux.banking.ui.screens
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -12,7 +10,7 @@ import kotlinx.coroutines.launch
import net.codinux.banking.client.model.isNegative import net.codinux.banking.client.model.isNegative
import net.codinux.banking.persistence.entities.AccountTransactionEntity import net.codinux.banking.persistence.entities.AccountTransactionEntity
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.extensions.verticalScroll import net.codinux.banking.ui.extensions.rememberVerticalScroll
import net.codinux.banking.ui.forms.LabelledValue import net.codinux.banking.ui.forms.LabelledValue
import net.codinux.banking.ui.forms.OutlinedTextField import net.codinux.banking.ui.forms.OutlinedTextField
import net.codinux.banking.ui.forms.SectionHeader import net.codinux.banking.ui.forms.SectionHeader
@ -72,7 +70,7 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
onClosed = onClosed onClosed = onClosed
) { ) {
SelectionContainer { SelectionContainer {
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { Column(Modifier.fillMaxSize().rememberVerticalScroll()) {
Column(Modifier.fillMaxWidth()) { Column(Modifier.fillMaxWidth()) {
SectionHeader(if (isExpense) "Empfänger*in" else "Zahlende*r", false) SectionHeader(if (isExpense) "Empfänger*in" else "Zahlende*r", false)

View File

@ -2,9 +2,7 @@ package net.codinux.banking.ui.screens
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -21,6 +19,7 @@ import net.codinux.banking.ui.composables.tan.ImageSizeControls
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.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.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
@ -88,7 +87,7 @@ fun CreateEpcQrCodeScreen(onClosed: () -> Unit) {
FullscreenViewBase("EPC QR Code erstellen", "Schließen", onClosed = onClosed) { FullscreenViewBase("EPC QR Code erstellen", "Schließen", onClosed = onClosed) {
Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { Column(Modifier.fillMaxWidth().rememberVerticalScroll()) {
if (epcQrCodeGeneratingError != null) { if (epcQrCodeGeneratingError != null) {
Text("QR Code konnte nicht erstellt werden:${NewLine}$epcQrCodeGeneratingError", color = MaterialTheme.colors.error, modifier = Modifier.padding(vertical = 8.dp)) Text("QR Code konnte nicht erstellt werden:${NewLine}$epcQrCodeGeneratingError", color = MaterialTheme.colors.error, modifier = Modifier.padding(vertical = 8.dp))
} else if (epcQrCodeBytes == null) { } else if (epcQrCodeBytes == null) {

View File

@ -5,26 +5,46 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextButton import androidx.compose.material.TextButton
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import kotlinx.coroutines.Dispatchers import androidx.compose.ui.text.style.TextAlign
import kotlinx.coroutines.launch import androidx.compose.ui.unit.dp
import net.codinux.banking.ui.IOorDefault import net.codinux.banking.client.model.BankAccount
import net.codinux.banking.persistence.entities.BankAccountEntity
import net.codinux.banking.ui.composables.BankIcon
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.DI
import net.codinux.banking.ui.extensions.horizontalScroll import net.codinux.banking.ui.extensions.horizontalScroll
import net.codinux.banking.ui.extensions.verticalScroll import net.codinux.banking.ui.extensions.verticalScroll
import net.codinux.banking.ui.forms.SectionHeader
import net.codinux.banking.ui.forms.Select
import net.codinux.banking.ui.model.Config.NewLine import net.codinux.banking.ui.model.Config.NewLine
@Composable @Composable
fun FeedbackScreen(onClosed: () -> Unit) { fun FeedbackScreen(onClosed: () -> Unit) {
val banks = DI.uiState.banks.collectAsState().value
val messageLog by remember { mutableStateOf(DI.bankingService.getMessageLog()) } val messageLog by remember { mutableStateOf(DI.bankingService.getMessageLog()) }
val displayedLogEntries by remember { derivedStateOf { messageLog.sortedBy { it.messageNumber } } } val accountsWithMessageLog: List<BankAccount?> by remember(messageLog) { derivedStateOf {
listOf<BankAccount?>(null) + messageLog.mapNotNull { it.account }.toSet().toList()
} }
var selectedAccount by remember { mutableStateOf<BankAccount?>(null) }
val bankOfSelectedAccount by remember(selectedAccount) {
// TODO: MessageLogEntries of added accounts contain a BankAccount instead of a BankAccountEntity object
derivedStateOf { banks.firstOrNull { it.id == (selectedAccount as? BankAccountEntity)?.bankId } }
}
val displayedLogEntries by remember(messageLog, selectedAccount) { derivedStateOf {
messageLog.filter { selectedAccount == null || it.account == selectedAccount }.sortedBy { it.messageNumber }
} }
val displayedLogEntriesText by remember(displayedLogEntries) { val displayedLogEntriesText by remember(displayedLogEntries) {
derivedStateOf { displayedLogEntries.map { derivedStateOf { displayedLogEntries.map {
@ -34,24 +54,47 @@ fun FeedbackScreen(onClosed: () -> Unit) {
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
val coroutineScope = rememberCoroutineScope()
coroutineScope.launch(Dispatchers.IOorDefault) {
}
FullscreenViewBase("Feedback", onClosed = onClosed) { FullscreenViewBase("Feedback", onClosed = onClosed) {
Column(Modifier.fillMaxWidth()) { Column(Modifier.fillMaxWidth()) {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { Text("Man kann uns direkt aus der App heraus noch kein Feedback schicken (kommt noch), aber schon mal das Nachrichten-Protokoll, das ihr bei Fehlern an die (unfähgigen) Entwickler schicken könnt:", modifier = Modifier.padding(horizontal = 24.dp).padding(top = 8.dp), textAlign = TextAlign.Center)
TextButton({ clipboardManager.setText(AnnotatedString(displayedLogEntriesText))}) {
Text("Kopieren", color = Colors.CodinuxSecondaryColor)
}
}
Column(Modifier.verticalScroll().horizontalScroll()) { SectionHeader("Nachrichten-Protokoll:") // TODO: add a Switch "Add message protocol"
SelectionContainer(modifier = Modifier.fillMaxSize()) {
Text(displayedLogEntriesText, fontFamily = FontFamily.Monospace) if (messageLog.isEmpty()) {
Row(Modifier.fillMaxSize().padding(24.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
Text("Sie haben noch keine Aktion wie das Abrufen der Kontoumsätze ausgeführt, deshalb gibt es noch kein Nachrichtenprotokoll zu diesen Aktionen", textAlign = TextAlign.Center)
}
} else {
Row(Modifier.fillMaxWidth().padding(top = 6.dp), verticalAlignment = Alignment.CenterVertically) {
Select(
"Konto",
accountsWithMessageLog,
selectedAccount,
{ account -> selectedAccount = account },
{ account -> account?.displayName ?: "Alle Konten" },
Modifier.weight(0.9f),
leadingIcon = bankOfSelectedAccount?.let { { BankIcon(bankOfSelectedAccount) } },
dropDownItemContent = { account ->
Row(verticalAlignment = Alignment.CenterVertically) {
BankIcon(banks.firstOrNull { it.id == (account as? BankAccountEntity)?.bankId }, Modifier.padding(end = 6.dp))
Text(account?.displayName ?: "")
}
},
)
Spacer(Modifier.weight(0.1f))
TextButton({ clipboardManager.setText(AnnotatedString(displayedLogEntriesText))}) {
Text("Kopieren", color = Colors.CodinuxSecondaryColor)
}
}
Column(Modifier.verticalScroll().horizontalScroll().padding(top = 8.dp)) {
SelectionContainer(modifier = Modifier.fillMaxSize()) {
Text(displayedLogEntriesText, fontFamily = FontFamily.Monospace)
}
} }
} }
} }

View File

@ -291,7 +291,7 @@ class BankingService(
bankingRepository.updateAccount(account, response.balance ?: account.balance, response.transactionsRetrievalTime, response.retrievedTransactionsFrom ?: account.retrievedTransactionsFrom) bankingRepository.updateAccount(account, response.balance ?: account.balance, response.transactionsRetrievalTime, response.retrievedTransactionsFrom ?: account.retrievedTransactionsFrom)
} }
bankingRepository.updateBank(bank, bank.clientData) bankingRepository.updateBank(bank, bank.serializedClientData)
notifyBanksListUpdated() // notify about updated values like account balance notifyBanksListUpdated() // notify about updated values like account balance
} catch (e: Throwable) { } catch (e: Throwable) {