Implemented displaying Holdings
This commit is contained in:
parent
d618556f5b
commit
28530d63cd
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -71,7 +71,7 @@ open class SqliteBankingRepository(
|
||||||
mapToInt(serverTransactionsRetentionDays),
|
mapToInt(serverTransactionsRetentionDays),
|
||||||
mapToInstant(lastTransactionsRetrievalTime), mapToDate(retrievedTransactionsFrom),
|
mapToInstant(lastTransactionsRetrievalTime), mapToDate(retrievedTransactionsFrom),
|
||||||
|
|
||||||
mutableListOf(), mutableListOf(),
|
mutableListOf(), mutableListOf(), emptyList(),
|
||||||
|
|
||||||
userSetDisplayName, mapToInt(displayIndex),
|
userSetDisplayName, mapToInt(displayIndex),
|
||||||
hideAccount, includeInAutomaticAccountsUpdate
|
hideAccount, includeInAutomaticAccountsUpdate
|
||||||
|
@ -106,7 +106,7 @@ open class SqliteBankingRepository(
|
||||||
persistTransaction(userId, accountId, transaction)
|
persistTransaction(userId, accountId, transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
return BankAccountEntity(accountId, userId, account, accountTransactionEntities)
|
return BankAccountEntity(accountId, userId, account, accountTransactionEntities, account.holdings)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package net.codinux.banking.dataaccess.entities
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import net.codinux.banking.client.model.*
|
import net.codinux.banking.client.model.*
|
||||||
|
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||||
|
|
||||||
class BankAccountEntity(
|
class BankAccountEntity(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
|
@ -29,6 +30,7 @@ class BankAccountEntity(
|
||||||
|
|
||||||
bookedTransactions: MutableList<AccountTransactionEntity> = mutableListOf(),
|
bookedTransactions: MutableList<AccountTransactionEntity> = mutableListOf(),
|
||||||
prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
|
prebookedTransactions: MutableList<PrebookedAccountTransaction> = mutableListOf(),
|
||||||
|
holdings: List<Holding> = emptyList(),
|
||||||
|
|
||||||
userSetDisplayName: String? = null,
|
userSetDisplayName: String? = null,
|
||||||
displayIndex: Int = 0,
|
displayIndex: Int = 0,
|
||||||
|
@ -47,11 +49,12 @@ class BankAccountEntity(
|
||||||
serverTransactionsRetentionDays, lastTransactionsRetrievalTime, retrievedTransactionsFrom,
|
serverTransactionsRetentionDays, lastTransactionsRetrievalTime, retrievedTransactionsFrom,
|
||||||
|
|
||||||
bookedTransactions as MutableList<AccountTransaction>, prebookedTransactions,
|
bookedTransactions as MutableList<AccountTransaction>, prebookedTransactions,
|
||||||
|
holdings,
|
||||||
|
|
||||||
userSetDisplayName, displayIndex,
|
userSetDisplayName, displayIndex,
|
||||||
hideAccount, includeInAutomaticAccountsUpdate
|
hideAccount, includeInAutomaticAccountsUpdate
|
||||||
) {
|
) {
|
||||||
constructor(id: Long, userId: Long, account: BankAccount, transactions: List<AccountTransactionEntity> = emptyList()) : this(
|
constructor(id: Long, userId: Long, account: BankAccount, transactions: List<AccountTransactionEntity> = emptyList(), holdings: List<Holding> = emptyList()) : this(
|
||||||
id, userId,
|
id, userId,
|
||||||
account.identifier, account.subAccountNumber, account.iban, account.productName,
|
account.identifier, account.subAccountNumber, account.iban, account.productName,
|
||||||
|
|
||||||
|
@ -65,7 +68,7 @@ class BankAccountEntity(
|
||||||
account.serverTransactionsRetentionDays,
|
account.serverTransactionsRetentionDays,
|
||||||
account.lastTransactionsRetrievalTime, account.retrievedTransactionsFrom,
|
account.lastTransactionsRetrievalTime, account.retrievedTransactionsFrom,
|
||||||
|
|
||||||
transactions.toMutableList(), mutableListOf(),
|
transactions.toMutableList(), mutableListOf(), holdings,
|
||||||
|
|
||||||
account.userSetDisplayName, account.displayIndex,
|
account.userSetDisplayName, account.displayIndex,
|
||||||
account.hideAccount, account.includeInAutomaticAccountsUpdate
|
account.hideAccount, account.includeInAutomaticAccountsUpdate
|
||||||
|
|
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.launch
|
||||||
import net.codinux.banking.ui.composables.BanksList
|
import net.codinux.banking.ui.composables.BanksList
|
||||||
import net.codinux.banking.ui.composables.NavigationMenuItem
|
import net.codinux.banking.ui.composables.NavigationMenuItem
|
||||||
import net.codinux.banking.ui.composables.settings.UiSettings
|
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.Colors
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
|
import net.codinux.banking.ui.model.ShowTransferMoneyDialogData
|
||||||
|
@ -69,7 +70,7 @@ fun SideMenuContent() {
|
||||||
Text("Version 1.0.0 Alpha 12", color = Color.LightGray)
|
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.padding(horizontal = 16.dp, vertical = 24.dp)) {
|
||||||
Column(Modifier.height(ItemHeight), verticalArrangement = Arrangement.Center) {
|
Column(Modifier.height(ItemHeight), verticalArrangement = Arrangement.Center) {
|
||||||
|
@ -110,7 +111,7 @@ fun SideMenuContent() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounts.isNotEmpty()) {
|
if (accounts.isNotEmpty()) {
|
||||||
Divider(color = Colors.DrawerDivider)
|
ItemDivider(color = Colors.DrawerDivider)
|
||||||
|
|
||||||
Column(Modifier.padding(16.dp)) {
|
Column(Modifier.padding(16.dp)) {
|
||||||
UiSettings(Modifier.fillMaxWidth().padding(bottom = VerticalSpacing), textColor)
|
UiSettings(Modifier.fillMaxWidth().padding(bottom = VerticalSpacing), textColor)
|
||||||
|
|
|
@ -86,7 +86,7 @@ fun NavigationMenuItem(
|
||||||
if (balance != null) {
|
if (balance != null) {
|
||||||
Text(
|
Text(
|
||||||
formatUtil.formatAmount(balance, calculator.getTransactionsCurrency(emptyList())),
|
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)
|
modifier = Modifier.padding(start = 4.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,17 +59,24 @@ fun StateHandler(uiState: UiState, snackbarHostState: SnackbarHostState) {
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
uiState.transactionsRetrievedEvents.collect { event ->
|
uiState.transactionsRetrievedEvents.collect { event ->
|
||||||
val messagePrefix = if (event.newTransactions.isEmpty()) {
|
var actionLabel = "Coolio"
|
||||||
"Keine neuen Umsätze"
|
|
||||||
} else if (event.newTransactions.size == 1) {
|
val messagePrefix = if (event.newTransactions.size == 1) {
|
||||||
"1 Umsatz"
|
"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 {
|
} else {
|
||||||
"${event.newTransactions.size} Umsätze"
|
actionLabel = "Na super"
|
||||||
|
"Keine neuen Umsätze"
|
||||||
}
|
}
|
||||||
|
|
||||||
snackbarHostState.showSnackbar(
|
snackbarHostState.showSnackbar(
|
||||||
message = "$messagePrefix für ${event.user.displayName} ${event.account.displayName}",
|
message = "$messagePrefix für ${event.user.displayName} ${event.account.displayName}",
|
||||||
actionLabel = "Coolio",
|
actionLabel = actionLabel,
|
||||||
duration = SnackbarDuration.Long
|
duration = SnackbarDuration.Long
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ 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.dataaccess.entities.UserEntity
|
import net.codinux.banking.dataaccess.entities.UserEntity
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
import net.codinux.banking.ui.forms.RoundedCornersCard
|
import net.codinux.banking.ui.forms.RoundedCornersCard
|
||||||
|
@ -28,6 +29,7 @@ private val formatUtil = DI.formatUtil
|
||||||
fun GroupedTransactionsListItems(
|
fun GroupedTransactionsListItems(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
transactionsToDisplay: List<AccountTransactionViewModel>,
|
transactionsToDisplay: List<AccountTransactionViewModel>,
|
||||||
|
holdingsToDisplay: List<Holding>,
|
||||||
usersById: Map<Long, UserEntity>,
|
usersById: Map<Long, UserEntity>,
|
||||||
transactionsGrouping: TransactionsGrouping
|
transactionsGrouping: TransactionsGrouping
|
||||||
) {
|
) {
|
||||||
|
@ -40,6 +42,27 @@ fun GroupedTransactionsListItems(
|
||||||
val showColoredAmounts by DI.uiSettings.showColoredAmounts.collectAsState()
|
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)
|
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 ->
|
items(groupedByDate.keys.sortedDescending()) { groupingDate ->
|
||||||
Column(Modifier.fillMaxWidth()) {
|
Column(Modifier.fillMaxWidth()) {
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.codinux.banking.client.model.User
|
import net.codinux.banking.client.model.User
|
||||||
import net.codinux.banking.ui.composables.BankIcon
|
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.Colors
|
||||||
import net.codinux.banking.ui.config.DI
|
import net.codinux.banking.ui.config.DI
|
||||||
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
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 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
|
val bottomPadding = 56.dp
|
||||||
|
|
||||||
|
@ -128,6 +129,6 @@ fun TransactionListItem(user: User?, transaction: AccountTransactionViewModel, i
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemIndex < countItems - 1) {
|
if (itemIndex < countItems - 1) {
|
||||||
Divider(color = Colors.Zinc200, thickness = 1.dp)
|
ItemDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -38,6 +38,13 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
|
||||||
derivedStateOf { filterService.filterAccounts(transactions, transactionsFilter) }
|
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 showBalance by uiSettings.showBalance.collectAsState()
|
||||||
|
|
||||||
val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState()
|
val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState()
|
||||||
|
@ -59,9 +66,15 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactionsGrouping != TransactionsGrouping.None) {
|
if (transactionsGrouping != TransactionsGrouping.None) {
|
||||||
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, usersById, transactionsGrouping)
|
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, holdingsToDisplay, usersById, transactionsGrouping)
|
||||||
} 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 ->
|
||||||
|
// key(holding.isin) {
|
||||||
|
HoldingListItem(holding, index % 2 == 1, index < holdingsToDisplay.size - 1)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
itemsIndexed(transactionsToDisplay) { index, transaction ->
|
itemsIndexed(transactionsToDisplay) { index, transaction ->
|
||||||
key(transaction.id) {
|
key(transaction.id) {
|
||||||
TransactionListItem(usersById[transaction.userId], transaction, index, transactionsToDisplay.size)
|
TransactionListItem(usersById[transaction.userId], transaction, index, transactionsToDisplay.size)
|
||||||
|
|
|
@ -52,4 +52,9 @@ object Colors {
|
||||||
|
|
||||||
val Emerald700 = Color(4, 120, 87)
|
val Emerald700 = Color(4, 120, 87)
|
||||||
|
|
||||||
|
|
||||||
|
val ZebraStripesColor = Zinc100_50
|
||||||
|
|
||||||
|
val ItemDividerColor = Colors.Zinc200
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@ package net.codinux.banking.ui.config
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
object Style {
|
object Style {
|
||||||
|
@ -12,4 +13,7 @@ object Style {
|
||||||
|
|
||||||
val HeaderFontWeight: FontWeight = FontWeight.Bold
|
val HeaderFontWeight: FontWeight = FontWeight.Bold
|
||||||
|
|
||||||
|
|
||||||
|
val DividerThickness = 1.dp
|
||||||
|
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import net.codinux.banking.ui.config.Colors
|
import net.codinux.banking.ui.composables.text.ItemDivider
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -125,7 +125,7 @@ fun <T> AutocompleteTextField(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showDividersBetweenItems && index < suggestions.size - 1) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,12 @@ package net.codinux.banking.ui.model.events
|
||||||
|
|
||||||
import net.codinux.banking.client.model.BankAccount
|
import net.codinux.banking.client.model.BankAccount
|
||||||
import net.codinux.banking.client.model.User
|
import net.codinux.banking.client.model.User
|
||||||
|
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||||
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
import net.codinux.banking.ui.model.AccountTransactionViewModel
|
||||||
|
|
||||||
data class AccountTransactionsRetrievedEvent(
|
data class AccountTransactionsRetrievedEvent(
|
||||||
val user: User,
|
val user: User,
|
||||||
val account: BankAccount,
|
val account: BankAccount,
|
||||||
val newTransactions: List<AccountTransactionViewModel>
|
val newTransactions: List<AccountTransactionViewModel>,
|
||||||
|
val updatedHoldings: List<Holding> = emptyList()
|
||||||
)
|
)
|
|
@ -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.GetAccountDataRequest
|
||||||
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
import net.codinux.banking.client.model.request.TransferMoneyRequestForUser
|
||||||
import net.codinux.banking.client.model.response.*
|
import net.codinux.banking.client.model.response.*
|
||||||
|
import net.codinux.banking.client.model.securitiesaccount.Holding
|
||||||
import net.codinux.banking.client.service.BankingModelService
|
import net.codinux.banking.client.service.BankingModelService
|
||||||
import net.codinux.banking.dataaccess.BankingRepository
|
import net.codinux.banking.dataaccess.BankingRepository
|
||||||
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
import net.codinux.banking.dataaccess.entities.AccountTransactionEntity
|
||||||
|
@ -114,6 +115,7 @@ class BankingService(
|
||||||
uiState.users.value = users
|
uiState.users.value = users
|
||||||
|
|
||||||
updateTransactionsInUi(newUserEntity.accounts.flatMap { it.bookedTransactionsEntities })
|
updateTransactionsInUi(newUserEntity.accounts.flatMap { it.bookedTransactionsEntities })
|
||||||
|
updateHoldingsInUi(newUserEntity.accounts.flatMap { it.holdings })
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not save user account ${response.user}" }
|
log.error(e) { "Could not save user account ${response.user}" }
|
||||||
}
|
}
|
||||||
|
@ -171,8 +173,10 @@ class BankingService(
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateHoldingsInUi(response.holdings)
|
||||||
|
|
||||||
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)
|
val transactionsViewModel = updateTransactionsInUi(newTransactionsEntities)
|
||||||
uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel))
|
uiState.dispatchNewTransactionsRetrieved(AccountTransactionsRetrievedEvent(user, account, transactionsViewModel, response.holdings))
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
log.error(e) { "Could not save updated account transactions for user $user" }
|
log.error(e) { "Could not save updated account transactions for user $user" }
|
||||||
|
@ -189,6 +193,12 @@ class BankingService(
|
||||||
return transactionsViewModel
|
return transactionsViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateHoldingsInUi(holdings: List<Holding>) {
|
||||||
|
val allHoldings = uiState.holdings.value.toMutableList()
|
||||||
|
allHoldings.addAll(holdings)
|
||||||
|
uiState.holdings.value = allHoldings.sortedByDescending { it.pricingTime }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
suspend fun transferMoney(user: UserEntity, account: BankAccountEntity,
|
suspend fun transferMoney(user: UserEntity, account: BankAccountEntity,
|
||||||
recipientName: String, recipientAccountIdentifier: String, amount: Amount, currency: String,
|
recipientName: String, recipientAccountIdentifier: String, amount: Amount, currency: String,
|
||||||
|
|
|
@ -55,23 +55,8 @@ class FormatUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun formatAmount(amount: Amount, currency: String): String { // TODO: find a better way
|
fun formatAmount(amount: Amount, currency: String): String {
|
||||||
val parts = amount.amount.split('.')
|
return "${formatToTwoDecimalPlaces(amount.amount)} ${formatCurrency(currency)}"
|
||||||
|
|
||||||
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 formatCurrency(currency: String): String = when (currency) {
|
fun formatCurrency(currency: String): String = when (currency) {
|
||||||
|
@ -85,6 +70,27 @@ class FormatUtil {
|
||||||
else -> Colors.Green600
|
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 =
|
fun minDigits(toConvert: Int, minDigits: Int): String =
|
||||||
toConvert.toString().padStart(minDigits, '0')
|
toConvert.toString().padStart(minDigits, '0')
|
||||||
|
|
|
@ -5,6 +5,7 @@ import androidx.compose.material.DrawerValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.EnterTanResult
|
||||||
import net.codinux.banking.client.model.tan.TanChallenge
|
import net.codinux.banking.client.model.tan.TanChallenge
|
||||||
import net.codinux.banking.dataaccess.entities.UserEntity
|
import net.codinux.banking.dataaccess.entities.UserEntity
|
||||||
|
@ -21,6 +22,8 @@ class UiState : ViewModel() {
|
||||||
|
|
||||||
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
|
val transactions = MutableStateFlow<List<AccountTransactionViewModel>>(emptyList())
|
||||||
|
|
||||||
|
val holdings = MutableStateFlow<List<Holding>>(emptyList())
|
||||||
|
|
||||||
val transactionsRetrievedEvents = MutableSharedFlow<AccountTransactionsRetrievedEvent>()
|
val transactionsRetrievedEvents = MutableSharedFlow<AccountTransactionsRetrievedEvent>()
|
||||||
|
|
||||||
suspend fun dispatchNewTransactionsRetrieved(event: AccountTransactionsRetrievedEvent) {
|
suspend fun dispatchNewTransactionsRetrieved(event: AccountTransactionsRetrievedEvent) {
|
||||||
|
|
Loading…
Reference in New Issue