From d792384efc510898d90945a98e70e7304683604c Mon Sep 17 00:00:00 2001 From: dankito Date: Fri, 6 Sep 2024 16:14:12 +0200 Subject: [PATCH] Implemented different kinds of grouping the transactions --- .../banking/ui/appskeleton/FilterBar.kt | 22 ++++++++++-- .../ui/composables/settings/UiSettings.kt | 26 +++++++++++--- .../GroupedTransactionsListItems.kt | 18 +++++----- .../transactions/TransactionsList.kt | 7 ++-- .../banking/ui/config/Internationalization.kt | 9 +++++ .../net/codinux/banking/ui/forms/Select.kt | 11 ++++-- .../banking/ui/model/TransactionsGrouping.kt | 9 +++++ .../codinux/banking/ui/service/FormatUtil.kt | 28 +++++++++++++-- .../ui/service/TransactionsGroupingService.kt | 36 +++++++++++++++++++ .../codinux/banking/ui/settings/UiSettings.kt | 3 +- 10 files changed, 146 insertions(+), 23 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/TransactionsGrouping.kt create mode 100644 composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/TransactionsGroupingService.kt diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FilterBar.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FilterBar.kt index 0b6d381..2caa552 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FilterBar.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/appskeleton/FilterBar.kt @@ -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)) diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/settings/UiSettings.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/settings/UiSettings.kt index 420a657..baec99c 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/settings/UiSettings.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/composables/settings/UiSettings.kt @@ -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) + ) + } } } \ 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 1d008ee..ff646f8 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 @@ -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, - userAccountsId: Map + userAccountsId: Map, + 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 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 665f26e..15d85d0 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 @@ -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 -> diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt index ec2cfd5..76cf02c 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/config/Internationalization.kt @@ -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" + } + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/Select.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/Select.kt index 46d35ab..7c276d1 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/Select.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/forms/Select.kt @@ -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 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 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 diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/TransactionsGrouping.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/TransactionsGrouping.kt new file mode 100644 index 0000000..3a19578 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/model/TransactionsGrouping.kt @@ -0,0 +1,9 @@ +package net.codinux.banking.ui.model + +enum class TransactionsGrouping { + None, + Day, + Week, + Month, + Quarter +} \ No newline at end of file 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 a35af95..07a9b9d 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 @@ -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}" diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/TransactionsGroupingService.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/TransactionsGroupingService.kt new file mode 100644 index 0000000..1b10542 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/service/TransactionsGroupingService.kt @@ -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 + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/settings/UiSettings.kt b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/settings/UiSettings.kt index b78dfaa..a82d39f 100644 --- a/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/settings/UiSettings.kt +++ b/composeApp/src/commonMain/kotlin/net/codinux/banking/ui/settings/UiSettings.kt @@ -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)