Compare commits
12 Commits
f365bfd883
...
48bffd0316
Author | SHA1 | Date |
---|---|---|
dankito | 48bffd0316 | |
dankito | bbfc591e5b | |
dankito | 4531380bac | |
dankito | c6f4b6d250 | |
dankito | 5a0ade46b2 | |
dankito | 4d7cca7a7e | |
dankito | 97282adf12 | |
dankito | 3c2eb3d4d7 | |
dankito | a88ddbdd16 | |
dankito | b86c59ef24 | |
dankito | 33b93c170f | |
dankito | 78edbd6d72 |
|
@ -26,6 +26,13 @@ interface BankingRepository {
|
|||
|
||||
suspend fun persistBank(bank: BankAccess): BankAccessEntity
|
||||
|
||||
suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?)
|
||||
|
||||
suspend fun updateAccount(bank: BankAccountEntity, userSetDisplayName: String?, hideAccount: Boolean, includeInAutomaticAccountsUpdate: Boolean)
|
||||
|
||||
suspend fun deleteBank(bank: BankAccessEntity)
|
||||
|
||||
|
||||
suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity>
|
||||
|
||||
|
||||
|
|
|
@ -48,6 +48,19 @@ class InMemoryBankingRepository(
|
|||
return entity
|
||||
}
|
||||
|
||||
override suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override suspend fun updateAccount(bank: BankAccountEntity, userSetDisplayName: String?, hideAccount: Boolean, includeInAutomaticAccountsUpdate: Boolean) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
override suspend fun deleteBank(bank: BankAccessEntity) {
|
||||
this.banks.remove(bank)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity> {
|
||||
throw NotImplementedError("Lazy developer, method is not implemented")
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ open class SqliteBankingRepository : BankingRepository {
|
|||
val tanMedia = getAllTanMedia().groupBy { it.bankId }.mapValues { it.value.toMutableList() }
|
||||
val holdings = getAllHoldings().groupBy { it.accountId }
|
||||
|
||||
return bankQueries.getAllBanks { id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, countryCode, userSetDisplayName, clientData, displayIndex, iconUrl, wrongCredentialsEntered ->
|
||||
return bankQueries.getAllBanks { id, domesticBankCode, loginName, password, bankName, bic, customerName, userId, selectedTanMethodIdentifier, selectedTanMediumIdentifier, bankingGroup, serverAddress, countryCode, clientData, 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(),
|
||||
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, countryCode, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered)
|
||||
}.executeAsList()
|
||||
|
@ -125,6 +125,46 @@ open class SqliteBankingRepository : BankingRepository {
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, userSetDisplayName: String?) {
|
||||
bankQueries.transaction {
|
||||
if (bank.loginName != loginName) {
|
||||
bankQueries.updateBankLoginName(loginName, bank.id)
|
||||
}
|
||||
|
||||
if (bank.password != password) {
|
||||
bankQueries.updateBankPassword(password, bank.id)
|
||||
}
|
||||
|
||||
if (bank.userSetDisplayName != userSetDisplayName) {
|
||||
bankQueries.updateBankUserSetDisplayName(userSetDisplayName, bank.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateAccount(bank: BankAccountEntity, userSetDisplayName: String?, hideAccount: Boolean, includeInAutomaticAccountsUpdate: Boolean) {
|
||||
bankQueries.transaction {
|
||||
if (bank.userSetDisplayName != userSetDisplayName) {
|
||||
bankQueries.updateBankAccountUserSetDisplayName(userSetDisplayName, bank.id)
|
||||
}
|
||||
|
||||
if (bank.hideAccount != hideAccount) {
|
||||
bankQueries.updateBankAccountHideAccount(hideAccount, bank.id)
|
||||
}
|
||||
|
||||
if (bank.includeInAutomaticAccountsUpdate != includeInAutomaticAccountsUpdate) {
|
||||
bankQueries.updateBankAccountIncludeInAutomaticAccountsUpdate(includeInAutomaticAccountsUpdate, bank.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun deleteBank(bank: BankAccessEntity) {
|
||||
bankQueries.transaction {
|
||||
accountTransactionQueries.deleteTransactionsByBankId(bankId = bank.id)
|
||||
|
||||
bankQueries.deleteBank(bank.id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getAllBankAccounts(): List<BankAccountEntity> = bankQueries.getAllBankAccounts { id, bankId, identifier, subAccountNumber, iban, productName, accountHolderName, type, currency, accountLimit, isAccountTypeSupportedByApplication, features, balance, serverTransactionsRetentionDays, lastAccountUpdateTime, retrievedTransactionsFrom, userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate ->
|
||||
BankAccountEntity(
|
||||
|
@ -337,8 +377,8 @@ open class SqliteBankingRepository : BankingRepository {
|
|||
|
||||
|
||||
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
|
||||
accountTransactionQueries.getAllTransactionsAsViewModel { id, bankId, accountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetDisplayName, userSetOtherPartyName ->
|
||||
AccountTransactionViewModel(id, bankId, accountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetDisplayName, userSetOtherPartyName)
|
||||
accountTransactionQueries.getAllTransactionsAsViewModel { id, bankId, accountId, amount, currency, reference, valueDate, otherPartyName, postingText, userSetReference, userSetOtherPartyName ->
|
||||
AccountTransactionViewModel(id, bankId, accountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetReference, userSetOtherPartyName)
|
||||
}.executeAsList()
|
||||
|
||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
||||
|
|
|
@ -145,6 +145,15 @@ SELECT AccountTransaction.*
|
|||
FROM AccountTransaction WHERE id = ?;
|
||||
|
||||
|
||||
deleteTransactionsByBankId {
|
||||
DELETE FROM BankAccount
|
||||
WHERE bankId = :bankId;
|
||||
|
||||
DELETE FROM Holding
|
||||
WHERE bankId = :bankId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS Holding (
|
||||
|
|
|
@ -79,6 +79,38 @@ SELECT BankAccess.*
|
|||
FROM BankAccess;
|
||||
|
||||
|
||||
updateBankLoginName:
|
||||
UPDATE BankAccess
|
||||
SET loginName = ?
|
||||
WHERE id = ?;
|
||||
|
||||
updateBankPassword:
|
||||
UPDATE BankAccess
|
||||
SET password = ?
|
||||
WHERE id = ?;
|
||||
|
||||
updateBankUserSetDisplayName:
|
||||
UPDATE BankAccess
|
||||
SET userSetDisplayName = ?
|
||||
WHERE id = ?;
|
||||
|
||||
|
||||
deleteBank {
|
||||
DELETE FROM TanMethod
|
||||
WHERE bankId = :bankId;
|
||||
|
||||
DELETE FROM TanMedium
|
||||
WHERE bankId = :bankId;
|
||||
|
||||
DELETE FROM BankAccount
|
||||
WHERE bankId = :bankId;
|
||||
|
||||
DELETE FROM BankAccess
|
||||
WHERE id = :bankId;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS BankAccount (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
@ -155,6 +187,22 @@ SELECT BankAccount.*
|
|||
FROM BankAccount;
|
||||
|
||||
|
||||
updateBankAccountUserSetDisplayName:
|
||||
UPDATE BankAccount
|
||||
SET userSetDisplayName = ?
|
||||
WHERE id = ?;
|
||||
|
||||
updateBankAccountHideAccount:
|
||||
UPDATE BankAccount
|
||||
SET hideAccount = ?
|
||||
WHERE id = ?;
|
||||
|
||||
updateBankAccountIncludeInAutomaticAccountsUpdate:
|
||||
UPDATE BankAccount
|
||||
SET includeInAutomaticAccountsUpdate = ?
|
||||
WHERE id = ?;
|
||||
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS TanMethod (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
|
|
@ -6,8 +6,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import net.codinux.banking.persistence.AndroidContext
|
||||
import net.codinux.banking.persistence.SqliteBankingRepository
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.service.AuthenticationService
|
||||
import net.codinux.banking.ui.service.BiometricAuthenticationService
|
||||
import net.codinux.banking.ui.service.ImageService
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui
|
|||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
@ -11,6 +12,10 @@ import kotlinx.coroutines.Dispatchers
|
|||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||
get() = Dispatchers.IO
|
||||
|
||||
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean =
|
||||
this.nativeKeyEvent.keyCode == android.view.KeyEvent.KEYCODE_BACK
|
||||
|
||||
|
||||
@Composable
|
||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||
val config = LocalConfiguration.current
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package net.codinux.banking.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
@ -9,6 +10,9 @@ import kotlinx.coroutines.Dispatchers
|
|||
|
||||
expect val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||
|
||||
expect fun KeyEvent.isBackButtonPressedEvent(): Boolean
|
||||
|
||||
|
||||
@Composable
|
||||
expect fun rememberScreenSizeInfo(): ScreenSizeInfo
|
||||
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
package net.codinux.banking.ui.appskeleton
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.focusTarget
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.config.Internationalization
|
||||
import net.codinux.banking.ui.forms.RoundedCornersCard
|
||||
import net.codinux.banking.ui.forms.Select
|
||||
import net.codinux.banking.ui.isBackButtonPressedEvent
|
||||
import net.codinux.banking.ui.model.settings.TransactionsGrouping
|
||||
|
||||
private val uiState = DI.uiState
|
||||
|
@ -37,10 +43,20 @@ fun FilterBar() {
|
|||
|
||||
val months = listOf("Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" /*, "1. Quartal", "2. Quartal", "3. Quartal", "4. Quartal" */, null)
|
||||
|
||||
val filterBarFocus = remember { FocusRequester() }
|
||||
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.BottomEnd,
|
||||
modifier = Modifier.fillMaxSize().zIndex(100f)
|
||||
.padding(bottom = 64.dp, end = 74.dp)
|
||||
.padding(bottom = 64.dp, end = 74.dp).focusable(true).focusRequester(filterBarFocus).focusTarget().onKeyEvent { event ->
|
||||
if (event.isBackButtonPressedEvent() || event.key == Key.Escape) {
|
||||
DI.uiState.showFilterBar.value = false
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column(Modifier.height(230.dp).width(390.dp)) {
|
||||
RoundedCornersCard(cornerSize = 4.dp, shadowElevation = 24.dp) {
|
||||
|
@ -90,4 +106,9 @@ fun FilterBar() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
LaunchedEffect(filterBarFocus) {
|
||||
filterBarFocus.requestFocus() // focus filter bar so that it receives key events to handle e.g. Escape button press
|
||||
}
|
||||
|
||||
}
|
|
@ -44,7 +44,7 @@ fun BanksList(
|
|||
accountSelected?.invoke(bank, null)
|
||||
}
|
||||
|
||||
bank.accountsSorted.forEach { account ->
|
||||
bank.accountsSorted.filterNot { it.hideAccount }.forEach { account ->
|
||||
NavigationMenuItem(itemModifier, account.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, bankAccount = account) {
|
||||
accountSelected?.invoke(bank, account)
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import net.codinux.banking.client.model.Amount
|
||||
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||
import net.codinux.banking.persistence.entities.BankAccessEntity
|
||||
import net.codinux.banking.persistence.entities.HoldingEntity
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.config.Style
|
||||
|
@ -31,7 +31,7 @@ private val formatUtil = DI.formatUtil
|
|||
fun GroupedTransactionsListItems(
|
||||
modifier: Modifier,
|
||||
transactionsToDisplay: List<AccountTransactionViewModel>,
|
||||
holdingsToDisplay: List<Holding>,
|
||||
holdingsToDisplay: List<HoldingEntity>,
|
||||
banksById: Map<Long, BankAccessEntity>,
|
||||
transactionsGrouping: TransactionsGrouping
|
||||
) {
|
||||
|
@ -65,9 +65,9 @@ fun GroupedTransactionsListItems(
|
|||
RoundedCornersCard {
|
||||
Column(Modifier.background(Color.White)) {
|
||||
holdingsToDisplay.forEachIndexed { index, holding ->
|
||||
// key(statementOfHoldings.id) {
|
||||
key(holding.id) {
|
||||
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ fun GroupedTransactionsListItems(
|
|||
}
|
||||
|
||||
items(groupedByDate.keys.sortedDescending()) { groupingDate ->
|
||||
key(groupingDate.toEpochDays()) {
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = DI.formatUtil.formatGroupingDate(groupingDate, transactionsGrouping),
|
||||
|
@ -124,3 +125,4 @@ fun GroupedTransactionsListItems(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,11 +51,11 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
|||
|
||||
DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData(
|
||||
DI.uiState.banks.value.firstNotNullOf { it.accounts.firstOrNull { it.id == transaction.accountId } },
|
||||
transaction.otherPartyName,
|
||||
transaction.otherPartyName, // we don't use userSetOtherPartyName here on purpose
|
||||
transactionEntity?.otherPartyBankId,
|
||||
transactionEntity?.otherPartyAccountId,
|
||||
if (withSameData) transaction.amount else null,
|
||||
if (withSameData) transaction.reference else null
|
||||
if (withSameData) transaction.reference else null // we don't use userSetReference here on purpose
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
|||
}
|
||||
|
||||
Text(
|
||||
text = transaction.otherPartyName ?: transaction.postingText ?: "",
|
||||
text = transaction.userSetOtherPartyName ?: transaction.otherPartyName ?: transaction.postingText ?: "",
|
||||
Modifier.fillMaxWidth(),
|
||||
color = Style.ListItemHeaderTextColor,
|
||||
fontWeight = Style.ListItemHeaderWeight,
|
||||
|
@ -95,7 +95,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
|||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
Text(
|
||||
text = transaction.reference ?: "",
|
||||
text = transaction.userSetReference ?: transaction.reference ?: "",
|
||||
Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
|
@ -121,7 +121,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
|||
offset = showMenuAt ?: DpOffset.Zero,
|
||||
) {
|
||||
DropdownMenuItem({ newMoneyTransferToOtherParty(false) }) {
|
||||
Text("Neue Überweisung an ${transaction.otherPartyName} ...")
|
||||
Text("Neue Überweisung an ${transaction.userSetOtherPartyName ?: transaction.otherPartyName} ...") // really use userSetOtherPartyName here as we don't use it in ShowTransferMoneyDialogData
|
||||
}
|
||||
|
||||
DropdownMenuItem({ newMoneyTransferToOtherParty(true) }) {
|
||||
|
|
|
@ -69,9 +69,9 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
|
|||
} else {
|
||||
LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) {
|
||||
itemsIndexed(holdingsToDisplay) { index, holding ->
|
||||
// key(holding.isin) {
|
||||
key(holding.id) {
|
||||
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(transactionsToDisplay) { index, transaction ->
|
||||
|
|
|
@ -13,6 +13,8 @@ object Internationalization {
|
|||
|
||||
const val ErrorTransferMoney = "Überweisung konnte nicht ausgeführt werden"
|
||||
|
||||
const val SaveToDatabase = "Daten konnten nicht in der Datenbank gespeichert werden"
|
||||
|
||||
const val ErrorBiometricAuthentication = "Biometrische Authentifizierung fehlgeschlagen"
|
||||
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ fun ApplicationErrorDialog(error: ApplicationError, onDismiss: (() -> Unit)? = n
|
|||
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
||||
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
||||
ErroneousAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
||||
ErroneousAction.SaveToDatabase -> Internationalization.SaveToDatabase
|
||||
ErroneousAction.BiometricAuthentication -> Internationalization.ErrorBiometricAuthentication
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,10 @@ import net.codinux.banking.ui.forms.*
|
|||
@Composable
|
||||
fun BaseDialog(
|
||||
title: String,
|
||||
centerTitle: Boolean = false,
|
||||
confirmButtonTitle: String = "OK",
|
||||
confirmButtonEnabled: Boolean = true,
|
||||
dismissButtonTitle: String = "Abbrechen",
|
||||
showProgressIndicatorOnConfirmButton: Boolean = false,
|
||||
useMoreThanPlatformDefaultWidthOnMobile: Boolean = false,
|
||||
onDismiss: () -> Unit,
|
||||
|
@ -38,7 +40,7 @@ fun BaseDialog(
|
|||
Column(Modifier.background(Color.White).padding(8.dp)) {
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
HeaderText(title, Modifier.padding(top = 8.dp, bottom = 16.dp).weight(1f))
|
||||
HeaderText(title, Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 16.dp).weight(1f), textAlign = if (centerTitle) TextAlign.Center else TextAlign.Start)
|
||||
|
||||
if (DI.platform.isDesktop) {
|
||||
TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
||||
|
@ -49,9 +51,9 @@ fun BaseDialog(
|
|||
|
||||
content()
|
||||
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Row(Modifier.fillMaxWidth().padding(top = 8.dp)) {
|
||||
TextButton(onClick = onDismiss, Modifier.weight(0.5f)) {
|
||||
Text("Abbrechen", color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
|
||||
Text(dismissButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
|
||||
TextButton(
|
||||
|
@ -61,7 +63,7 @@ fun BaseDialog(
|
|||
) {
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||
if (showProgressIndicatorOnConfirmButton) {
|
||||
CircularProgressIndicator(Modifier.padding(end = 6.dp), color = Colors.CodinuxSecondaryColor)
|
||||
CircularProgressIndicator(Modifier.padding(end = 6.dp).size(36.dp), color = Colors.CodinuxSecondaryColor)
|
||||
}
|
||||
|
||||
Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth())
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package net.codinux.banking.ui.dialogs
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun ConfirmDialog(
|
||||
text: String,
|
||||
title: String? = null,
|
||||
confirmButtonTitle: String = "Ja",
|
||||
dismissButtonTitle: String = "Nein",
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit
|
||||
) {
|
||||
|
||||
BaseDialog(
|
||||
title = title ?: "",
|
||||
centerTitle = true,
|
||||
confirmButtonTitle = confirmButtonTitle,
|
||||
dismissButtonTitle = dismissButtonTitle,
|
||||
onDismiss = { onDismiss() },
|
||||
onConfirm = { onConfirm(); onDismiss() }
|
||||
) {
|
||||
Text(text, textAlign = TextAlign.Center, lineHeight = 22.sp, modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp))
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.window.DialogProperties
|
|||
import net.codinux.banking.ui.composables.text.HeaderText
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.extensions.verticalScroll
|
||||
import net.codinux.banking.ui.model.Config.NewLine
|
||||
|
||||
@Composable
|
||||
fun ErrorDialog(
|
||||
|
@ -22,7 +23,7 @@ fun ErrorDialog(
|
|||
) {
|
||||
|
||||
val effectiveText = if (exception == null) text else {
|
||||
"$text\r\n\r\nFehlermeldung:\r\n${exception.stackTraceToString()}"
|
||||
"$text${NewLine}${NewLine}Fehlermeldung:${NewLine}${exception.stackTraceToString()}"
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ private fun FormListItemImpl(
|
|||
}
|
||||
}
|
||||
|
||||
Text(label, color = Colors.FormListItemTextColor, fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.fillMaxHeight())
|
||||
Text(label, color = Colors.FormListItemTextColor, fontSize = 16.sp, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
|
||||
}
|
|
@ -7,5 +7,7 @@ enum class ErroneousAction {
|
|||
|
||||
TransferMoney,
|
||||
|
||||
SaveToDatabase,
|
||||
|
||||
BiometricAuthentication
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
package net.codinux.banking.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import net.codinux.banking.client.model.isNegative
|
||||
import net.codinux.banking.persistence.entities.AccountTransactionEntity
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.extensions.verticalScroll
|
||||
import net.codinux.banking.ui.forms.LabelledValue
|
||||
import net.codinux.banking.ui.forms.OutlinedTextField
|
||||
import net.codinux.banking.ui.forms.SectionHeader
|
||||
|
||||
private val formatUtil = DI.formatUtil
|
||||
|
@ -35,14 +39,50 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|
|||
|| transaction.journalNumber != null || transaction.textKeyAddition != null
|
||||
|
||||
|
||||
FullscreenViewBase("Umsatzdetails", onClosed = onClosed) {
|
||||
var enteredOtherPartyName by remember { mutableStateOf(transaction.userSetOtherPartyName ?: transaction.otherPartyName ?: "") }
|
||||
|
||||
var enteredReference by remember { mutableStateOf(transaction.userSetReference ?: transaction.reference ?: "") }
|
||||
|
||||
var enteredNotes by remember { mutableStateOf(transaction.notes ?: "") }
|
||||
|
||||
val hasDataChanged by remember(enteredOtherPartyName, enteredReference, enteredNotes) {
|
||||
mutableStateOf(
|
||||
(enteredOtherPartyName != transaction.userSetOtherPartyName && (transaction.userSetOtherPartyName?.isNotBlank() == true || enteredOtherPartyName.isNotBlank()))
|
||||
|| (enteredReference != transaction.userSetReference && (transaction.userSetReference?.isNotBlank() == true || enteredReference.isNotBlank()))
|
||||
|| (enteredNotes != transaction.notes && enteredNotes.isNotBlank())
|
||||
)
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
|
||||
fun saveChanges() {
|
||||
coroutineScope.launch {
|
||||
DI.bankingService.updateAccountTransactionEntity(transaction, enteredOtherPartyName.takeUnless { it.isBlank() }, enteredReference.takeUnless { it.isBlank() }, enteredNotes.takeUnless { it.isBlank() })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FullscreenViewBase(
|
||||
"Umsatzdetails",
|
||||
confirmButtonTitle = "Speichern",
|
||||
confirmButtonEnabled = hasDataChanged,
|
||||
showDismissButton = true,
|
||||
onConfirm = { saveChanges() },
|
||||
onClosed = onClosed
|
||||
) {
|
||||
SelectionContainer {
|
||||
Column(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
|
||||
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(8.dp)) {
|
||||
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
SectionHeader(if (isExpense) "Empfänger*in" else "Zahlende*r", false)
|
||||
|
||||
LabelledValue("Name", transaction.otherPartyName ?: "")
|
||||
OutlinedTextField(
|
||||
label = { Text("Name") },
|
||||
value = enteredOtherPartyName,
|
||||
onValueChange = { enteredOtherPartyName = it },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
|
||||
LabelledValue("BIC", transaction.otherPartyBankId ?: "")
|
||||
|
||||
|
@ -57,7 +97,12 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|
|||
|
||||
LabelledValue("Buchungstext", transaction.postingText ?: "")
|
||||
|
||||
LabelledValue("Verwendungszweck", transaction.reference ?: "")
|
||||
OutlinedTextField(
|
||||
label = { Text("Verwendungszweck") },
|
||||
value = enteredReference,
|
||||
onValueChange = { enteredReference = it },
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
|
||||
)
|
||||
|
||||
LabelledValue("Buchungsdatum", formatUtil.formatDate(transaction.bookingDate))
|
||||
|
||||
|
@ -70,6 +115,16 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|
|||
transaction.closingBalance?.let {
|
||||
LabelledValue("Tagesendsaldo", formatUtil.formatAmount(it, accountCurrency))
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
label = { Text("Notizen") },
|
||||
value = enteredNotes,
|
||||
onValueChange = { enteredNotes = it },
|
||||
singleLine = false,
|
||||
minLines = 2,
|
||||
maxLines = 3,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (hasDetailedValues) {
|
||||
|
|
|
@ -6,7 +6,9 @@ import androidx.compose.material.Text
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import net.codinux.banking.persistence.entities.BankAccountEntity
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.config.Internationalization
|
||||
import net.codinux.banking.ui.extensions.verticalScroll
|
||||
import net.codinux.banking.ui.forms.*
|
||||
|
@ -16,20 +18,36 @@ fun BankAccountSettingsScreen(account: BankAccountEntity, onClosed: () -> Unit)
|
|||
|
||||
var enteredAccountName by remember { mutableStateOf(account.displayName) }
|
||||
|
||||
var selectedIncludeInAutomaticAccountsUpdate by remember { mutableStateOf(account.includeInAutomaticAccountsUpdate) }
|
||||
|
||||
var selectedHideAccount by remember { mutableStateOf(account.hideAccount) }
|
||||
|
||||
val hasDataChanged by remember(enteredAccountName) {
|
||||
var selectedIncludeInAutomaticAccountsUpdate by remember { mutableStateOf(account.includeInAutomaticAccountsUpdate) }
|
||||
|
||||
val hasDataChanged by remember(enteredAccountName, selectedHideAccount, selectedIncludeInAutomaticAccountsUpdate) {
|
||||
mutableStateOf(
|
||||
enteredAccountName != account.displayName
|
||||
|| selectedIncludeInAutomaticAccountsUpdate != account.includeInAutomaticAccountsUpdate
|
||||
|| selectedHideAccount != account.hideAccount
|
||||
|| selectedIncludeInAutomaticAccountsUpdate != account.includeInAutomaticAccountsUpdate
|
||||
)
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
FullscreenViewBase(account.displayName, onClosed = onClosed) {
|
||||
|
||||
fun saveChanges() {
|
||||
coroutineScope.launch {
|
||||
DI.bankingService.updateAccount(account, enteredAccountName, selectedHideAccount, selectedIncludeInAutomaticAccountsUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FullscreenViewBase(
|
||||
account.displayName,
|
||||
confirmButtonTitle = "Speichern",
|
||||
confirmButtonEnabled = hasDataChanged,
|
||||
showDismissButton = true,
|
||||
onConfirm = { saveChanges() },
|
||||
onClosed = onClosed
|
||||
) {
|
||||
Column(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
|
||||
Column {
|
||||
SectionHeader("Einstellungen", false)
|
||||
|
@ -41,9 +59,9 @@ fun BankAccountSettingsScreen(account: BankAccountEntity, onClosed: () -> Unit)
|
|||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp)
|
||||
)
|
||||
|
||||
// BooleanOption("Bei Kontoaktualisierung einbeziehen", selectedIncludeInAutomaticAccountsUpdate) { selectedIncludeInAutomaticAccountsUpdate = it }
|
||||
//
|
||||
// BooleanOption("Konto ausblenden", selectedHideAccount) { selectedHideAccount = it }
|
||||
BooleanOption("Bei Kontoaktualisierung einbeziehen (autom. Kontoaktualisierung noch nicht umgesetzt)", selectedIncludeInAutomaticAccountsUpdate) { selectedIncludeInAutomaticAccountsUpdate = it }
|
||||
|
||||
BooleanOption("Konto ausblenden", selectedHideAccount) { selectedHideAccount = it }
|
||||
}
|
||||
|
||||
SelectionContainer {
|
||||
|
|
|
@ -2,13 +2,20 @@ package net.codinux.banking.ui.screens
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.launch
|
||||
import net.codinux.banking.persistence.entities.BankAccessEntity
|
||||
import net.codinux.banking.ui.config.Colors
|
||||
import net.codinux.banking.ui.config.DI
|
||||
import net.codinux.banking.ui.dialogs.ConfirmDialog
|
||||
import net.codinux.banking.ui.extensions.verticalScroll
|
||||
import net.codinux.banking.ui.forms.*
|
||||
import net.codinux.banking.ui.model.Config.NewLine
|
||||
|
||||
@Composable
|
||||
fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
||||
|
@ -19,15 +26,48 @@ fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
|||
|
||||
var enteredPassword by remember { mutableStateOf(bank.password ?: "") }
|
||||
|
||||
val hasDataChanged by remember(enteredBankName) {
|
||||
var showDeleteBankAccessConfirmationDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val hasDataChanged by remember(enteredBankName, enteredLoginName, enteredPassword) {
|
||||
mutableStateOf(
|
||||
(enteredBankName != bank.bankName && (bank.userSetDisplayName == null || enteredBankName != bank.userSetDisplayName))
|
||||
|| enteredLoginName != bank.loginName || enteredPassword != bank.password
|
||||
|| (enteredLoginName != bank.loginName && enteredLoginName.isNotBlank())
|
||||
|| (enteredPassword != bank.password && enteredPassword.isNotBlank())
|
||||
)
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
|
||||
fun saveChanges() {
|
||||
coroutineScope.launch {
|
||||
DI.bankingService.updateBank(bank, enteredLoginName, enteredPassword, enteredBankName.takeUnless { it.isBlank() })
|
||||
}
|
||||
}
|
||||
|
||||
if (showDeleteBankAccessConfirmationDialog) {
|
||||
ConfirmDialog(
|
||||
title = "${bank.displayName} wirklich löschen?",
|
||||
text = "Dadurch werden auch alle zum Konto gehörenden Daten wie seine Kontoumsätze unwiderruflich gelöscht.${NewLine}Die Daten können nicht widerhergestellt werden.",
|
||||
onDismiss = { showDeleteBankAccessConfirmationDialog = false },
|
||||
onConfirm = {
|
||||
coroutineScope.launch {
|
||||
DI.bankingService.deleteBank(bank)
|
||||
}
|
||||
onClosed()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
FullscreenViewBase(bank.displayName, onClosed = onClosed) {
|
||||
FullscreenViewBase(
|
||||
bank.displayName,
|
||||
confirmButtonTitle = "Speichern",
|
||||
confirmButtonEnabled = hasDataChanged,
|
||||
showDismissButton = true,
|
||||
onConfirm = { saveChanges() },
|
||||
onClosed = onClosed
|
||||
) {
|
||||
Column(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
|
@ -98,13 +138,13 @@ fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
|||
}
|
||||
}
|
||||
|
||||
// Spacer(Modifier.weight(1f))
|
||||
//
|
||||
// Column(Modifier.padding(top = 18.dp, bottom = 18.dp)) {
|
||||
// TextButton(modifier = Modifier.fillMaxWidth().height(50.dp), onClick = { }, enabled = false) {
|
||||
// Text("Konto löschen", color = Colors.DestructiveColor, textAlign = TextAlign.Center)
|
||||
// }
|
||||
// }
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Column(Modifier.padding(top = 24.dp, bottom = 18.dp)) {
|
||||
TextButton(modifier = Modifier.fillMaxWidth().height(50.dp), onClick = { showDeleteBankAccessConfirmationDialog = true }) {
|
||||
Text("Konto löschen", fontSize = 15.sp, color = Colors.DestructiveColor, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,7 +23,10 @@ fun FullscreenViewBase(
|
|||
title: String,
|
||||
confirmButtonTitle: String = "OK",
|
||||
confirmButtonEnabled: Boolean = true,
|
||||
dismissButtonTitle: String = "Abbrechen",
|
||||
showDismissButton: Boolean = false,
|
||||
showButtonBar: Boolean = true,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
onClosed: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
@ -49,16 +52,18 @@ fun FullscreenViewBase(
|
|||
|
||||
if (showButtonBar) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||
// TextButton(onClick = onClosed, Modifier.width(Style.DialogButtonWidth)) {
|
||||
// Text("Abbrechen", color = Colors.CodinuxSecondaryColor)
|
||||
// }
|
||||
//
|
||||
// Spacer(Modifier.width(8.dp))
|
||||
if (showDismissButton) {
|
||||
TextButton(onClick = onClosed, Modifier.weight(1f)) {
|
||||
Text(dismissButtonTitle, color = Colors.CodinuxSecondaryColor)
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
|
||||
TextButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
modifier = Modifier.weight(1f),
|
||||
enabled = confirmButtonEnabled,
|
||||
onClick = { /* onConfirm?.invoke() ?: */ onClosed() }
|
||||
onClick = { onConfirm?.invoke(); onClosed() }
|
||||
) {
|
||||
Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center)
|
||||
}
|
||||
|
|
|
@ -82,9 +82,9 @@ fun ProtectAppSettingsDialog(appSettings: AppSettings, onClosed: () -> Unit) {
|
|||
Spacer(Modifier.weight(1f))
|
||||
|
||||
if (selectedAuthenticationMethod == AppAuthenticationMethod.None) {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||
if (currentAuthenticationMethod == AppAuthenticationMethod.None) {
|
||||
Text("Appzugangsschutz ist bereits ungeschützt", fontSize = 18.sp, textAlign = TextAlign.Center)
|
||||
Text("Appzugang ist bereits ungeschützt", fontSize = 18.sp, textAlign = TextAlign.Center)
|
||||
} else {
|
||||
Text("Möchten Sie den Appzugangsschutz wirklich entfernen?", fontSize = 18.sp, textAlign = TextAlign.Center)
|
||||
}
|
||||
|
|
|
@ -46,7 +46,10 @@ class AccountTransactionsFilterService {
|
|||
|
||||
private fun matchesSearchTerm(transaction: AccountTransactionViewModel, searchTerm: String): Boolean =
|
||||
transaction.reference?.contains(searchTerm, true) == true
|
||||
|| transaction.userSetReference?.contains(searchTerm, true) == true
|
||||
|| transaction.otherPartyName?.contains(searchTerm, true) == true
|
||||
|| transaction.userSetOtherPartyName?.contains(searchTerm, true) == true
|
||||
|| transaction.postingText?.contains(searchTerm, true) == true
|
||||
|
||||
|
||||
fun filterHoldings(holdings: List<HoldingEntity>, filter: AccountTransactionsFilter): List<HoldingEntity> {
|
||||
|
|
|
@ -19,6 +19,7 @@ class BankDataImporterAndExporter {
|
|||
transactions.forEach { transaction ->
|
||||
writer.writeRow(
|
||||
// TODO: add bank and bank account
|
||||
// TODO: also regard userSetOtherPartyName and userSetReference?
|
||||
formatAmount(transaction.amount, decimalSeparator), transaction.currency,
|
||||
transaction.valueDate.toString(), transaction.bookingDate.toString(),
|
||||
transaction.reference,
|
||||
|
|
|
@ -72,10 +72,10 @@ class BankingService(
|
|||
updateOnChanges(uiSettings)
|
||||
|
||||
|
||||
uiState.banks.value = getAllBanks()
|
||||
updateBanksInUi(getAllBanks())
|
||||
|
||||
uiState.transactions.value = getAllAccountTransactionsAsViewModel()
|
||||
uiState.holdings.value = uiState.banks.value.flatMap { it.accounts }.flatMap { it.holdings }
|
||||
uiState.holdings.value = getCurrentUiBanksList().flatMap { it.accounts }.flatMap { it.holdings }
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not read all banks and account transactions from repository" }
|
||||
}
|
||||
|
@ -93,6 +93,60 @@ class BankingService(
|
|||
|
||||
fun getAllBanks() = bankingRepository.getAllBanks()
|
||||
|
||||
suspend fun updateBank(bank: BankAccessEntity, loginName: String, password: String, bankName: String?) {
|
||||
try {
|
||||
bankingRepository.updateBank(bank, loginName, password, bankName)
|
||||
|
||||
if (bank.loginName != loginName) {
|
||||
bank.loginName = loginName
|
||||
}
|
||||
if (bank.password != password) {
|
||||
bank.password = password
|
||||
}
|
||||
if (bank.userSetDisplayName != bankName) {
|
||||
bank.userSetDisplayName = bankName
|
||||
}
|
||||
|
||||
notifyBanksListUpdated()
|
||||
} catch (e: Throwable) {
|
||||
showAndLogError(ErroneousAction.SaveToDatabase, "Could not update bank $bank", "Bankzugangsdaten konnten nicht aktualisisert werden", e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateAccount(account: BankAccountEntity, userSetDisplayName: String?, hideAccount: Boolean, includeInAutomaticAccountsUpdate: Boolean) {
|
||||
try {
|
||||
bankingRepository.updateAccount(account, userSetDisplayName, hideAccount, includeInAutomaticAccountsUpdate)
|
||||
|
||||
if (account.userSetDisplayName != userSetDisplayName) {
|
||||
account.userSetDisplayName = userSetDisplayName
|
||||
}
|
||||
if (account.hideAccount != hideAccount) {
|
||||
account.hideAccount = hideAccount
|
||||
}
|
||||
if (account.includeInAutomaticAccountsUpdate != includeInAutomaticAccountsUpdate) {
|
||||
account.includeInAutomaticAccountsUpdate = includeInAutomaticAccountsUpdate
|
||||
}
|
||||
|
||||
notifyBanksListUpdated()
|
||||
} catch (e: Throwable) {
|
||||
showAndLogError(ErroneousAction.SaveToDatabase, "Could not update bank $account", "Bankzugangsdaten konnten nicht aktualisisert werden", e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteBank(bank: BankAccessEntity) {
|
||||
try {
|
||||
bankingRepository.deleteBank(bank)
|
||||
|
||||
uiState.transactions.value = uiState.transactions.value.filterNot { it.bankId == bank.id }
|
||||
uiState.holdings.value = uiState.holdings.value.filterNot { it.bankId == bank.id }
|
||||
updateBanksInUi(uiState.banks.value.toMutableList().also { it.remove(bank) })
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not delete bank ${bank.displayName}" }
|
||||
showAndLogError(ErroneousAction.SaveToDatabase, "Could not delete bank ${bank.displayName}", "Fehler beim Löschen der Bankzugangsdaten für ${bank.displayName}", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getAllAccountTransactions() = bankingRepository.getAllAccountTransactions()
|
||||
|
||||
fun getAllTransactionsForBank(bank: BankAccessEntity) = bankingRepository.getAllTransactionsForBank(bank)
|
||||
|
@ -113,14 +167,12 @@ class BankingService(
|
|||
if (response.type == ResponseType.Success && response.data != null) {
|
||||
handleSuccessfulGetAccountDataResponse(response.data!!)
|
||||
} else {
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.AddAccount, response)
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.AddAccount, bank.name, response)
|
||||
}
|
||||
|
||||
return response.type == ResponseType.Success
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not add account for ${bank.name} $loginName" }
|
||||
|
||||
uiState.applicationErrorOccurred(ErroneousAction.AddAccount, null, e)
|
||||
showAndLogError(ErroneousAction.AddAccount, "Could not add account for ${bank.name} $loginName", "Konto für ${bank.name} konnte nicht hinzugefügt werden", e)
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -144,9 +196,7 @@ class BankingService(
|
|||
|
||||
log.info { "Saved bank $newBankEntity with ${newBankEntity.accounts.flatMap { it.bookedTransactions }.size} transactions" }
|
||||
|
||||
val banks = uiState.banks.value.toMutableList()
|
||||
banks.add(newBankEntity)
|
||||
uiState.banks.value = banks
|
||||
updateBanksInUi(uiState.banks.value.toMutableList().also { it.add(newBankEntity) })
|
||||
|
||||
updateTransactionsInUi(newBankEntity.accounts.flatMap { it.bookedTransactions })
|
||||
updateHoldingsInUi(newBankEntity.accounts.flatMap { it.holdings }, emptyList())
|
||||
|
@ -161,7 +211,8 @@ class BankingService(
|
|||
if (selectedAccount != null) {
|
||||
updateAccountTransactions(selectedAccount.bank, selectedAccount.bankAccount)
|
||||
} else {
|
||||
uiState.banks.value.forEach { bank ->
|
||||
getCurrentUiBanksList().forEach { bank ->
|
||||
// TODO: when implementing automatic account transactions update, filter out accounts with includeInAutomaticAccountsUpdate == false
|
||||
updateAccountTransactions(bank)
|
||||
}
|
||||
}
|
||||
|
@ -174,7 +225,7 @@ class BankingService(
|
|||
if (response.type == ResponseType.Success && response.data != null) {
|
||||
handleSuccessfulUpdateAccountTransactionsResponse(bank, response.data!!)
|
||||
} else {
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, response)
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, bankAccount?.displayName ?: bank.displayName, response)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
log.error(e) { "Could not update account transactions for $bank" }
|
||||
|
@ -251,6 +302,62 @@ class BankingService(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
suspend fun updateAccountTransactionEntity(transaction: AccountTransactionEntity, userSetOtherPartyName: String?, userSetReference: String?, notes: String?) {
|
||||
try {
|
||||
bankingRepository.updateTransaction(transaction, userSetOtherPartyName, userSetReference, notes)
|
||||
|
||||
val transactionViewModel = uiState.transactions.value.firstOrNull { it.id == transaction.id } // should actually never be null
|
||||
|
||||
if (transaction.userSetOtherPartyName != userSetOtherPartyName) {
|
||||
transaction.userSetOtherPartyName = userSetOtherPartyName
|
||||
transactionViewModel?.userSetOtherPartyName = userSetOtherPartyName // also update displayed AccountTransactionViewModel
|
||||
}
|
||||
if (transaction.userSetReference != userSetReference) {
|
||||
transaction.userSetReference = userSetReference
|
||||
transactionViewModel?.userSetReference = userSetReference
|
||||
}
|
||||
if (transaction.notes != notes) {
|
||||
transaction.notes = notes
|
||||
}
|
||||
|
||||
notifyAccountTransactionListUpdated()
|
||||
} catch (e: Throwable) {
|
||||
showAndLogError(ErroneousAction.SaveToDatabase, "Could not update account transaction $transaction", "Kontoumsatz konnten nicht aktualisisert werden", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getCurrentUiBanksList() = uiState.banks.value
|
||||
|
||||
private suspend fun notifyBanksListUpdated() {
|
||||
val currentBanksList = getCurrentUiBanksList()
|
||||
|
||||
if (currentBanksList.isNotEmpty()) {
|
||||
// if we only would call uiState.banks.emit(banks) with the same banks list as currently, nothing would change ->
|
||||
// update does not get triggered -> for a short time display a different banks list and then return to actual banks list
|
||||
updateBanksInUi(currentBanksList.toMutableList().also { it.add(it.last()) })
|
||||
try { delay(10) } catch (e: Throwable) { }
|
||||
updateBanksInUi(currentBanksList)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateBanksInUi(banks: List<BankAccessEntity>) {
|
||||
uiState.banks.emit(banks)
|
||||
}
|
||||
|
||||
private suspend fun notifyAccountTransactionListUpdated() {
|
||||
val currentTransactionsList = uiState.transactions.value
|
||||
|
||||
if (currentTransactionsList.isNotEmpty()) {
|
||||
// if we only would call uiState.banks.emit(banks) with the same banks list as currently, nothing would change ->
|
||||
// update does not get triggered -> for a short time display a different banks list and then return to actual banks list
|
||||
uiState.transactions.emit(currentTransactionsList.toMutableList().also { it.add(it.last()) })
|
||||
try { delay(10) } catch (e: Throwable) { }
|
||||
uiState.transactions.emit(currentTransactionsList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTransactionsInUi(addedTransactions: List<AccountTransactionEntity>): List<AccountTransactionViewModel> {
|
||||
val transactionsViewModel = addedTransactions.map { AccountTransactionViewModel(it) }
|
||||
|
||||
|
@ -281,7 +388,7 @@ class BankingService(
|
|||
))
|
||||
|
||||
if (response.error != null) {
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.TransferMoney, response)
|
||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.TransferMoney, account.displayName, response)
|
||||
} else if (response.type == ResponseType.Success) {
|
||||
uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency))
|
||||
|
||||
|
@ -292,8 +399,8 @@ class BankingService(
|
|||
}
|
||||
|
||||
|
||||
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, response: Response<*>) {
|
||||
log.error { "$action was not successful: $response" }
|
||||
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, accountOrBankName: String, response: Response<*>) {
|
||||
log.error { "$action was not successful for $accountOrBankName: $response" }
|
||||
|
||||
response.error?.let { error ->
|
||||
if (error.type != ErrorType.UserCancelledAction) { // the user knows that she cancelled the action
|
||||
|
@ -320,6 +427,12 @@ class BankingService(
|
|||
}
|
||||
}
|
||||
|
||||
private fun showAndLogError(action: ErroneousAction, logMessage: String, messageToShowToUser: String? = null, exception: Throwable? = null) {
|
||||
log.error(exception) { logMessage }
|
||||
|
||||
uiState.applicationErrorOccurred(action, messageToShowToUser, exception)
|
||||
}
|
||||
|
||||
|
||||
private suspend fun readTransactionsFromCsv(): List<AccountTransaction> {
|
||||
val csv = Res.readBytes("files/transactions.csv").decodeToString()
|
||||
|
|
|
@ -32,6 +32,7 @@ class RecipientFinder(private val bankFinder: BankFinder) {
|
|||
|
||||
suspend fun updateData(transactions: List<AccountTransaction>) {
|
||||
availableRecipients = transactions.mapNotNull {
|
||||
// TODO: also regard userSetOtherPartyName?
|
||||
if (it.otherPartyName != null && it.otherPartyAccountId != null) {
|
||||
RecipientSuggestion(it.otherPartyName!!, it.otherPartyBankId, it.otherPartyAccountId!!)
|
||||
} else {
|
||||
|
@ -45,6 +46,7 @@ class RecipientFinder(private val bankFinder: BankFinder) {
|
|||
|
||||
transactionsByIban = transactions.filter { it.otherPartyAccountId != null }.groupBy { it.otherPartyAccountId!! }
|
||||
.mapValues { it.value.map {
|
||||
// TODO: also regard userSetReference?
|
||||
PaymentDataSuggestion(it.reference ?: "", Amount(it.amount.toString().replace("-", "")), it.currency, it.valueDate)
|
||||
}.toSet().sortedByDescending { it.valueDate } }
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalWindowInfo
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
@ -11,6 +12,9 @@ import kotlinx.coroutines.Dispatchers
|
|||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||
get() = Dispatchers.IO
|
||||
|
||||
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean = false
|
||||
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||
|
|
|
@ -2,6 +2,7 @@ package net.codinux.banking.ui
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.cinterop.ExperimentalForeignApi
|
||||
|
@ -16,6 +17,9 @@ import platform.UIKit.UIScreen
|
|||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||
get() = Dispatchers.IO
|
||||
|
||||
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean = false // TODO
|
||||
|
||||
|
||||
@OptIn(ExperimentalForeignApi::class)
|
||||
@Composable
|
||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.input.key.KeyEvent
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalWindowInfo
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
|
@ -11,6 +12,9 @@ import kotlinx.coroutines.Dispatchers
|
|||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||
get() = Dispatchers.Default
|
||||
|
||||
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean = false
|
||||
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||
|
|
Loading…
Reference in New Issue