From 120a26d6c351052e9d6028ebe558f29fc42ee2b0 Mon Sep 17 00:00:00 2001 From: Vishwa Raghavendra K S Date: Tue, 3 May 2022 17:59:56 +0530 Subject: [PATCH] PieChartScreen Refactor --- .../domain/action/charts/PieChartAct.kt | 17 +- .../ui/statistic/level1/CategoryAmount.kt | 3 +- .../level1/PieChartStatisticScreen.kt | 187 +++------- .../level1/PieChartStatisticViewModel.kt | 352 +++++++----------- 4 files changed, 207 insertions(+), 352 deletions(-) diff --git a/app/src/main/java/com/ivy/wallet/domain/action/charts/PieChartAct.kt b/app/src/main/java/com/ivy/wallet/domain/action/charts/PieChartAct.kt index e0ce53d101..35c668ff7a 100644 --- a/app/src/main/java/com/ivy/wallet/domain/action/charts/PieChartAct.kt +++ b/app/src/main/java/com/ivy/wallet/domain/action/charts/PieChartAct.kt @@ -82,7 +82,7 @@ class PieChartAct @Inject constructor( ) } then { addAccountTransfersCategory( - treatTransferAsIncExp = treatTransferAsIncExp, + showAccountTransfersCategory = showAccountTransfersCategory, type = type, accountTransfersCategory = accountTransfersCategory, accountIdFilterSet = accountIdFilterList.toHashSet(), @@ -172,7 +172,8 @@ class PieChartAct @Inject constructor( TransactionType.EXPENSE -> catIncomeExpense.expense.toDouble() else -> error("not supported transactionType - $type") }, - associatedTransactions = categoryTransactions.await() + associatedTransactions = categoryTransactions.await(), + isCategoryUnspecified = category == null ) } thenFilter { catAmt -> catAmt.amount != 0.0 @@ -213,7 +214,7 @@ class PieChartAct @Inject constructor( @Pure private suspend fun addAccountTransfersCategory( - treatTransferAsIncExp: Boolean, + showAccountTransfersCategory: Boolean, type: TransactionType, accountTransfersCategory: Category, accountIdFilterSet: Set, @@ -231,7 +232,7 @@ class PieChartAct @Inject constructor( val incExpQuad = incomeExpenseTransfer() val catAmtList = - if (!treatTransferAsIncExp || incExpQuad.transferIncome == BigDecimal.ZERO && incExpQuad.transferExpense == BigDecimal.ZERO) + if (!showAccountTransfersCategory || incExpQuad.transferIncome == BigDecimal.ZERO && incExpQuad.transferExpense == BigDecimal.ZERO) categoryAmounts then { it.sortedByDescending { ca -> ca.amount } } else { @@ -251,7 +252,12 @@ class PieChartAct @Inject constructor( categoryAmounts then { it.plus( - CategoryAmount(accountTransfersCategory, amt, categoryTrans) + CategoryAmount( + category = accountTransfersCategory, + amount = amt, + associatedTransactions = categoryTrans, + isCategoryUnspecified = true + ) ) } then { it.sortedByDescending { ca -> ca.amount } @@ -267,6 +273,7 @@ class PieChartAct @Inject constructor( val type: TransactionType, val accountIdFilterList: List, val treatTransferAsIncExp: Boolean = false, + val showAccountTransfersCategory: Boolean = treatTransferAsIncExp, val existingTransactions: List = emptyList() ) diff --git a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/CategoryAmount.kt b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/CategoryAmount.kt index 3f8c39e9ac..d11a3faf7a 100644 --- a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/CategoryAmount.kt +++ b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/CategoryAmount.kt @@ -6,5 +6,6 @@ import com.ivy.wallet.domain.data.core.Transaction data class CategoryAmount( val category: Category?, val amount: Double, - val associatedTransactions: List = emptyList() + val associatedTransactions: List = emptyList(), + val isCategoryUnspecified: Boolean = false ) \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt index 86b43b7aa7..2801de6c31 100644 --- a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt @@ -10,7 +10,9 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.Text -import androidx.compose.runtime.* +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.draw.alpha @@ -32,16 +34,13 @@ import com.ivy.design.l0_system.style import com.ivy.wallet.R import com.ivy.wallet.domain.data.TransactionType import com.ivy.wallet.domain.data.core.Category -import com.ivy.wallet.domain.data.core.Transaction import com.ivy.wallet.ui.* import com.ivy.wallet.ui.onboarding.model.TimePeriod import com.ivy.wallet.ui.theme.* import com.ivy.wallet.ui.theme.components.* import com.ivy.wallet.ui.theme.modal.ChoosePeriodModal -import com.ivy.wallet.ui.theme.modal.ChoosePeriodModalData import com.ivy.wallet.ui.theme.wallet.AmountCurrencyB1Row import com.ivy.wallet.utils.* -import java.util.* @ExperimentalFoundationApi @Composable @@ -49,73 +48,31 @@ fun BoxWithConstraintsScope.PieChartStatisticScreen( screen: PieChartStatistic ) { val viewModel: PieChartStatisticViewModel = viewModel() - - val ivyContext = ivyWalletCtx() - - val type by viewModel.type.collectAsState() - val period by viewModel.period.collectAsState() - val currency by viewModel.baseCurrencyCode.collectAsState() - val totalAmount by viewModel.totalAmount.collectAsState() - val categoryAmounts by viewModel.categoryAmounts.collectAsState() - val selectedCategory by viewModel.selectedCategory.collectAsState() - val accountIdList by viewModel.accountIdFilterList.collectAsState() - val showCloseButtonOnly by viewModel.showCloseButtonOnly.collectAsState() - val transactions by viewModel.transaction.collectAsState() + val state by viewModel.state().collectAsState() onScreenStart { viewModel.start(screen) } UI( - transactionType = type, - period = period, - currency = currency, - totalAmount = totalAmount, - categoryAmounts = categoryAmounts, - selectedCategory = selectedCategory, - accountIdFilterList = accountIdList, - showCloseButtonOnly = showCloseButtonOnly, - transactions = transactions, - - onSetPeriod = viewModel::onSetPeriod, - onSelectNextMonth = viewModel::nextMonth, - onSelectPreviousMonth = viewModel::previousMonth, - onSetSelectedCategory = viewModel::setSelectedCategory, - checkForUnSpecifiedCategory = viewModel::checkForUnspecifiedCategory + state = state, + onEventHandler = viewModel::onEvent ) } @ExperimentalFoundationApi @Composable private fun BoxWithConstraintsScope.UI( - transactionType: TransactionType, - period: TimePeriod, - currency: String, - totalAmount: Double, - categoryAmounts: List, - selectedCategory: SelectedCategory?, - accountIdFilterList: List = emptyList(), - showCloseButtonOnly: Boolean = false, - transactions: List = emptyList(), - - onSelectNextMonth: () -> Unit = {}, - onSelectPreviousMonth: () -> Unit = {}, - onSetPeriod: (TimePeriod) -> Unit = {}, - onSetSelectedCategory: (SelectedCategory?) -> Unit = {}, - checkForUnSpecifiedCategory: (Category?) -> Boolean, + state: PieChartStatisticState = PieChartStatisticState(), + onEventHandler: (PieChartStatisticEvent) -> Unit = {} ) { - var choosePeriodModal: ChoosePeriodModalData? by remember { - mutableStateOf(null) - } - + val nav = navigation() val lazyState = rememberLazyListState() - val expanded = lazyState.firstVisibleItemIndex < 1 val percentExpanded by animateFloatAsState( targetValue = if (expanded) 1f else 0f, animationSpec = springBounce() ) - val nav = navigation() LazyColumn( modifier = Modifier @@ -125,21 +82,23 @@ private fun BoxWithConstraintsScope.UI( ) { stickyHeader { Header( - transactionType = transactionType, - period = period, + transactionType = state.transactionType, + period = state.period, percentExpanded = percentExpanded, - currency = currency, - amount = totalAmount, + currency = state.baseCurrency, + amount = state.totalAmount, onShowMonthModal = { - choosePeriodModal = ChoosePeriodModalData( - period = period - ) + onEventHandler.invoke(PieChartStatisticEvent.OnShowMonthModal(state.period)) + }, + onSelectNextMonth = { + onEventHandler.invoke(PieChartStatisticEvent.OnSelectNextMonth) }, - onSelectNextMonth = onSelectNextMonth, - onSelectPreviousMonth = onSelectPreviousMonth, - showCloseButtonOnly = showCloseButtonOnly, + onSelectPreviousMonth = { + onEventHandler.invoke(PieChartStatisticEvent.OnSelectPreviousMonth) + }, + showCloseButtonOnly = state.showCloseButtonOnly, onClose = { nav.back() @@ -160,7 +119,7 @@ private fun BoxWithConstraintsScope.UI( Text( modifier = Modifier.padding(start = 32.dp), - text = if (transactionType == TransactionType.EXPENSE) stringResource(R.string.expenses) else stringResource( + text = if (state.transactionType == TransactionType.EXPENSE) stringResource(R.string.expenses) else stringResource( R.string.income ), style = UI.typo.b1.style( @@ -172,8 +131,8 @@ private fun BoxWithConstraintsScope.UI( modifier = Modifier .padding(start = 32.dp, end = 16.dp) .alpha(percentExpanded), - currency = currency, - balance = totalAmount, + currency = state.baseCurrency, + balance = state.totalAmount, currencyUpfront = false, currencyFontSize = 30.sp ) @@ -183,31 +142,11 @@ private fun BoxWithConstraintsScope.UI( Spacer(Modifier.height(40.dp)) PieChart( - type = transactionType, - categoryAmounts = categoryAmounts, - selectedCategory = selectedCategory, + type = state.transactionType, + categoryAmounts = state.categoryAmounts, + selectedCategory = state.selectedCategory, onCategoryClicked = { clickedCategory -> - //null - Unspecified - if (selectedCategory == null) { - //no category selected, select the click one - selectCategory( - categoryToSelect = clickedCategory, - setSelectedCategory = onSetSelectedCategory - ) - } else { - //category selected - val currentlySelected = selectedCategory.category - if (currentlySelected == clickedCategory) { - //deselect clicked category - onSetSelectedCategory(null) - } else { - //select new category - selectCategory( - categoryToSelect = clickedCategory, - setSelectedCategory = onSetSelectedCategory - ) - } - } + onEventHandler.invoke(PieChartStatisticEvent.OnCategoryClicked(clickedCategory)) } ) @@ -215,7 +154,7 @@ private fun BoxWithConstraintsScope.UI( } itemsIndexed( - items = categoryAmounts + items = state.categoryAmounts ) { index, item -> if (item.amount != 0.0) { if (index != 0) { @@ -224,25 +163,17 @@ private fun BoxWithConstraintsScope.UI( CategoryAmountCard( categoryAmount = item, - currency = currency, - totalAmount = totalAmount, - selectedCategory = selectedCategory + currency = state.baseCurrency, + totalAmount = state.totalAmount, + selectedCategory = state.selectedCategory ) { nav.navigateTo( - if (transactions.isEmpty() && item.associatedTransactions.isEmpty()) - ItemStatistic( - categoryId = item.category?.id, - unspecifiedCategory = checkForUnSpecifiedCategory(item.category), - accountIdFilterList = accountIdFilterList - ) - else { - ItemStatistic( - categoryId = item.category?.id, - unspecifiedCategory = checkForUnSpecifiedCategory(item.category), - accountIdFilterList = accountIdFilterList, - transactions = item.associatedTransactions - ) - } + ItemStatistic( + categoryId = item.category?.id, + unspecifiedCategory = item.isCategoryUnspecified, + accountIdFilterList = state.accountIdFilterList, + transactions = item.associatedTransactions + ) ) } } @@ -254,27 +185,15 @@ private fun BoxWithConstraintsScope.UI( } ChoosePeriodModal( - modal = choosePeriodModal, + modal = state.choosePeriodModal, dismiss = { - choosePeriodModal = null + onEventHandler.invoke(PieChartStatisticEvent.OnShowMonthModal(null)) } ) { - onSetPeriod(it) + onEventHandler.invoke(PieChartStatisticEvent.OnSetPeriod(it)) } } -private fun selectCategory( - categoryToSelect: Category?, - - setSelectedCategory: (SelectedCategory?) -> Unit -) { - setSelectedCategory( - SelectedCategory( - category = categoryToSelect - ) - ) -} - @Composable private fun Header( transactionType: TransactionType, @@ -487,12 +406,12 @@ private fun PercentText( @Composable private fun Preview_Expense() { IvyWalletPreview { - UI( + val state = PieChartStatisticState( transactionType = TransactionType.EXPENSE, period = TimePeriod.currentMonth( startDayOfMonth = 1 ), //preview - currency = "BGN", + baseCurrency = "BGN", totalAmount = 1828.0, categoryAmounts = listOf( CategoryAmount( @@ -501,7 +420,8 @@ private fun Preview_Expense() { ), CategoryAmount( category = null, - amount = 497.0 + amount = 497.0, + isCategoryUnspecified = true ), CategoryAmount( category = Category("Shisha", Orange.toArgb(), icon = "trees"), @@ -528,9 +448,10 @@ private fun Preview_Expense() { amount = 2.0 ), ), - selectedCategory = null, - checkForUnSpecifiedCategory = { false } + selectedCategory = null ) + + UI(state = state) } } @@ -539,12 +460,12 @@ private fun Preview_Expense() { @Composable private fun Preview_Income() { IvyWalletPreview { - UI( + val state = PieChartStatisticState( transactionType = TransactionType.INCOME, period = TimePeriod.currentMonth( startDayOfMonth = 1 ), //preview - currency = "BGN", + baseCurrency = "BGN", totalAmount = 1828.0, categoryAmounts = listOf( CategoryAmount( @@ -553,7 +474,8 @@ private fun Preview_Income() { ), CategoryAmount( category = null, - amount = 497.0 + amount = 497.0, + isCategoryUnspecified = true ), CategoryAmount( category = Category("Shisha", Orange.toArgb(), icon = "trees"), @@ -580,9 +502,10 @@ private fun Preview_Income() { amount = 2.0 ), ), - selectedCategory = null, - checkForUnSpecifiedCategory = { false } + selectedCategory = null ) + + UI(state = state) } } diff --git a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticViewModel.kt b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticViewModel.kt index f7b4e05610..df1c58a42d 100644 --- a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticViewModel.kt +++ b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticViewModel.kt @@ -1,34 +1,21 @@ package com.ivy.wallet.ui.statistic.level1 -import androidx.compose.ui.graphics.toArgb -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ivy.wallet.R +import com.ivy.fp.viewmodel.IvyViewModel import com.ivy.wallet.domain.action.charts.PieChartAct import com.ivy.wallet.domain.data.TransactionType import com.ivy.wallet.domain.data.core.Category import com.ivy.wallet.domain.data.core.Transaction -import com.ivy.wallet.domain.deprecated.logic.currency.ExchangeRatesLogic -import com.ivy.wallet.domain.deprecated.logic.currency.sumInBaseCurrency -import com.ivy.wallet.domain.pure.data.WalletDAOs import com.ivy.wallet.io.persistence.SharedPrefs -import com.ivy.wallet.io.persistence.dao.CategoryDao import com.ivy.wallet.io.persistence.dao.SettingsDao -import com.ivy.wallet.io.persistence.dao.TransactionDao -import com.ivy.wallet.stringRes import com.ivy.wallet.ui.IvyWalletCtx import com.ivy.wallet.ui.PieChartStatistic -import com.ivy.wallet.ui.onboarding.model.FromToTimeRange import com.ivy.wallet.ui.onboarding.model.TimePeriod -import com.ivy.wallet.ui.theme.IvyLight +import com.ivy.wallet.ui.theme.modal.ChoosePeriodModalData import com.ivy.wallet.utils.dateNowUTC import com.ivy.wallet.utils.ioThread -import com.ivy.wallet.utils.readOnly -import com.ivy.wallet.utils.scopedIOThread import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import java.util.* @@ -36,62 +23,21 @@ import javax.inject.Inject @HiltViewModel class PieChartStatisticViewModel @Inject constructor( - private val walletDAOs: WalletDAOs, - private val categoryDao: CategoryDao, private val settingsDao: SettingsDao, - private val transactionDao: TransactionDao, - private val exchangeRatesLogic: ExchangeRatesLogic, private val ivyContext: IvyWalletCtx, private val pieChartAct: PieChartAct, private val sharedPrefs: SharedPrefs -) : ViewModel() { - private val _period = MutableStateFlow(ivyContext.selectedPeriod) - val period = _period.readOnly() +) : IvyViewModel() { - private val _type = MutableStateFlow(TransactionType.EXPENSE) - val type = _type.readOnly() - - private val _baseCurrencyCode = MutableStateFlow("") - val baseCurrencyCode = _baseCurrencyCode.readOnly() - - private val _totalAmount = MutableStateFlow(0.0) - val totalAmount = _totalAmount.readOnly() - - private val _categoryAmounts = MutableStateFlow>(emptyList()) - val categoryAmounts = _categoryAmounts.readOnly() - - private val _selectedCategory = MutableStateFlow(null) - val selectedCategory = _selectedCategory.readOnly() - - private val _accountIdFilterList = MutableStateFlow>(emptyList()) - val accountIdFilterList = _accountIdFilterList.readOnly() - - private val _showCloseButtonOnly = MutableStateFlow(false) - val showCloseButtonOnly = _showCloseButtonOnly.readOnly() - - private val _transactions = MutableStateFlow>(emptyList()) - val transaction = _transactions.readOnly() - - private var filterExcluded = true - private val transfersCategory = - Category( - stringRes(R.string.account_transfers), - color = IvyLight.toArgb(), - icon = "transfer" - ) + override val mutableState: MutableStateFlow = MutableStateFlow( + PieChartStatisticState() + ) fun start( screen: PieChartStatistic ) { - if (screen.transactions.isEmpty()) { - initPieChartNormally( - period = ivyContext.selectedPeriod, - type = screen.type, - accountList = screen.accountList, - filterExcluded = screen.filterExcluded - ) - } else { - initPieChartFormTransactions( + viewModelScope.launch(Dispatchers.Default) { + startInternally( period = ivyContext.selectedPeriod, type = screen.type, accountIdFilterList = screen.accountList, @@ -101,206 +47,184 @@ class PieChartStatisticViewModel @Inject constructor( } } - private fun initPieChartNormally( + private suspend fun startInternally( period: TimePeriod, type: TransactionType, - accountList: List, - filterExcluded: Boolean + accountIdFilterList: List, + filterExclude: Boolean, + transactions: List ) { - _showCloseButtonOnly.value = false - _accountIdFilterList.value = accountList - this.filterExcluded = filterExcluded - _transactions.value = emptyList() - - load( - period = period, - type = type, - accountIdFilterList = accountList - ) + initialise(period, type, accountIdFilterList, filterExclude, transactions) + load(period = period) } - private fun initPieChartFormTransactions( + private suspend fun initialise( period: TimePeriod, type: TransactionType, accountIdFilterList: List, filterExclude: Boolean, transactions: List ) { - viewModelScope.launch(Dispatchers.Default) { - _showCloseButtonOnly.value = true - _accountIdFilterList.value = accountIdFilterList - _baseCurrencyCode.value = ioThread { settingsDao.findFirst() }.currency - _transactions.value = transactions - _type.value = type - filterExcluded = filterExclude - - val accountTransfersCategoryAmount = - async { getAccountTransfersCategoryAmount(transactions, type, accountIdFilterList) } - - val catAmounts = scopedIOThread { scope -> - transactions.groupBy { it.categoryId }.map { mapEntry -> - scope.async { - val category = - if (mapEntry.key == null) null else categoryDao.findById(mapEntry.key!!) - - val trans = mapEntry.value.filter { it.type == type } - - val amount = trans.sumInBaseCurrency( - exchangeRatesLogic = exchangeRatesLogic, - settingsDao = settingsDao, - accountDao = walletDAOs.accountDao - ) - - CategoryAmount(category?.toDomain(), amount, trans) - } - }.awaitAll().sortedByDescending { it.amount } - } - - _totalAmount.value = catAmounts.sumOf { it.amount } - _categoryAmounts.value = listOf(accountTransfersCategoryAmount.await()) + catAmounts - } - } - - private suspend fun getAccountTransfersCategoryAmount( - transactions: List, - type: TransactionType, - accountIdFilterList: List - ): CategoryAmount { - - val accountTransferTrans = transactions.filter { - it.type == TransactionType.TRANSFER && it.categoryId == null - } - - //Converted to set for faster filtering - val accountIdFilterSet = accountIdFilterList.toHashSet() - - val categoryAmount = scopedIOThread { - - val trans = if (type == TransactionType.EXPENSE) - accountTransferTrans.filter { accountIdFilterSet.contains(it.accountId) } - else - accountTransferTrans.filter { accountIdFilterSet.contains(it.toAccountId) } - - val amt = trans.sumOf { - exchangeRatesLogic.toAmountBaseCurrency( - transaction = it, - baseCurrency = baseCurrencyCode.value, - accounts = walletDAOs.accountDao.findAll().map { it.toDomain() } - ) - } - CategoryAmount(transfersCategory, amt, trans) + val settings = ioThread { settingsDao.findFirst() } + val baseCurrency = settings.currency + + updateState { + it.copy( + period = period, + transactionType = type, + accountIdFilterList = accountIdFilterList, + filterExcluded = filterExclude, + transactions = transactions, + showCloseButtonOnly = transactions.isNotEmpty(), + baseCurrency = baseCurrency + ) } - return categoryAmount } - private fun load( - period: TimePeriod, - type: TransactionType, - accountIdFilterList: List + private suspend fun load( + period: TimePeriod ) { - - _period.value = period + val type = stateVal().transactionType + val accountIdFilterList = stateVal().accountIdFilterList + val transactions = stateVal().transactions + val baseCurrency = stateVal().baseCurrency val range = period.toRange(ivyContext.startDayOfMonth) - _type.value = type - - _selectedCategory.value = null - viewModelScope.launch(Dispatchers.IO) { - val settings = ioThread { settingsDao.findFirst() } - _baseCurrencyCode.value = settings.currency + //transactions.isEmpty() condition disables treating transfers as income/expenses in Reports Screen + val treatTransferAsIncExp = + sharedPrefs.getBoolean( + SharedPrefs.TRANSFERS_AS_INCOME_EXPENSE, + false + ) && accountIdFilterList.isNotEmpty() && transactions.isEmpty() - val treatTransferAsIncExp = - sharedPrefs.getBoolean( - SharedPrefs.TRANSFERS_AS_INCOME_EXPENSE, - false - ) && accountIdFilterList.isNotEmpty() - - val pieChartActOutput = pieChartAct( + val pieChartActOutput = ioThread { + pieChartAct( PieChartAct.Input( - baseCurrency = _baseCurrencyCode.value, + baseCurrency = baseCurrency, range = range, - type = _type.value, + type = type, accountIdFilterList = accountIdFilterList, - treatTransferAsIncExp = treatTransferAsIncExp + treatTransferAsIncExp = treatTransferAsIncExp, + existingTransactions = transactions, + showAccountTransfersCategory = accountIdFilterList.isNotEmpty() ) ) - - _totalAmount.value = pieChartActOutput.totalAmount - _categoryAmounts.value = pieChartActOutput.categoryAmounts } - } - fun setSelectedCategory(selectedCategory: SelectedCategory?) { - _selectedCategory.value = selectedCategory + val totalAmount = pieChartActOutput.totalAmount + val categoryAmounts = pieChartActOutput.categoryAmounts - val categoryAmounts = _categoryAmounts.value - _categoryAmounts.value = if (selectedCategory != null) { - categoryAmounts - .sortedByDescending { it.amount } - .sortedByDescending { - selectedCategory.category == it.category - } - } else { - categoryAmounts.sortedByDescending { - it.amount - } + updateState { + it.copy( + period = period, + totalAmount = totalAmount, + categoryAmounts = categoryAmounts, + selectedCategory = null + ) } } - fun onSetPeriod(period: TimePeriod) { + private suspend fun onSetPeriod(period: TimePeriod) { ivyContext.updateSelectedPeriodInMemory(period) load( - period = period, - type = type.value, - accountIdFilterList = accountIdFilterList.value + period = period ) } - fun nextMonth() { - val month = period.value.month - val year = period.value.year ?: dateNowUTC().year + private suspend fun nextMonth() { + val month = stateVal().period.month + val year = stateVal().period.year ?: dateNowUTC().year if (month != null) { load( - period = month.incrementMonthPeriod(ivyContext, 1L, year), - type = type.value, - accountIdFilterList = accountIdFilterList.value + period = month.incrementMonthPeriod(ivyContext, 1L, year) ) } } - fun previousMonth() { - val month = period.value.month - val year = period.value.year ?: dateNowUTC().year + private suspend fun previousMonth() { + val month = stateVal().period.month + val year = stateVal().period.year ?: dateNowUTC().year if (month != null) { load( - period = month.incrementMonthPeriod(ivyContext, -1L, year), - type = type.value, - accountIdFilterList = accountIdFilterList.value + period = month.incrementMonthPeriod(ivyContext, -1L, year) ) } } - private suspend fun getCategories( - fetchCategoriesFromTransactions: Boolean, - timeRange: FromToTimeRange - ): List { - return scopedIOThread { scope -> - if (fetchCategoriesFromTransactions) { - transactionDao.findAllBetween(timeRange.from(), timeRange.to()) - .map { it.toDomain() } - .filter { - it.categoryId != null - }.map { - scope.async { - categoryDao.findById(it.categoryId!!)?.toDomain() - } - }.awaitAll().filterNotNull().distinctBy { it.id } - } else - categoryDao.findAll().map { it.toDomain() } + private suspend fun configureMonthModal(timePeriod: TimePeriod?) { + val choosePeriodModalData = if (timePeriod != null) + ChoosePeriodModalData(period = timePeriod) + else + null + + updateState { + it.copy(choosePeriodModal = choosePeriodModalData) + } + } + + private suspend fun onCategoryClicked(clickedCategory: Category?) { + val selectedCategory = if (clickedCategory == stateVal().selectedCategory?.category) + null + else + SelectedCategory(category = clickedCategory) + + val existingCategoryAmounts = stateVal().categoryAmounts + val newCategoryAmounts = if (selectedCategory != null) { + existingCategoryAmounts + .sortedByDescending { it.amount } + .sortedByDescending { + selectedCategory.category == it.category + } + } else { + existingCategoryAmounts.sortedByDescending { + it.amount + } + } + + updateState { + it.copy( + selectedCategory = selectedCategory, + categoryAmounts = newCategoryAmounts + ) } } - fun checkForUnspecifiedCategory(category: Category?): Boolean { - return category == null || category == transfersCategory || category.name == stringRes(R.string.account_transfers) + fun onEvent(event: PieChartStatisticEvent) { + viewModelScope.launch(Dispatchers.Default) { + when (event) { + is PieChartStatisticEvent.OnSelectNextMonth -> nextMonth() + is PieChartStatisticEvent.OnSelectPreviousMonth -> previousMonth() + is PieChartStatisticEvent.OnSetPeriod -> onSetPeriod(event.timePeriod) + is PieChartStatisticEvent.OnShowMonthModal -> configureMonthModal(event.timePeriod) + is PieChartStatisticEvent.OnCategoryClicked -> onCategoryClicked(event.category) + } + } } +} + +data class PieChartStatisticState( + val transactionType: TransactionType = TransactionType.INCOME, + val period: TimePeriod = TimePeriod(), + val baseCurrency: String = "", + val totalAmount: Double = 0.0, + val categoryAmounts: List = emptyList(), + val selectedCategory: SelectedCategory? = null, + val accountIdFilterList: List = emptyList(), + val showCloseButtonOnly: Boolean = false, + val filterExcluded: Boolean = false, + val transactions: List = emptyList(), + val choosePeriodModal: ChoosePeriodModalData? = null +) + +sealed class PieChartStatisticEvent { + object OnSelectNextMonth : PieChartStatisticEvent() + + object OnSelectPreviousMonth : PieChartStatisticEvent() + + data class OnSetPeriod(val timePeriod: TimePeriod) : PieChartStatisticEvent() + + data class OnCategoryClicked(val category: Category?) : + PieChartStatisticEvent() + + data class OnShowMonthModal(val timePeriod: TimePeriod?) : PieChartStatisticEvent() } \ No newline at end of file