From 28530d63cd81692140641ee3f57e98629d6503cb Mon Sep 17 00:00:00 2001 From: dankito Date: Wed, 11 Sep 2024 23:40:48 +0200 Subject: [PATCH] Implemented displaying Holdings --- .../transactions/HoldingListItemPreview.kt | 23 ++++ .../dataaccess/SqliteBankingRepository.kt | 4 +- .../dataaccess/entities/BankAccountEntity.kt | 7 +- .../banking/ui/appskeleton/SideMenuContent.kt | 5 +- .../ui/composables/NavigationMenuItem.kt | 2 +- .../banking/ui/composables/StateHandler.kt | 19 +++- .../ui/composables/text/ItemDivider.kt | 14 +++ .../GroupedTransactionsListItems.kt | 23 ++++ .../transactions/HoldingListItem.kt | 103 ++++++++++++++++++ .../transactions/TransactionListItem.kt | 5 +- .../transactions/TransactionsList.kt | 15 ++- .../net/codinux/banking/ui/config/Colors.kt | 5 + .../net/codinux/banking/ui/config/Style.kt | 4 + .../banking/ui/forms/AutocompleteTextField.kt | 4 +- .../AccountTransactionsRetrievedEvent.kt | 4 +- .../banking/ui/service/BankingService.kt | 12 +- .../codinux/banking/ui/service/FormatUtil.kt | 40 ++++--- .../net/codinux/banking/ui/state/UiState.kt | 3 + 18 files changed, 255 insertions(+), 37 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItemPreview.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/text/ItemDivider.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItem.kt diff --git a/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItemPreview.kt b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItemPreview.kt new file mode 100644 index 0000000..cc60fec --- /dev/null +++ b/composeApp/src/androidMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItemPreview.kt @@ -0,0 +1,23 @@ +package net.codinux.banking.ui.composables.transactions + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import net.codinux.banking.client.model.Amount +import net.codinux.banking.client.model.securitiesaccount.Holding +import net.codinux.banking.ui.forms.RoundedCornersCard + +@Preview +@Composable +fun HoldingListItemPreview() { + val holding1 = Holding("MUL Amundi MSCI World V", null, null, 1693, "EUR", Amount("18578.04"), Amount("16.888"), -0.35f, Amount("17944.48"), Amount("16.828")) + val holding2 = Holding("NVIDIA Corp.", null, null, 214, "EUR", Amount("21455.36"), Amount("100.18"), 8.8f, Amount("19872.04"), Amount("92.04")) + + RoundedCornersCard { + Column { + HoldingListItem(holding1, false, false) + + HoldingListItem(holding2, true, true) + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/SqliteBankingRepository.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/SqliteBankingRepository.kt index 5e40efb..5c5ed4d 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/SqliteBankingRepository.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/SqliteBankingRepository.kt @@ -71,7 +71,7 @@ open class SqliteBankingRepository( mapToInt(serverTransactionsRetentionDays), mapToInstant(lastTransactionsRetrievalTime), mapToDate(retrievedTransactionsFrom), - mutableListOf(), mutableListOf(), + mutableListOf(), mutableListOf(), emptyList(), userSetDisplayName, mapToInt(displayIndex), hideAccount, includeInAutomaticAccountsUpdate @@ -106,7 +106,7 @@ open class SqliteBankingRepository( persistTransaction(userId, accountId, transaction) } - return BankAccountEntity(accountId, userId, account, accountTransactionEntities) + return BankAccountEntity(accountId, userId, account, accountTransactionEntities, account.holdings) } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/entities/BankAccountEntity.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/entities/BankAccountEntity.kt index 9e3bf78..08ebbd6 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/entities/BankAccountEntity.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/dataaccess/entities/BankAccountEntity.kt @@ -3,6 +3,7 @@ package net.codinux.banking.dataaccess.entities import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import net.codinux.banking.client.model.* +import net.codinux.banking.client.model.securitiesaccount.Holding class BankAccountEntity( val id: Long, @@ -29,6 +30,7 @@ class BankAccountEntity( bookedTransactions: MutableList = mutableListOf(), prebookedTransactions: MutableList = mutableListOf(), + holdings: List = emptyList(), userSetDisplayName: String? = null, displayIndex: Int = 0, @@ -47,11 +49,12 @@ class BankAccountEntity( serverTransactionsRetentionDays, lastTransactionsRetrievalTime, retrievedTransactionsFrom, bookedTransactions as MutableList, prebookedTransactions, + holdings, userSetDisplayName, displayIndex, hideAccount, includeInAutomaticAccountsUpdate ) { - constructor(id: Long, userId: Long, account: BankAccount, transactions: List = emptyList()) : this( + constructor(id: Long, userId: Long, account: BankAccount, transactions: List = emptyList(), holdings: List = emptyList()) : this( id, userId, account.identifier, account.subAccountNumber, account.iban, account.productName, @@ -65,7 +68,7 @@ class BankAccountEntity( account.serverTransactionsRetentionDays, account.lastTransactionsRetrievalTime, account.retrievedTransactionsFrom, - transactions.toMutableList(), mutableListOf(), + transactions.toMutableList(), mutableListOf(), holdings, account.userSetDisplayName, account.displayIndex, account.hideAccount, account.includeInAutomaticAccountsUpdate diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt index c1f9efd..ec766d7 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/SideMenuContent.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.launch import net.codinux.banking.ui.composables.BanksList import net.codinux.banking.ui.composables.NavigationMenuItem import net.codinux.banking.ui.composables.settings.UiSettings +import net.codinux.banking.ui.composables.text.ItemDivider import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.model.ShowTransferMoneyDialogData @@ -69,7 +70,7 @@ fun SideMenuContent() { Text("Version 1.0.0 Alpha 12", color = Color.LightGray) } - Divider(color = Colors.DrawerDivider) + ItemDivider(color = Colors.DrawerDivider) Column(Modifier.padding(horizontal = 16.dp, vertical = 24.dp)) { Column(Modifier.height(ItemHeight), verticalArrangement = Arrangement.Center) { @@ -110,7 +111,7 @@ fun SideMenuContent() { } if (accounts.isNotEmpty()) { - Divider(color = Colors.DrawerDivider) + ItemDivider(color = Colors.DrawerDivider) Column(Modifier.padding(16.dp)) { UiSettings(Modifier.fillMaxWidth().padding(bottom = VerticalSpacing), textColor) diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/NavigationMenuItem.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/NavigationMenuItem.kt index bd37631..66973f4 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/NavigationMenuItem.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/NavigationMenuItem.kt @@ -86,7 +86,7 @@ fun NavigationMenuItem( if (balance != null) { Text( formatUtil.formatAmount(balance, calculator.getTransactionsCurrency(emptyList())), - color = if (showColoredAmounts) formatUtil.getColorForAmount(balance) else textColor, + color = formatUtil.getColorForAmount(balance, showColoredAmounts), modifier = Modifier.padding(start = 4.dp) ) } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt index c94d83d..3849b70 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/StateHandler.kt @@ -59,17 +59,24 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) { LaunchedEffect(Unit) { coroutineScope.launch { uiState.transactionsRetrievedEvents.collect { event -> - val messagePrefix = if (event.newTransactions.isEmpty()) { - "Keine neuen Umsätze" - } else if (event.newTransactions.size == 1) { - "1 Umsatz" + var actionLabel = "Coolio" + + val messagePrefix = if (event.newTransactions.size == 1) { + "1 neuer Umsatz" + } else if (event.newTransactions.size > 1) { + "${event.newTransactions.size} neue Umsätze" + } else if (event.updatedHoldings.size == 1) { + "1 Kurse aktualisiert" + } else if (event.updatedHoldings.size > 1) { + "${event.updatedHoldings.size} Kurse aktualisiert" } else { - "${event.newTransactions.size} Umsätze" + actionLabel = "Na super" + "Keine neuen Umsätze" } snackbarHostState.showSnackbar( message = "$messagePrefix für ${event.user.displayName} ${event.account.displayName}", - actionLabel = "Coolio", + actionLabel = actionLabel, duration = SnackbarDuration.Long ) } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/text/ItemDivider.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/text/ItemDivider.kt new file mode 100644 index 0000000..2d82cde --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/text/ItemDivider.kt @@ -0,0 +1,14 @@ +package net.codinux.banking.ui.composables.text + +import androidx.compose.material.Divider +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Dp +import net.codinux.banking.ui.config.Colors +import net.codinux.banking.ui.config.Style + +@Composable +fun ItemDivider(color: Color = Colors.ItemDividerColor, thickness: Dp = Style.DividerThickness, modifier: Modifier = Modifier) { + Divider(color = color, thickness = thickness, modifier = modifier) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/GroupedTransactionsListItems.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/GroupedTransactionsListItems.kt index 83dc406..68d0f7e 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/GroupedTransactionsListItems.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/GroupedTransactionsListItems.kt @@ -13,6 +13,7 @@ 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.dataaccess.entities.UserEntity import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.forms.RoundedCornersCard @@ -28,6 +29,7 @@ private val formatUtil = DI.formatUtil fun GroupedTransactionsListItems( modifier: Modifier, transactionsToDisplay: List, + holdingsToDisplay: List, usersById: Map, transactionsGrouping: TransactionsGrouping ) { @@ -40,6 +42,27 @@ fun GroupedTransactionsListItems( val showColoredAmounts by DI.uiSettings.showColoredAmounts.collectAsState() + if (holdingsToDisplay.isNotEmpty()) { + Column(Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 16.dp)) { + Text( + text = "Depotwerte", + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(bottom = 2.dp), + ) + + RoundedCornersCard { + Column(Modifier.background(Color.White)) { + holdingsToDisplay.forEachIndexed { index, holding -> +// key(statementOfHoldings.id) { + HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1) +// } + } + } + } + } + } + LazyColumn(modifier, contentPadding = PaddingValues(bottom = 12.dp)) { // padding bottom = add the space the FAB sticks into the content area (= 26 - the 16 we add at the bottom of the expenses line) items(groupedByDate.keys.sortedDescending()) { groupingDate -> Column(Modifier.fillMaxWidth()) { diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItem.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItem.kt new file mode 100644 index 0000000..6f5e2a5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/HoldingListItem.kt @@ -0,0 +1,103 @@ +package net.codinux.banking.ui.composables.transactions + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import net.codinux.banking.client.model.Amount +import net.codinux.banking.client.model.DefaultValues +import net.codinux.banking.client.model.securitiesaccount.Holding +import net.codinux.banking.ui.composables.text.ItemDivider +import net.codinux.banking.ui.config.Colors +import net.codinux.banking.ui.config.DI + +private val uiSettings = DI.uiSettings + +private val formatUtil = DI.formatUtil + +private val verticalSpace = 6.dp + +@Composable +fun HoldingListItem(holding: Holding, isOddItem: Boolean = false, isNotLastItem: Boolean = true, fallbackCurrency: String = DefaultValues.DefaultCurrency) { + + // TODO: also regard showBalance? + val showColoredAmounts by uiSettings.showColoredAmounts.collectAsState() + + val zebraStripes by uiSettings.zebraStripes.collectAsState() + + val showBankIcons by uiSettings.showBankIcons.collectAsState() + + val backgroundColor = if (zebraStripes && isOddItem) Colors.ZebraStripesColor else Color.White + + val currency = holding.currency ?: fallbackCurrency + + + Column(Modifier.fillMaxWidth().background(backgroundColor).padding(horizontal = 6.dp, vertical = 6.dp)) { + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + if (showBankIcons) { +// BankIcon(user, Modifier.padding(end = 6.dp)) + } + + Text( + holding.name, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f).padding(end = 4.dp) + ) + + Row(Modifier.width(70.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { + val performance = holding.performancePercentage + if (performance != null) { + Text( + text = formatUtil.formatPercentage(performance), + color = formatUtil.getColorForAmount(Amount(performance.toDouble()), showColoredAmounts) + ) + } + } + } + + Row(Modifier.fillMaxWidth().padding(top = verticalSpace), verticalAlignment = Alignment.CenterVertically) { + Row(Modifier.weight(1f).padding(end = 6.dp), verticalAlignment = Alignment.CenterVertically) { + // TODO: set maxLines = 1 and TextOverflow.Ellipsis + if (holding.quantity != null) { + Text(holding.quantity.toString() + " Stück, ") + } + + if (holding.averageCostPrice != null) { + Text(formatUtil.formatAmount(holding.averageCostPrice!!, currency) + " ➞ ") + } + + val marketValue = holding.marketValue + if (marketValue != null) { + Text(formatUtil.formatAmount(marketValue, currency), color = formatUtil.getColorForAmount(marketValue, showColoredAmounts)) + } + } + + Row(Modifier.widthIn(90.dp, 210.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically) { + if (holding.totalCostPrice != null) { + Text(formatUtil.formatAmount(holding.totalCostPrice!!, currency) + " ➞ ") + } + + val totalBalance = holding.totalBalance + if (totalBalance != null) { + Text(formatUtil.formatAmount(totalBalance, currency), color = formatUtil.getColorForAmount(totalBalance, showColoredAmounts)) + } + } + } + } + + + if (isNotLastItem) { + ItemDivider() + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionListItem.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionListItem.kt index b75cc62..763a0ac 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionListItem.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionListItem.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import net.codinux.banking.client.model.User import net.codinux.banking.ui.composables.BankIcon +import net.codinux.banking.ui.composables.text.ItemDivider import net.codinux.banking.ui.config.Colors import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.model.AccountTransactionViewModel @@ -33,7 +34,7 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i val showColoredAmounts by uiSettings.showColoredAmounts.collectAsState() - val backgroundColor = if (zebraStripes && itemIndex % 2 == 1) Colors.Zinc100_50 else Color.White + val backgroundColor = if (zebraStripes && itemIndex % 2 == 1) Colors.ZebraStripesColor else Color.White val bottomPadding = 56.dp @@ -128,6 +129,6 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i } if (itemIndex < countItems - 1) { - Divider(color = Colors.Zinc200, thickness = 1.dp) + ItemDivider() } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionsList.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionsList.kt index 9ca12d5..8dec6b0 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionsList.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/transactions/TransactionsList.kt @@ -38,6 +38,13 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean derivedStateOf { filterService.filterAccounts(transactions, transactionsFilter) } } + val holdings by uiState.holdings.collectAsState() + + val holdingsToDisplay by remember(transactionsFilter, holdings) { +// derivedStateOf { filterService.filterAccounts(transactions, transactionsFilter) } // TODO: filter which holdings to display + derivedStateOf { holdings } + } + val showBalance by uiSettings.showBalance.collectAsState() val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState() @@ -59,9 +66,15 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean } if (transactionsGrouping != TransactionsGrouping.None) { - GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, usersById, transactionsGrouping) + GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, holdingsToDisplay, usersById, transactionsGrouping) } else { LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) { + itemsIndexed(holdingsToDisplay) { index, holding -> +// key(holding.isin) { + HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1) +// } + } + itemsIndexed(transactionsToDisplay) { index, transaction -> key(transaction.id) { TransactionListItem(usersById[transaction.userId], transaction, index, transactionsToDisplay.size) diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt index 7d41f4b..d27bd08 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Colors.kt @@ -52,4 +52,9 @@ object Colors { val Emerald700 = Color(4, 120, 87) + + val ZebraStripesColor = Zinc100_50 + + val ItemDividerColor = Colors.Zinc200 + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Style.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Style.kt index 1db47ef..93ea59e 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Style.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Style.kt @@ -2,6 +2,7 @@ package net.codinux.banking.ui.config import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp object Style { @@ -12,4 +13,7 @@ object Style { val HeaderFontWeight: FontWeight = FontWeight.Bold + + val DividerThickness = 1.dp + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/AutocompleteTextField.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/AutocompleteTextField.kt index fa9e087..9024326 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/AutocompleteTextField.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/AutocompleteTextField.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import net.codinux.banking.ui.config.Colors +import net.codinux.banking.ui.composables.text.ItemDivider @OptIn(ExperimentalMaterialApi::class) @Composable @@ -125,7 +125,7 @@ fun AutocompleteTextField( } if (showDividersBetweenItems && index < suggestions.size - 1) { - Divider(color = Colors.Zinc200, thickness = 1.dp, modifier = Modifier.padding(horizontal = 8.dp)) + ItemDivider(modifier = Modifier.padding(horizontal = 8.dp)) } } } diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/events/AccountTransactionsRetrievedEvent.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/events/AccountTransactionsRetrievedEvent.kt index 66a0b94..c3d19ac 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/events/AccountTransactionsRetrievedEvent.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/events/AccountTransactionsRetrievedEvent.kt @@ -2,10 +2,12 @@ package net.codinux.banking.ui.model.events import net.codinux.banking.client.model.BankAccount import net.codinux.banking.client.model.User +import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.ui.model.AccountTransactionViewModel data class AccountTransactionsRetrievedEvent( val user: User, val account: BankAccount, - val newTransactions: List + val newTransactions: List, + val updatedHoldings: List = emptyList() ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt index e2ed763..6a5e53d 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/BankingService.kt @@ -11,6 +11,7 @@ import net.codinux.banking.client.model.options.RetrieveTransactions import net.codinux.banking.client.model.request.GetAccountDataRequest import net.codinux.banking.client.model.request.TransferMoneyRequestForUser import net.codinux.banking.client.model.response.* +import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.service.BankingModelService import net.codinux.banking.dataaccess.BankingRepository import net.codinux.banking.dataaccess.entities.AccountTransactionEntity @@ -114,6 +115,7 @@ class BankingService( uiState.users.value = users updateTransactionsInUi(newUserEntity.accounts.flatMap { it.bookedTransactionsEntities }) + updateHoldingsInUi(newUserEntity.accounts.flatMap { it.holdings }) } catch (e: Throwable) { log.error(e) { "Could not save user account ${response.user}" } } @@ -171,8 +173,10 @@ class BankingService( emptyList() } + updateHoldingsInUi(response.holdings) + val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities) - uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel)) + uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel, response.holdings)) } } catch (e: Throwable) { log.error(e) { "Could not save updated account transactions for user $user" } @@ -189,6 +193,12 @@ class BankingService( return transactionsViewModel } + private fun updateHoldingsInUi(holdings: List) { + val allHoldings = uiState.holdings.value.toMutableList() + allHoldings.addAll(holdings) + uiState.holdings.value = allHoldings.sortedByDescending { it.pricingTime } + } + suspend fun transferMoney(user: UserEntity, account: BankAccountEntity, recipientName: String, recipientAccountIdentifier: String, amount: Amount, currency: String, diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/FormatUtil.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/FormatUtil.kt index 07a9b9d..944b377 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/FormatUtil.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/FormatUtil.kt @@ -55,23 +55,8 @@ class FormatUtil { } - fun formatAmount(amount: Amount, currency: String): String { // TODO: find a better way - val parts = amount.amount.split('.') - - var integerPart = parts[0] - val isNegative = integerPart.startsWith("-") - - if ((isNegative && integerPart.length > 7) || (isNegative == false && integerPart.length > 6)) { - integerPart = (integerPart.substring(0, integerPart.length - 6) + "." + integerPart.substring(integerPart.length - 6)) - } - if ((isNegative && integerPart.length > 4) || (isNegative == false && integerPart.length > 3)) { - integerPart = (integerPart.substring(0, integerPart.length - 3) + "." + integerPart.substring(integerPart.length - 3)) - } - - val decimalPart = if (parts.size == 2) parts[1] else "00" - - // TODO: add thousands separator - return "$integerPart,${decimalPart.padEnd(2, '0').substring(0, 2)} ${formatCurrency(currency)}" + fun formatAmount(amount: Amount, currency: String): String { + return "${formatToTwoDecimalPlaces(amount.amount)} ${formatCurrency(currency)}" } fun formatCurrency(currency: String): String = when (currency) { @@ -85,6 +70,27 @@ class FormatUtil { else -> Colors.Green600 } + fun formatPercentage(performance: Float): String = + (if (performance > 0) "+" else "") + formatToTwoDecimalPlaces(performance.toString()) + " %" + + private fun formatToTwoDecimalPlaces(amount: String): String { // TODO: find a better way + val parts = amount.split('.') + + var integerPart = parts[0] + val isNegative = integerPart.startsWith("-") + + if ((isNegative && integerPart.length > 7) || (isNegative == false && integerPart.length > 6)) { + integerPart = (integerPart.substring(0, integerPart.length - 6) + "." + integerPart.substring(integerPart.length - 6)) + } + if ((isNegative && integerPart.length > 4) || (isNegative == false && integerPart.length > 3)) { + integerPart = (integerPart.substring(0, integerPart.length - 3) + "." + integerPart.substring(integerPart.length - 3)) + } + + val decimalPart = if (parts.size == 2) parts[1] else "00" + + return "$integerPart,${decimalPart.padEnd(2, '0').substring(0, 2)}" + } + fun minDigits(toConvert: Int, minDigits: Int): String = toConvert.toString().padStart(minDigits, '0') diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt index 848bb9d..aa2f2ab 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/state/UiState.kt @@ -5,6 +5,7 @@ import androidx.compose.material.DrawerValue import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import net.codinux.banking.client.model.securitiesaccount.Holding import net.codinux.banking.client.model.tan.EnterTanResult import net.codinux.banking.client.model.tan.TanChallenge import net.codinux.banking.dataaccess.entities.UserEntity @@ -21,6 +22,8 @@ class UiState : ViewModel() { val transactions = MutableStateFlow>(emptyList()) + val holdings = MutableStateFlow>(emptyList()) + val transactionsRetrievedEvents = MutableSharedFlow() suspend fun dispatchNewTransactionsRetrieved(event: AccountTransactionsRetrievedEvent) {