Implemented different kinds of grouping the transactions

This commit is contained in:
dankito 2024-09-06 16:14:12 +02:00
parent 43bd89a047
commit d792384efc
10 changed files with 146 additions and 23 deletions

View File

@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.config.Internationalization
import net.codinux.banking.ui.forms.RoundedCornersCard
import net.codinux.banking.ui.forms.Select
@ -17,11 +18,11 @@ private val uiState = DI.uiState
private val uiSettings = DI.uiSettings
val labelsWidth = 60.dp
private val labelsWidth = 60.dp
val selectBoxesWidth = 154.dp
private val selectBoxesWidth = 154.dp
val horizontalPadding = 6.dp
private val horizontalPadding = 6.dp
@Composable
fun FilterBar() {
@ -29,6 +30,8 @@ fun FilterBar() {
val transactionsFilter = uiState.transactionsFilter.collectAsState()
val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState()
val filterService = DI.accountTransactionsFilterService
val years by remember(transactions) { derivedStateOf { filterService.getYearForWhichWeHaveTransactions(transactions.value).sorted() + listOf(null as? Int) } }
@ -43,6 +46,19 @@ fun FilterBar() {
Column(Modifier.height(166.dp).width(390.dp)) {
RoundedCornersCard(cornerSize = 4.dp, shadowElevation = 24.dp) {
Column(Modifier.fillMaxWidth().background(Color.White).padding(16.dp)) {
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Umsätze", Modifier.width(labelsWidth))
Select(
label = "Gruppieren",
items = TransactionsGrouping.entries,
selectedItem = transactionsGrouping,
onSelectedItemChanged = { grouping -> uiSettings.transactionsGrouping.value = grouping },
getItemDisplayText = { grouping -> Internationalization.translate(grouping) },
modifier = Modifier.width(selectBoxesWidth).padding(horizontal = horizontalPadding)
)
}
Row(Modifier.fillMaxWidth().padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically) {
Text("Zeitraum", Modifier.width(labelsWidth))

View File

@ -1,13 +1,19 @@
package net.codinux.banking.ui.composables.settings
import androidx.compose.foundation.layout.Column
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.unit.dp
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.config.Internationalization
import net.codinux.banking.ui.forms.BooleanOption
import net.codinux.banking.ui.forms.Select
import net.codinux.banking.ui.model.TransactionsGrouping
@Composable
fun UiSettings(modifier: Modifier, textColor: Color = Color.Unspecified) {
@ -15,7 +21,7 @@ fun UiSettings(modifier: Modifier, textColor: Color = Color.Unspecified) {
val showBalance by uiSettings.showBalance.collectAsState()
val groupTransactions by uiSettings.groupTransactions.collectAsState()
val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState()
val zebraStripes by uiSettings.zebraStripes.collectAsState()
@ -27,13 +33,25 @@ fun UiSettings(modifier: Modifier, textColor: Color = Color.Unspecified) {
Column(modifier) {
BooleanOption("Kontostand anzeigen", showBalance, textColor = textColor) { uiSettings.showBalance.value = it }
BooleanOption("Umsätze gruppieren", groupTransactions, textColor = textColor) { uiSettings.groupTransactions.value = it }
BooleanOption("Zebra Stripes", zebraStripes, textColor = textColor) { uiSettings.zebraStripes.value = it }
BooleanOption("Bank Icons anzeigen", showBankIcons, textColor = textColor) { uiSettings.showBankIcons.value = it }
BooleanOption("Umsätze farbig anzeigen", showColoredAmounts, textColor = textColor) { uiSettings.showColoredAmounts.value = it }
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Text("Umsätze gruppieren", color = textColor)
Select(
label = "",
items = TransactionsGrouping.entries,
selectedItem = transactionsGrouping,
onSelectedItemChanged = { grouping -> uiSettings.transactionsGrouping.value = grouping },
getItemDisplayText = { grouping -> Internationalization.translate(grouping) },
textColor = textColor,
modifier = Modifier.width(175.dp).padding(horizontal = 6.dp)
)
}
}
}

View File

@ -12,12 +12,13 @@ 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
import kotlinx.datetime.LocalDate
import net.codinux.banking.client.model.Amount
import net.codinux.banking.dataaccess.entities.UserAccountEntity
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.forms.RoundedCornersCard
import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.TransactionsGrouping
import net.codinux.banking.ui.service.TransactionsGroupingService
private val calculator = DI.calculator
@ -27,21 +28,23 @@ private val formatUtil = DI.formatUtil
fun GroupedTransactionsListItems(
modifier: Modifier,
transactionsToDisplay: List<AccountTransactionViewModel>,
userAccountsId: Map<Long, UserAccountEntity>
userAccountsId: Map<Long, UserAccountEntity>,
transactionsGrouping: TransactionsGrouping
) {
val groupingService = remember { TransactionsGroupingService() }
val groupedByMonth by remember(transactionsToDisplay) {
derivedStateOf { transactionsToDisplay.groupBy { LocalDate(it.valueDate.year, it.valueDate.monthNumber, 1) } }
val groupedByDate by remember(transactionsToDisplay, transactionsGrouping) {
derivedStateOf { transactionsToDisplay.groupBy { groupingService.getKeyForGroup(it, transactionsGrouping) } }
}
val showColoredAmounts by DI.uiSettings.showColoredAmounts.collectAsState()
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(groupedByMonth.keys.sortedDescending()) { month ->
items(groupedByDate.keys.sortedDescending()) { groupingDate ->
Column(Modifier.fillMaxWidth()) {
Text(
text = DI.formatUtil.formatMonth(month),
text = DI.formatUtil.formatGroupingDate(groupingDate, transactionsGrouping),
fontSize = 16.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.padding(top = 8.dp, bottom = 2.dp),
@ -49,8 +52,7 @@ fun GroupedTransactionsListItems(
Spacer(Modifier.height(4.dp))
val monthTransactions =
groupedByMonth[month].orEmpty().sortedByDescending { it.valueDate }
val monthTransactions = groupedByDate[groupingDate].orEmpty().sortedByDescending { it.valueDate }
RoundedCornersCard {
Column(Modifier.background(Color.White)) { // LazyColumn inside LazyColumn is not allowed

View File

@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.config.DI
import net.codinux.banking.ui.model.TransactionsGrouping
import net.codinux.banking.ui.settings.UiSettings
import net.codinux.banking.ui.state.UiState
import org.jetbrains.compose.ui.tooling.preview.Preview
@ -39,7 +40,7 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
val showBalance by uiSettings.showBalance.collectAsState()
val groupTransactions by uiSettings.groupTransactions.collectAsState()
val transactionsGrouping by uiSettings.transactionsGrouping.collectAsState()
val showColoredAmounts by DI.uiSettings.showColoredAmounts.collectAsState()
@ -57,8 +58,8 @@ fun TransactionsList(uiState: UiState, uiSettings: UiSettings, isMobile: Boolean
}
}
if (groupTransactions) {
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, userAccountsId)
if (transactionsGrouping != TransactionsGrouping.None) {
GroupedTransactionsListItems(transactionsListModifier, transactionsToDisplay, userAccountsId, transactionsGrouping)
} else {
LazyColumn(transactionsListModifier, contentPadding = PaddingValues(top = 8.dp, bottom = 16.dp)) {
itemsIndexed(transactionsToDisplay) { index, transaction ->

View File

@ -1,6 +1,7 @@
package net.codinux.banking.ui.config
import net.codinux.banking.client.model.tan.ActionRequiringTan
import net.codinux.banking.ui.model.TransactionsGrouping
object Internationalization {
@ -21,4 +22,12 @@ object Internationalization {
ActionRequiringTan.ChangeTanMedium -> "um das TAN Medium zu ändern"
}
fun translate(transactionsGrouping: TransactionsGrouping): String = when (transactionsGrouping) {
TransactionsGrouping.Quarter -> "Quartal"
TransactionsGrouping.Month -> "Monat"
TransactionsGrouping.Day -> "Tag"
TransactionsGrouping.Week -> "Woche"
TransactionsGrouping.None -> "Nicht gruppieren"
}
}

View File

@ -4,6 +4,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import net.codinux.banking.ui.config.Colors
@OptIn(ExperimentalMaterialApi::class)
@Composable
@ -14,6 +17,7 @@ fun <T> Select(
onSelectedItemChanged: (T) -> Unit,
getItemDisplayText: (T) -> String,
modifier: Modifier = Modifier,
textColor: Color? = null,
leadingIcon: @Composable (() -> Unit)? = null,
dropDownItemContent: @Composable ((T) -> Unit)? = null
) {
@ -24,11 +28,14 @@ fun <T> Select(
value = getItemDisplayText(selectedItem),
onValueChange = { },
modifier = Modifier.fillMaxWidth(),
label = { Text(label) },
textStyle = if (textColor != null) TextStyle(textColor) else LocalTextStyle.current,
label = { Text(label, color = textColor ?: Color.Unspecified) },
readOnly = true,
maxLines = 1,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(showDropDownMenu) },
leadingIcon = leadingIcon
leadingIcon = leadingIcon,
colors = if (textColor == null) TextFieldDefaults.outlinedTextFieldColors()
else TextFieldDefaults.outlinedTextFieldColors(textColor = textColor, unfocusedBorderColor = textColor, unfocusedLabelColor = textColor, placeholderColor = textColor, focusedBorderColor = Colors.CodinuxSecondaryColor)
)
// due to a bug (still not fixed since 2021) in ExposedDropdownMenu its popup has a maximum width of 800 pixel / 320dp which is too less to fit

View File

@ -0,0 +1,9 @@
package net.codinux.banking.ui.model
enum class TransactionsGrouping {
None,
Day,
Week,
Month,
Quarter
}

View File

@ -1,16 +1,40 @@
package net.codinux.banking.ui.service
import androidx.compose.ui.graphics.Color
import kotlinx.datetime.LocalDate
import kotlinx.datetime.Month
import kotlinx.datetime.*
import net.codinux.banking.client.model.Amount
import net.codinux.banking.ui.config.Colors
import net.codinux.banking.ui.model.TransactionsGrouping
class FormatUtil {
fun formatDate(date: LocalDate): String = // TODO: find a better way
"${minDigits(date.dayOfMonth, 2)}.${minDigits(date.monthNumber, 2)}.${date.year.toString().substring(2)}"
fun formatGroupingDate(date: LocalDate, transactionsGrouping: TransactionsGrouping): String = when (transactionsGrouping) {
TransactionsGrouping.Day -> formatDate(date)
TransactionsGrouping.Week -> formatWeek(date)
TransactionsGrouping.Month -> formatMonth(date)
TransactionsGrouping.Quarter -> {
val quarter = when (date.monthNumber) {
1 -> "1"
4 -> "2"
7 -> "3"
else -> "4"
}
"${quarter}. Quartal ${date.year}"
}
TransactionsGrouping.None -> "" // illegal state
}
fun formatWeek(date: LocalDate): String {
// not fully correct, just for a proof of concept
val calenderWeek = date.dayOfYear / 7 + 1
val endOfWeek = date.plus(6, DateTimeUnit.DAY)
return "KW ${minDigits(calenderWeek, 2)}, ${minDigits(date.dayOfMonth, 2)}.${minDigits(date.monthNumber, 2)} - ${formatDate(endOfWeek)}"
}
fun formatMonth(date: LocalDate): String = // TODO: find a better way
"${getMonthName(date.month)} ${date.year}"

View File

@ -0,0 +1,36 @@
package net.codinux.banking.ui.service
import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.LocalDate
import kotlinx.datetime.Month
import net.codinux.banking.fints.extensions.minusDays
import net.codinux.banking.ui.model.AccountTransactionViewModel
import net.codinux.banking.ui.model.TransactionsGrouping
class TransactionsGroupingService {
fun getQuarter(date: LocalDate): Int = when (date.month) {
Month.JANUARY, Month.FEBRUARY, Month.MARCH -> 1
Month.APRIL, Month.MAY, Month.JUNE -> 4
Month.JULY, Month.AUGUST, Month.SEPTEMBER -> 7
else -> 10
}
fun getStartOfWeek(date: LocalDate): LocalDate = when (date.dayOfWeek) {
DayOfWeek.TUESDAY -> date.minusDays(1)
DayOfWeek.WEDNESDAY -> date.minusDays(2)
DayOfWeek.THURSDAY -> date.minusDays(3)
DayOfWeek.FRIDAY -> date.minusDays(4)
DayOfWeek.SATURDAY -> date.minusDays(5)
DayOfWeek.SUNDAY -> date.minusDays(6)
else -> date
}
fun getKeyForGroup(transaction: AccountTransactionViewModel, grouping: TransactionsGrouping) = when (grouping) {
TransactionsGrouping.Quarter -> LocalDate(transaction.valueDate.year, getQuarter(transaction.valueDate), 1)
TransactionsGrouping.Month -> LocalDate(transaction.valueDate.year, transaction.valueDate.monthNumber, 1)
TransactionsGrouping.Week -> getStartOfWeek(transaction.valueDate)
else -> transaction.valueDate
}
}

View File

@ -2,12 +2,13 @@ package net.codinux.banking.ui.settings
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import net.codinux.banking.ui.model.TransactionsGrouping
class UiSettings : ViewModel() {
val showBalance = MutableStateFlow(true)
val groupTransactions = MutableStateFlow(true)
val transactionsGrouping = MutableStateFlow(TransactionsGrouping.Month)
val zebraStripes = MutableStateFlow(true)