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 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>
|
suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,19 @@ class InMemoryBankingRepository(
|
||||||
return entity
|
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> {
|
override suspend fun persistTransactions(bankAccount: BankAccountEntity, transactions: List<AccountTransaction>): List<AccountTransactionEntity> {
|
||||||
throw NotImplementedError("Lazy developer, method is not implemented")
|
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 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, 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(),
|
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)
|
bankingGroup?.let { BankingGroup.valueOf(it) }, serverAddress, countryCode, userSetDisplayName, displayIndex.toInt(), iconUrl, wrongCredentialsEntered)
|
||||||
}.executeAsList()
|
}.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 ->
|
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(
|
BankAccountEntity(
|
||||||
|
@ -337,8 +377,8 @@ open class SqliteBankingRepository : BankingRepository {
|
||||||
|
|
||||||
|
|
||||||
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
|
override fun getAllAccountTransactionsAsViewModel(): List<AccountTransactionViewModel> =
|
||||||
accountTransactionQueries.getAllTransactionsAsViewModel { id, bankId, accountId, amount, currency, reference, 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, userSetDisplayName, userSetOtherPartyName)
|
AccountTransactionViewModel(id, bankId, accountId, mapToAmount(amount), currency, reference, mapToDate(valueDate), otherPartyName, postingText, userSetReference, userSetOtherPartyName)
|
||||||
}.executeAsList()
|
}.executeAsList()
|
||||||
|
|
||||||
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
override fun getAllAccountTransactions(): List<AccountTransactionEntity> {
|
||||||
|
|
|
@ -145,6 +145,15 @@ SELECT AccountTransaction.*
|
||||||
FROM AccountTransaction WHERE id = ?;
|
FROM AccountTransaction WHERE id = ?;
|
||||||
|
|
||||||
|
|
||||||
|
deleteTransactionsByBankId {
|
||||||
|
DELETE FROM BankAccount
|
||||||
|
WHERE bankId = :bankId;
|
||||||
|
|
||||||
|
DELETE FROM Holding
|
||||||
|
WHERE bankId = :bankId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS Holding (
|
CREATE TABLE IF NOT EXISTS Holding (
|
||||||
|
|
|
@ -79,6 +79,38 @@ SELECT BankAccess.*
|
||||||
FROM 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 (
|
CREATE TABLE IF NOT EXISTS BankAccount (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
@ -155,6 +187,22 @@ SELECT BankAccount.*
|
||||||
FROM 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 (
|
CREATE TABLE IF NOT EXISTS TanMethod (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|
|
@ -6,8 +6,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import net.codinux.banking.persistence.AndroidContext
|
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.AuthenticationService
|
||||||
import net.codinux.banking.ui.service.BiometricAuthenticationService
|
import net.codinux.banking.ui.service.BiometricAuthenticationService
|
||||||
import net.codinux.banking.ui.service.ImageService
|
import net.codinux.banking.ui.service.ImageService
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
@ -11,6 +12,10 @@ import kotlinx.coroutines.Dispatchers
|
||||||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||||
get() = Dispatchers.IO
|
get() = Dispatchers.IO
|
||||||
|
|
||||||
|
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean =
|
||||||
|
this.nativeKeyEvent.keyCode == android.view.KeyEvent.KEYCODE_BACK
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||||
val config = LocalConfiguration.current
|
val config = LocalConfiguration.current
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.codinux.banking.ui
|
package net.codinux.banking.ui
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
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 androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
@ -9,6 +10,9 @@ import kotlinx.coroutines.Dispatchers
|
||||||
|
|
||||||
expect val Dispatchers.IOorDefault: CoroutineDispatcher
|
expect val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||||
|
|
||||||
|
expect fun KeyEvent.isBackButtonPressedEvent(): Boolean
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
expect fun rememberScreenSizeInfo(): ScreenSizeInfo
|
expect fun rememberScreenSizeInfo(): ScreenSizeInfo
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
package net.codinux.banking.ui.appskeleton
|
package net.codinux.banking.ui.appskeleton
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.Text
|
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.focusTarget
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.key.*
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
import net.codinux.banking.ui.config.Internationalization
|
import net.codinux.banking.ui.config.Internationalization
|
||||||
import net.codinux.banking.ui.forms.RoundedCornersCard
|
import net.codinux.banking.ui.forms.RoundedCornersCard
|
||||||
import net.codinux.banking.ui.forms.Select
|
import net.codinux.banking.ui.forms.Select
|
||||||
|
import net.codinux.banking.ui.isBackButtonPressedEvent
|
||||||
import net.codinux.banking.ui.model.settings.TransactionsGrouping
|
import net.codinux.banking.ui.model.settings.TransactionsGrouping
|
||||||
|
|
||||||
private val uiState = DI.uiState
|
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 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(
|
Box(
|
||||||
contentAlignment = Alignment.BottomEnd,
|
contentAlignment = Alignment.BottomEnd,
|
||||||
modifier = Modifier.fillMaxSize().zIndex(100f)
|
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)) {
|
Column(Modifier.height(230.dp).width(390.dp)) {
|
||||||
RoundedCornersCard(cornerSize = 4.dp, shadowElevation = 24.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)
|
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) {
|
NavigationMenuItem(itemModifier, account.displayName, textColor, iconSize, IconTextSpacing, itemHorizontalPadding, bankAccount = account) {
|
||||||
accountSelected?.invoke(bank, 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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import net.codinux.banking.client.model.Amount
|
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.BankAccessEntity
|
||||||
|
import net.codinux.banking.persistence.entities.HoldingEntity
|
||||||
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.config.Style
|
import net.codinux.banking.ui.config.Style
|
||||||
|
@ -31,7 +31,7 @@ private val formatUtil = DI.formatUtil
|
||||||
fun GroupedTransactionsListItems(
|
fun GroupedTransactionsListItems(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
transactionsToDisplay: List<AccountTransactionViewModel>,
|
transactionsToDisplay: List<AccountTransactionViewModel>,
|
||||||
holdingsToDisplay: List<Holding>,
|
holdingsToDisplay: List<HoldingEntity>,
|
||||||
banksById: Map<Long, BankAccessEntity>,
|
banksById: Map<Long, BankAccessEntity>,
|
||||||
transactionsGrouping: TransactionsGrouping
|
transactionsGrouping: TransactionsGrouping
|
||||||
) {
|
) {
|
||||||
|
@ -65,9 +65,9 @@ fun GroupedTransactionsListItems(
|
||||||
RoundedCornersCard {
|
RoundedCornersCard {
|
||||||
Column(Modifier.background(Color.White)) {
|
Column(Modifier.background(Color.White)) {
|
||||||
holdingsToDisplay.forEachIndexed { index, holding ->
|
holdingsToDisplay.forEachIndexed { index, holding ->
|
||||||
// key(statementOfHoldings.id) {
|
key(holding.id) {
|
||||||
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,49 +76,51 @@ fun GroupedTransactionsListItems(
|
||||||
}
|
}
|
||||||
|
|
||||||
items(groupedByDate.keys.sortedDescending()) { groupingDate ->
|
items(groupedByDate.keys.sortedDescending()) { groupingDate ->
|
||||||
Column(Modifier.fillMaxWidth()) {
|
key(groupingDate.toEpochDays()) {
|
||||||
Text(
|
Column(Modifier.fillMaxWidth()) {
|
||||||
text = DI.formatUtil.formatGroupingDate(groupingDate, transactionsGrouping),
|
Text(
|
||||||
color = Style.ListItemHeaderTextColor,
|
text = DI.formatUtil.formatGroupingDate(groupingDate, transactionsGrouping),
|
||||||
fontSize = 16.sp,
|
color = Style.ListItemHeaderTextColor,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontSize = 16.sp,
|
||||||
modifier = Modifier.padding(top = 8.dp, bottom = 2.dp),
|
fontWeight = FontWeight.SemiBold,
|
||||||
)
|
modifier = Modifier.padding(top = 8.dp, bottom = 2.dp),
|
||||||
|
)
|
||||||
|
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
|
|
||||||
val monthTransactions = groupedByDate[groupingDate].orEmpty().sortedByDescending { it.valueDate }
|
val monthTransactions = groupedByDate[groupingDate].orEmpty().sortedByDescending { it.valueDate }
|
||||||
|
|
||||||
RoundedCornersCard {
|
RoundedCornersCard {
|
||||||
Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed
|
Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed
|
||||||
monthTransactions.forEachIndexed { index, transaction ->
|
monthTransactions.forEachIndexed { index, transaction ->
|
||||||
key(transaction.id) {
|
key(transaction.id) {
|
||||||
TransactionListItem(banksById[transaction.bankId], transaction, index, monthTransactions.size)
|
TransactionListItem(banksById[transaction.bankId], transaction, index, monthTransactions.size)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth().padding(top = 10.dp),
|
Modifier.fillMaxWidth().padding(top = 10.dp),
|
||||||
horizontalAlignment = Alignment.End
|
horizontalAlignment = Alignment.End
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = formatUtil.formatAmount(calculator.sumIncome(monthTransactions),
|
text = formatUtil.formatAmount(calculator.sumIncome(monthTransactions),
|
||||||
calculator.getTransactionsCurrency(monthTransactions)),
|
calculator.getTransactionsCurrency(monthTransactions)),
|
||||||
color = formatUtil.getColorForAmount(Amount.Zero, showColoredAmounts)
|
color = formatUtil.getColorForAmount(Amount.Zero, showColoredAmounts)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 16.dp),
|
Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 16.dp),
|
||||||
horizontalAlignment = Alignment.End
|
horizontalAlignment = Alignment.End
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = formatUtil.formatAmount(calculator.sumExpenses(monthTransactions),
|
text = formatUtil.formatAmount(calculator.sumExpenses(monthTransactions),
|
||||||
calculator.getTransactionsCurrency(monthTransactions)),
|
calculator.getTransactionsCurrency(monthTransactions)),
|
||||||
color = formatUtil.getColorForAmount(Amount("-1"), showColoredAmounts)
|
color = formatUtil.getColorForAmount(Amount("-1"), showColoredAmounts)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,11 +51,11 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
||||||
|
|
||||||
DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData(
|
DI.uiState.showTransferMoneyDialogData.value = ShowTransferMoneyDialogData(
|
||||||
DI.uiState.banks.value.firstNotNullOf { it.accounts.firstOrNull { it.id == transaction.accountId } },
|
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?.otherPartyBankId,
|
||||||
transactionEntity?.otherPartyAccountId,
|
transactionEntity?.otherPartyAccountId,
|
||||||
if (withSameData) transaction.amount else null,
|
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(
|
||||||
text = transaction.otherPartyName ?: transaction.postingText ?: "",
|
text = transaction.userSetOtherPartyName ?: transaction.otherPartyName ?: transaction.postingText ?: "",
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
color = Style.ListItemHeaderTextColor,
|
color = Style.ListItemHeaderTextColor,
|
||||||
fontWeight = Style.ListItemHeaderWeight,
|
fontWeight = Style.ListItemHeaderWeight,
|
||||||
|
@ -95,7 +95,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = transaction.reference ?: "",
|
text = transaction.userSetReference ?: transaction.reference ?: "",
|
||||||
Modifier.fillMaxWidth(),
|
Modifier.fillMaxWidth(),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
|
@ -121,7 +121,7 @@ fun TransactionListItem(bank: BankAccess?, transaction: AccountTransactionViewMo
|
||||||
offset = showMenuAt ?: DpOffset.Zero,
|
offset = showMenuAt ?: DpOffset.Zero,
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem({ newMoneyTransferToOtherParty(false) }) {
|
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) }) {
|
DropdownMenuItem({ newMoneyTransferToOtherParty(true) }) {
|
||||||
|
|
|
@ -69,9 +69,9 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) {
|
LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) {
|
||||||
itemsIndexed(holdingsToDisplay) { index, holding ->
|
itemsIndexed(holdingsToDisplay) { index, holding ->
|
||||||
// key(holding.isin) {
|
key(holding.id) {
|
||||||
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsIndexed(transactionsToDisplay) { index, transaction ->
|
itemsIndexed(transactionsToDisplay) { index, transaction ->
|
||||||
|
|
|
@ -13,6 +13,8 @@ object Internationalization {
|
||||||
|
|
||||||
const val ErrorTransferMoney = "Überweisung konnte nicht ausgeführt werden"
|
const val ErrorTransferMoney = "Überweisung konnte nicht ausgeführt werden"
|
||||||
|
|
||||||
|
const val SaveToDatabase = "Daten konnten nicht in der Datenbank gespeichert werden"
|
||||||
|
|
||||||
const val ErrorBiometricAuthentication = "Biometrische Authentifizierung fehlgeschlagen"
|
const val ErrorBiometricAuthentication = "Biometrische Authentifizierung fehlgeschlagen"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ fun ApplicationErrorDialog(error: ApplicationError, onDismiss: (() -> Unit)? = n
|
||||||
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
ErroneousAction.AddAccount -> Internationalization.ErrorAddAccount
|
||||||
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
ErroneousAction.UpdateAccountTransactions -> Internationalization.ErrorUpdateAccountTransactions
|
||||||
ErroneousAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
ErroneousAction.TransferMoney -> Internationalization.ErrorTransferMoney
|
||||||
|
ErroneousAction.SaveToDatabase -> Internationalization.SaveToDatabase
|
||||||
ErroneousAction.BiometricAuthentication -> Internationalization.ErrorBiometricAuthentication
|
ErroneousAction.BiometricAuthentication -> Internationalization.ErrorBiometricAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,8 +22,10 @@ import net.codinux.banking.ui.forms.*
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseDialog(
|
fun BaseDialog(
|
||||||
title: String,
|
title: String,
|
||||||
|
centerTitle: Boolean = false,
|
||||||
confirmButtonTitle: String = "OK",
|
confirmButtonTitle: String = "OK",
|
||||||
confirmButtonEnabled: Boolean = true,
|
confirmButtonEnabled: Boolean = true,
|
||||||
|
dismissButtonTitle: String = "Abbrechen",
|
||||||
showProgressIndicatorOnConfirmButton: Boolean = false,
|
showProgressIndicatorOnConfirmButton: Boolean = false,
|
||||||
useMoreThanPlatformDefaultWidthOnMobile: Boolean = false,
|
useMoreThanPlatformDefaultWidthOnMobile: Boolean = false,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
|
@ -38,7 +40,7 @@ fun BaseDialog(
|
||||||
Column(Modifier.background(Color.White).padding(8.dp)) {
|
Column(Modifier.background(Color.White).padding(8.dp)) {
|
||||||
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
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) {
|
if (DI.platform.isDesktop) {
|
||||||
TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
TextButton(onDismiss, colors = ButtonDefaults.buttonColors(contentColor = Colors.Zinc700, backgroundColor = Color.Transparent)) {
|
||||||
|
@ -49,9 +51,9 @@ fun BaseDialog(
|
||||||
|
|
||||||
content()
|
content()
|
||||||
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth().padding(top = 8.dp)) {
|
||||||
TextButton(onClick = onDismiss, Modifier.weight(0.5f)) {
|
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(
|
TextButton(
|
||||||
|
@ -61,7 +63,7 @@ fun BaseDialog(
|
||||||
) {
|
) {
|
||||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (showProgressIndicatorOnConfirmButton) {
|
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())
|
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.composables.text.HeaderText
|
||||||
import net.codinux.banking.ui.config.Colors
|
import net.codinux.banking.ui.config.Colors
|
||||||
import net.codinux.banking.ui.extensions.verticalScroll
|
import net.codinux.banking.ui.extensions.verticalScroll
|
||||||
|
import net.codinux.banking.ui.model.Config.NewLine
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ErrorDialog(
|
fun ErrorDialog(
|
||||||
|
@ -22,7 +23,7 @@ fun ErrorDialog(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val effectiveText = if (exception == null) text else {
|
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,
|
TransferMoney,
|
||||||
|
|
||||||
|
SaveToDatabase,
|
||||||
|
|
||||||
BiometricAuthentication
|
BiometricAuthentication
|
||||||
}
|
}
|
|
@ -1,16 +1,20 @@
|
||||||
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.runtime.Composable
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
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.verticalScroll
|
||||||
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.SectionHeader
|
import net.codinux.banking.ui.forms.SectionHeader
|
||||||
|
|
||||||
private val formatUtil = DI.formatUtil
|
private val formatUtil = DI.formatUtil
|
||||||
|
@ -35,14 +39,50 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|
||||||
|| transaction.journalNumber != null || transaction.textKeyAddition != null
|
|| 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 {
|
SelectionContainer {
|
||||||
Column(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
|
Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(8.dp)) {
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
LabelledValue("Name", transaction.otherPartyName ?: "")
|
OutlinedTextField(
|
||||||
|
label = { Text("Name") },
|
||||||
|
value = enteredOtherPartyName,
|
||||||
|
onValueChange = { enteredOtherPartyName = it },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
LabelledValue("BIC", transaction.otherPartyBankId ?: "")
|
LabelledValue("BIC", transaction.otherPartyBankId ?: "")
|
||||||
|
|
||||||
|
@ -57,7 +97,12 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|
||||||
|
|
||||||
LabelledValue("Buchungstext", transaction.postingText ?: "")
|
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))
|
LabelledValue("Buchungsdatum", formatUtil.formatDate(transaction.bookingDate))
|
||||||
|
|
||||||
|
@ -70,6 +115,16 @@ fun AccountTransactionDetailsScreen(transaction: AccountTransactionEntity, onClo
|
||||||
transaction.closingBalance?.let {
|
transaction.closingBalance?.let {
|
||||||
LabelledValue("Tagesendsaldo", formatUtil.formatAmount(it, accountCurrency))
|
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) {
|
if (hasDetailedValues) {
|
||||||
|
|
|
@ -6,7 +6,9 @@ import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.codinux.banking.persistence.entities.BankAccountEntity
|
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.config.Internationalization
|
||||||
import net.codinux.banking.ui.extensions.verticalScroll
|
import net.codinux.banking.ui.extensions.verticalScroll
|
||||||
import net.codinux.banking.ui.forms.*
|
import net.codinux.banking.ui.forms.*
|
||||||
|
@ -16,20 +18,36 @@ fun BankAccountSettingsScreen(account: BankAccountEntity, onClosed: () -> Unit)
|
||||||
|
|
||||||
var enteredAccountName by remember { mutableStateOf(account.displayName) }
|
var enteredAccountName by remember { mutableStateOf(account.displayName) }
|
||||||
|
|
||||||
var selectedIncludeInAutomaticAccountsUpdate by remember { mutableStateOf(account.includeInAutomaticAccountsUpdate) }
|
|
||||||
|
|
||||||
var selectedHideAccount by remember { mutableStateOf(account.hideAccount) }
|
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(
|
mutableStateOf(
|
||||||
enteredAccountName != account.displayName
|
enteredAccountName != account.displayName
|
||||||
|| selectedIncludeInAutomaticAccountsUpdate != account.includeInAutomaticAccountsUpdate
|
|
||||||
|| selectedHideAccount != account.hideAccount
|
|| 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(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
|
||||||
Column {
|
Column {
|
||||||
SectionHeader("Einstellungen", false)
|
SectionHeader("Einstellungen", false)
|
||||||
|
@ -41,9 +59,9 @@ fun BankAccountSettingsScreen(account: BankAccountEntity, onClosed: () -> Unit)
|
||||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp)
|
modifier = Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
// BooleanOption("Bei Kontoaktualisierung einbeziehen", selectedIncludeInAutomaticAccountsUpdate) { selectedIncludeInAutomaticAccountsUpdate = it }
|
BooleanOption("Bei Kontoaktualisierung einbeziehen (autom. Kontoaktualisierung noch nicht umgesetzt)", selectedIncludeInAutomaticAccountsUpdate) { selectedIncludeInAutomaticAccountsUpdate = it }
|
||||||
//
|
|
||||||
// BooleanOption("Konto ausblenden", selectedHideAccount) { selectedHideAccount = it }
|
BooleanOption("Konto ausblenden", selectedHideAccount) { selectedHideAccount = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionContainer {
|
SelectionContainer {
|
||||||
|
|
|
@ -2,13 +2,20 @@ package net.codinux.banking.ui.screens
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
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.persistence.entities.BankAccessEntity
|
||||||
|
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.dialogs.ConfirmDialog
|
||||||
import net.codinux.banking.ui.extensions.verticalScroll
|
import net.codinux.banking.ui.extensions.verticalScroll
|
||||||
import net.codinux.banking.ui.forms.*
|
import net.codinux.banking.ui.forms.*
|
||||||
|
import net.codinux.banking.ui.model.Config.NewLine
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
||||||
|
@ -19,15 +26,48 @@ fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
||||||
|
|
||||||
var enteredPassword by remember { mutableStateOf(bank.password ?: "") }
|
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(
|
mutableStateOf(
|
||||||
(enteredBankName != bank.bankName && (bank.userSetDisplayName == null || enteredBankName != bank.userSetDisplayName))
|
(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(Modifier.fillMaxSize().verticalScroll().padding(8.dp)) {
|
||||||
Column {
|
Column {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
|
@ -98,13 +138,13 @@ fun BankSettingsScreen(bank: BankAccessEntity, onClosed: () -> Unit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
//
|
|
||||||
// Column(Modifier.padding(top = 18.dp, bottom = 18.dp)) {
|
Column(Modifier.padding(top = 24.dp, bottom = 18.dp)) {
|
||||||
// TextButton(modifier = Modifier.fillMaxWidth().height(50.dp), onClick = { }, enabled = false) {
|
TextButton(modifier = Modifier.fillMaxWidth().height(50.dp), onClick = { showDeleteBankAccessConfirmationDialog = true }) {
|
||||||
// Text("Konto löschen", color = Colors.DestructiveColor, textAlign = TextAlign.Center)
|
Text("Konto löschen", fontSize = 15.sp, color = Colors.DestructiveColor, textAlign = TextAlign.Center)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,7 +23,10 @@ fun FullscreenViewBase(
|
||||||
title: String,
|
title: String,
|
||||||
confirmButtonTitle: String = "OK",
|
confirmButtonTitle: String = "OK",
|
||||||
confirmButtonEnabled: Boolean = true,
|
confirmButtonEnabled: Boolean = true,
|
||||||
|
dismissButtonTitle: String = "Abbrechen",
|
||||||
|
showDismissButton: Boolean = false,
|
||||||
showButtonBar: Boolean = true,
|
showButtonBar: Boolean = true,
|
||||||
|
onConfirm: (() -> Unit)? = null,
|
||||||
onClosed: () -> Unit,
|
onClosed: () -> Unit,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
|
@ -49,16 +52,18 @@ fun FullscreenViewBase(
|
||||||
|
|
||||||
if (showButtonBar) {
|
if (showButtonBar) {
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||||
// TextButton(onClick = onClosed, Modifier.width(Style.DialogButtonWidth)) {
|
if (showDismissButton) {
|
||||||
// Text("Abbrechen", color = Colors.CodinuxSecondaryColor)
|
TextButton(onClick = onClosed, Modifier.weight(1f)) {
|
||||||
// }
|
Text(dismissButtonTitle, color = Colors.CodinuxSecondaryColor)
|
||||||
//
|
}
|
||||||
// Spacer(Modifier.width(8.dp))
|
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
|
||||||
TextButton(
|
TextButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.weight(1f),
|
||||||
enabled = confirmButtonEnabled,
|
enabled = confirmButtonEnabled,
|
||||||
onClick = { /* onConfirm?.invoke() ?: */ onClosed() }
|
onClick = { onConfirm?.invoke(); onClosed() }
|
||||||
) {
|
) {
|
||||||
Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center)
|
Text(confirmButtonTitle, color = Colors.CodinuxSecondaryColor, textAlign = TextAlign.Center)
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,9 +82,9 @@ fun ProtectAppSettingsDialog(appSettings: AppSettings, onClosed: () -> Unit) {
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
|
|
||||||
if (selectedAuthenticationMethod == AppAuthenticationMethod.None) {
|
if (selectedAuthenticationMethod == AppAuthenticationMethod.None) {
|
||||||
Row(Modifier.fillMaxWidth()) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||||
if (currentAuthenticationMethod == AppAuthenticationMethod.None) {
|
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 {
|
} else {
|
||||||
Text("Möchten Sie den Appzugangsschutz wirklich entfernen?", fontSize = 18.sp, textAlign = TextAlign.Center)
|
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 =
|
private fun matchesSearchTerm(transaction: AccountTransactionViewModel, searchTerm: String): Boolean =
|
||||||
transaction.reference?.contains(searchTerm, true) == true
|
transaction.reference?.contains(searchTerm, true) == true
|
||||||
|
|| transaction.userSetReference?.contains(searchTerm, true) == true
|
||||||
|| transaction.otherPartyName?.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> {
|
fun filterHoldings(holdings: List<HoldingEntity>, filter: AccountTransactionsFilter): List<HoldingEntity> {
|
||||||
|
|
|
@ -19,6 +19,7 @@ class BankDataImporterAndExporter {
|
||||||
transactions.forEach { transaction ->
|
transactions.forEach { transaction ->
|
||||||
writer.writeRow(
|
writer.writeRow(
|
||||||
// TODO: add bank and bank account
|
// TODO: add bank and bank account
|
||||||
|
// TODO: also regard userSetOtherPartyName and userSetReference?
|
||||||
formatAmount(transaction.amount, decimalSeparator), transaction.currency,
|
formatAmount(transaction.amount, decimalSeparator), transaction.currency,
|
||||||
transaction.valueDate.toString(), transaction.bookingDate.toString(),
|
transaction.valueDate.toString(), transaction.bookingDate.toString(),
|
||||||
transaction.reference,
|
transaction.reference,
|
||||||
|
|
|
@ -72,10 +72,10 @@ class BankingService(
|
||||||
updateOnChanges(uiSettings)
|
updateOnChanges(uiSettings)
|
||||||
|
|
||||||
|
|
||||||
uiState.banks.value = getAllBanks()
|
updateBanksInUi(getAllBanks())
|
||||||
|
|
||||||
uiState.transactions.value = getAllAccountTransactionsAsViewModel()
|
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) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not read all banks and account transactions from repository" }
|
log.error(e) { "Could not read all banks and account transactions from repository" }
|
||||||
}
|
}
|
||||||
|
@ -93,6 +93,60 @@ class BankingService(
|
||||||
|
|
||||||
fun getAllBanks() = bankingRepository.getAllBanks()
|
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 getAllAccountTransactions() = bankingRepository.getAllAccountTransactions()
|
||||||
|
|
||||||
fun getAllTransactionsForBank(bank: BankAccessEntity) = bankingRepository.getAllTransactionsForBank(bank)
|
fun getAllTransactionsForBank(bank: BankAccessEntity) = bankingRepository.getAllTransactionsForBank(bank)
|
||||||
|
@ -113,14 +167,12 @@ class BankingService(
|
||||||
if (response.type == ResponseType.Success && response.data != null) {
|
if (response.type == ResponseType.Success && response.data != null) {
|
||||||
handleSuccessfulGetAccountDataResponse(response.data!!)
|
handleSuccessfulGetAccountDataResponse(response.data!!)
|
||||||
} else {
|
} else {
|
||||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.AddAccount, response)
|
handleUnsuccessfulBankingClientResponse(BankingClientAction.AddAccount, bank.name, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.type == ResponseType.Success
|
return response.type == ResponseType.Success
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not add account for ${bank.name} $loginName" }
|
showAndLogError(ErroneousAction.AddAccount, "Could not add account for ${bank.name} $loginName", "Konto für ${bank.name} konnte nicht hinzugefügt werden", e)
|
||||||
|
|
||||||
uiState.applicationErrorOccurred(ErroneousAction.AddAccount, null, e)
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -144,9 +196,7 @@ class BankingService(
|
||||||
|
|
||||||
log.info { "Saved bank $newBankEntity with ${newBankEntity.accounts.flatMap { it.bookedTransactions }.size} transactions" }
|
log.info { "Saved bank $newBankEntity with ${newBankEntity.accounts.flatMap { it.bookedTransactions }.size} transactions" }
|
||||||
|
|
||||||
val banks = uiState.banks.value.toMutableList()
|
updateBanksInUi(uiState.banks.value.toMutableList().also { it.add(newBankEntity) })
|
||||||
banks.add(newBankEntity)
|
|
||||||
uiState.banks.value = banks
|
|
||||||
|
|
||||||
updateTransactionsInUi(newBankEntity.accounts.flatMap { it.bookedTransactions })
|
updateTransactionsInUi(newBankEntity.accounts.flatMap { it.bookedTransactions })
|
||||||
updateHoldingsInUi(newBankEntity.accounts.flatMap { it.holdings }, emptyList())
|
updateHoldingsInUi(newBankEntity.accounts.flatMap { it.holdings }, emptyList())
|
||||||
|
@ -161,7 +211,8 @@ class BankingService(
|
||||||
if (selectedAccount != null) {
|
if (selectedAccount != null) {
|
||||||
updateAccountTransactions(selectedAccount.bank, selectedAccount.bankAccount)
|
updateAccountTransactions(selectedAccount.bank, selectedAccount.bankAccount)
|
||||||
} else {
|
} else {
|
||||||
uiState.banks.value.forEach { bank ->
|
getCurrentUiBanksList().forEach { bank ->
|
||||||
|
// TODO: when implementing automatic account transactions update, filter out accounts with includeInAutomaticAccountsUpdate == false
|
||||||
updateAccountTransactions(bank)
|
updateAccountTransactions(bank)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +225,7 @@ class BankingService(
|
||||||
if (response.type == ResponseType.Success && response.data != null) {
|
if (response.type == ResponseType.Success && response.data != null) {
|
||||||
handleSuccessfulUpdateAccountTransactionsResponse(bank, response.data!!)
|
handleSuccessfulUpdateAccountTransactionsResponse(bank, response.data!!)
|
||||||
} else {
|
} else {
|
||||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, response)
|
handleUnsuccessfulBankingClientResponse(BankingClientAction.UpdateAccountTransactions, bankAccount?.displayName ?: bank.displayName, response)
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not update account transactions for $bank" }
|
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> {
|
private fun updateTransactionsInUi(addedTransactions: List<AccountTransactionEntity>): List<AccountTransactionViewModel> {
|
||||||
val transactionsViewModel = addedTransactions.map { AccountTransactionViewModel(it) }
|
val transactionsViewModel = addedTransactions.map { AccountTransactionViewModel(it) }
|
||||||
|
|
||||||
|
@ -281,7 +388,7 @@ class BankingService(
|
||||||
))
|
))
|
||||||
|
|
||||||
if (response.error != null) {
|
if (response.error != null) {
|
||||||
handleUnsuccessfulBankingClientResponse(BankingClientAction.TransferMoney, response)
|
handleUnsuccessfulBankingClientResponse(BankingClientAction.TransferMoney, account.displayName, response)
|
||||||
} else if (response.type == ResponseType.Success) {
|
} else if (response.type == ResponseType.Success) {
|
||||||
uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency))
|
uiState.dispatchTransferredMoney(TransferredMoneyEvent(recipientName, amount, currency))
|
||||||
|
|
||||||
|
@ -292,8 +399,8 @@ class BankingService(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, response: Response<*>) {
|
private fun handleUnsuccessfulBankingClientResponse(action: BankingClientAction, accountOrBankName: String, response: Response<*>) {
|
||||||
log.error { "$action was not successful: $response" }
|
log.error { "$action was not successful for $accountOrBankName: $response" }
|
||||||
|
|
||||||
response.error?.let { error ->
|
response.error?.let { error ->
|
||||||
if (error.type != ErrorType.UserCancelledAction) { // the user knows that she cancelled the action
|
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> {
|
private suspend fun readTransactionsFromCsv(): List<AccountTransaction> {
|
||||||
val csv = Res.readBytes("files/transactions.csv").decodeToString()
|
val csv = Res.readBytes("files/transactions.csv").decodeToString()
|
||||||
|
|
|
@ -32,6 +32,7 @@ class RecipientFinder(private val bankFinder: BankFinder) {
|
||||||
|
|
||||||
suspend fun updateData(transactions: List<AccountTransaction>) {
|
suspend fun updateData(transactions: List<AccountTransaction>) {
|
||||||
availableRecipients = transactions.mapNotNull {
|
availableRecipients = transactions.mapNotNull {
|
||||||
|
// TODO: also regard userSetOtherPartyName?
|
||||||
if (it.otherPartyName != null && it.otherPartyAccountId != null) {
|
if (it.otherPartyName != null && it.otherPartyAccountId != null) {
|
||||||
RecipientSuggestion(it.otherPartyName!!, it.otherPartyBankId, it.otherPartyAccountId!!)
|
RecipientSuggestion(it.otherPartyName!!, it.otherPartyBankId, it.otherPartyAccountId!!)
|
||||||
} else {
|
} else {
|
||||||
|
@ -45,6 +46,7 @@ class RecipientFinder(private val bankFinder: BankFinder) {
|
||||||
|
|
||||||
transactionsByIban = transactions.filter { it.otherPartyAccountId != null }.groupBy { it.otherPartyAccountId!! }
|
transactionsByIban = transactions.filter { it.otherPartyAccountId != null }.groupBy { it.otherPartyAccountId!! }
|
||||||
.mapValues { it.value.map {
|
.mapValues { it.value.map {
|
||||||
|
// TODO: also regard userSetReference?
|
||||||
PaymentDataSuggestion(it.reference ?: "", Amount(it.amount.toString().replace("-", "")), it.currency, it.valueDate)
|
PaymentDataSuggestion(it.reference ?: "", Amount(it.amount.toString().replace("-", "")), it.currency, it.valueDate)
|
||||||
}.toSet().sortedByDescending { it.valueDate } }
|
}.toSet().sortedByDescending { it.valueDate } }
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
@ -11,6 +12,9 @@ import kotlinx.coroutines.Dispatchers
|
||||||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||||
get() = Dispatchers.IO
|
get() = Dispatchers.IO
|
||||||
|
|
||||||
|
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean = false
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package net.codinux.banking.ui
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.cinterop.ExperimentalForeignApi
|
import kotlinx.cinterop.ExperimentalForeignApi
|
||||||
|
@ -16,6 +17,9 @@ import platform.UIKit.UIScreen
|
||||||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||||
get() = Dispatchers.IO
|
get() = Dispatchers.IO
|
||||||
|
|
||||||
|
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean = false // TODO
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalForeignApi::class)
|
@OptIn(ExperimentalForeignApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.ui
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
import androidx.compose.ui.input.key.KeyEvent
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalWindowInfo
|
import androidx.compose.ui.platform.LocalWindowInfo
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
@ -11,6 +12,9 @@ import kotlinx.coroutines.Dispatchers
|
||||||
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
actual val Dispatchers.IOorDefault: CoroutineDispatcher
|
||||||
get() = Dispatchers.Default
|
get() = Dispatchers.Default
|
||||||
|
|
||||||
|
actual fun KeyEvent.isBackButtonPressedEvent(): Boolean = false
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
actual fun rememberScreenSizeInfo(): ScreenSizeInfo {
|
||||||
|
|
Loading…
Reference in New Issue