Skip to content
This repository has been archived by the owner on Nov 5, 2024. It is now read-only.

Commit

Permalink
PieChart Improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Vishwa-Raghavendra committed Apr 27, 2022
1 parent e5ed4b4 commit bcca593
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import com.ivy.wallet.domain.action.transaction.CalcTrnsIncomeExpenseAct
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.domain.pure.data.IncomeExpensePair
import com.ivy.wallet.domain.pure.data.IncomeExpenseTransferPair
import javax.inject.Inject

class CategoryIncomeWithAccountFiltersAct @Inject constructor(
val calcTrnsIncomeExpenseAct: CalcTrnsIncomeExpenseAct
) : FPAction<CategoryIncomeWithAccountFiltersAct.Input, IncomeExpensePair>() {
private val calcTrnsIncomeExpenseAct: CalcTrnsIncomeExpenseAct
) : FPAction<CategoryIncomeWithAccountFiltersAct.Input, IncomeExpenseTransferPair>() {

override suspend fun Input.compose(): suspend () -> IncomeExpensePair = {
override suspend fun Input.compose(): suspend () -> IncomeExpenseTransferPair = {
val accountFilterSet = accountFilterList.map { it.id }.toHashSet()
transactions.filter {
it.categoryId == category?.id
Expand Down
227 changes: 180 additions & 47 deletions app/src/main/java/com/ivy/wallet/domain/action/charts/PieChartAct.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
package com.ivy.wallet.domain.action.charts

import androidx.compose.ui.graphics.toArgb
import com.ivy.fp.Pure
import com.ivy.fp.SideEffect
import com.ivy.fp.action.FPAction
import com.ivy.fp.action.then
import com.ivy.fp.action.thenFilter
import com.ivy.fp.action.thenMap
import com.ivy.wallet.R
import com.ivy.wallet.domain.action.account.AccountsAct
import com.ivy.wallet.domain.action.category.CategoriesAct
import com.ivy.wallet.domain.action.category.CategoryIncomeWithAccountFiltersAct
import com.ivy.wallet.domain.action.transaction.CalcTrnsIncomeExpenseAct
import com.ivy.wallet.domain.action.transaction.TrnsWithRangeAndAccFiltersAct
import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.domain.pure.account.filterExcluded
import com.ivy.wallet.domain.pure.data.IncomeExpenseTransferPair
import com.ivy.wallet.stringRes
import com.ivy.wallet.ui.onboarding.model.FromToTimeRange
import com.ivy.wallet.ui.statistic.level1.CategoryAmount
import com.ivy.wallet.ui.theme.RedLight
import java.math.BigDecimal
import java.util.*
import javax.inject.Inject

Expand All @@ -21,17 +34,15 @@ class PieChartAct @Inject constructor(
private val categoriesAct: CategoriesAct,
private val categoryIncomeWithAccountFiltersAct: CategoryIncomeWithAccountFiltersAct
) : FPAction<PieChartAct.Input, PieChartAct.Output>() {
override suspend fun Input.compose(): suspend () -> Output = suspend {
val allAccounts = accountsAct(Unit)
val accountsUsed = if (accountIdFilterList.isEmpty())
allAccounts.let(::filterExcluded)
else
accountIdFilterList.mapNotNull { accID ->
allAccounts.find { it.id == accID }
}
val accountIdFilterSet = accountsUsed.map { it.id }.toHashSet()

Pair(accountsUsed, accountIdFilterSet)
private val accountTransfersCategory =
Category(stringRes(R.string.account_transfers), RedLight.toArgb(), "transfer")

override suspend fun Input.compose(): suspend () -> Output = suspend {
getUsableAccounts(
accountIdFilterList = accountIdFilterList,
allAccounts = suspend { accountsAct(Unit) }
)
} then {
val accountsUsed = it.first
val accountIdFilterSet = it.second
Expand All @@ -48,60 +59,182 @@ class PieChartAct @Inject constructor(
val accountsUsed = it.first
val transactions = it.second

val totalAmount = asyncIo {
val incomeExpensePair = calcTrnsIncomeExpenseAct(
CalcTrnsIncomeExpenseAct.Input(
transactions = transactions,
accounts = accountsUsed,
val incomeExpenseTransfer = calcTrnsIncomeExpenseAct(
CalcTrnsIncomeExpenseAct.Input(
transactions = transactions,
accounts = accountsUsed,
baseCurrency = baseCurrency
)
)

val categoryAmounts = calculateCategoryAmounts(
type = type,
baseCurrency = baseCurrency,
allCategories = suspend {
categoriesAct(Unit).plus(null) //for unspecified
},
transactions = suspend { transactions },
accountsUsed = suspend { accountsUsed }
)

Pair(incomeExpenseTransfer, categoryAmounts)
} then {

val totalAmount = calculateTotalAmount(
type = type,
treatTransferAsIncExp = treatTransferAsIncExp,
incomeExpenseTransfer = suspend { it.first }
)

val catAmountList = addAccountTransfersCategory(
treatTransferAsIncExp = treatTransferAsIncExp,
type = type,
incomeExpenseTransfer = suspend { it.first },
accountTransfersCategory = accountTransfersCategory,
categoryAmounts = suspend { it.second }
)

Pair(totalAmount, catAmountList)
} then {
Output(it.first.toDouble(), it.second)
}

@Pure
private suspend fun getUsableAccounts(
accountIdFilterList: List<UUID>,

@SideEffect
allAccounts: suspend () -> List<Account>
): Pair<List<Account>, Set<UUID>> {

val accountsUsed = if (accountIdFilterList.isEmpty())
allAccounts then ::filterExcluded
else
allAccounts thenFilter {
accountIdFilterList.contains(it.id)
}

val accountsUsedIDSet = accountsUsed thenMap { it.id } then { it.toHashSet() }

return Pair(accountsUsed(), accountsUsedIDSet())
}

@Pure
private suspend fun calculateCategoryAmounts(
type: TransactionType,
baseCurrency: String,

@SideEffect
allCategories: suspend () -> List<Category?>,

@SideEffect
transactions: suspend () -> List<Transaction>,

@SideEffect
accountsUsed: suspend () -> List<Account>,
): List<CategoryAmount> {
val trans = transactions()
val accUsed = accountsUsed()

val catAmtList = allCategories thenMap { category ->
val catIncomeExpense = categoryIncomeWithAccountFiltersAct(
CategoryIncomeWithAccountFiltersAct.Input(
transactions = trans,
accountFilterList = accUsed,
category = category,
baseCurrency = baseCurrency
)
)

when (type) {
TransactionType.INCOME -> incomeExpensePair.income.toDouble()
TransactionType.EXPENSE -> incomeExpensePair.expense.toDouble()
else -> error("not supported transactionType - $type")
CategoryAmount(
category = category,
amount = when (type) {
TransactionType.INCOME -> catIncomeExpense.income.toDouble()
TransactionType.EXPENSE -> catIncomeExpense.expense.toDouble()
else -> error("not supported transactionType - $type")
}
)
} thenFilter { catAmt ->
catAmt.amount != 0.0
} then {
it.sortedByDescending { ca -> ca.amount }
}

return catAmtList()
}

@Pure
private suspend fun calculateTotalAmount(
type: TransactionType,
treatTransferAsIncExp: Boolean,

@SideEffect
incomeExpenseTransfer: suspend () -> IncomeExpenseTransferPair
): BigDecimal {
val incExpQuad = incomeExpenseTransfer()
return when (type) {
TransactionType.INCOME -> {
incExpQuad.income +
if (treatTransferAsIncExp)
incExpQuad.transferIncome
else
BigDecimal.ZERO
}
TransactionType.EXPENSE -> {
incExpQuad.expense +
if (treatTransferAsIncExp)
incExpQuad.transferExpense
else
BigDecimal.ZERO
}
else -> BigDecimal.ZERO
}
}

val categoryAmounts = asyncIo {
val categories = categoriesAct(Unit)
categories
.plus(null) //for unspecified
.map { category ->

val catIncomeExpense = categoryIncomeWithAccountFiltersAct(
CategoryIncomeWithAccountFiltersAct.Input(
transactions = transactions,
accountFilterList = accountsUsed,
category = category,
baseCurrency = baseCurrency
)
)
@Pure
private suspend fun addAccountTransfersCategory(
treatTransferAsIncExp: Boolean,
type: TransactionType,
accountTransfersCategory: Category,

@SideEffect
incomeExpenseTransfer: suspend () -> IncomeExpenseTransferPair,

@SideEffect
categoryAmounts: suspend () -> List<CategoryAmount>
): List<CategoryAmount> {

val incExpQuad = incomeExpenseTransfer()

CategoryAmount(
category = category,
amount = when (type) {
TransactionType.INCOME -> catIncomeExpense.income.toDouble()
TransactionType.EXPENSE -> catIncomeExpense.expense.toDouble()
else -> error("not supported transactionType - $type")
}
val catAmtList =
if (!treatTransferAsIncExp || incExpQuad.transferIncome == BigDecimal.ZERO && incExpQuad.transferExpense == BigDecimal.ZERO)
categoryAmounts then { it.sortedByDescending { ca -> ca.amount } }
else {

val amt = if (type == TransactionType.INCOME)
incExpQuad.transferIncome.toDouble()
else
incExpQuad.transferExpense.toDouble()


categoryAmounts then {
it.plus(
CategoryAmount(accountTransfersCategory, amt)
)
} then {
it.sortedByDescending { ca -> ca.amount }
}
.filter { catAmt ->
catAmt.amount != 0.0
}
.sortedByDescending { it.amount }
}
}

Output(totalAmount = totalAmount.await(), categoryAmounts = categoryAmounts.await())
return catAmtList()
}

data class Input(
val baseCurrency: String,
val range: FromToTimeRange,
val type: TransactionType,
val accountIdFilterList: List<UUID>
val accountIdFilterList: List<UUID>,
val treatTransferAsIncExp: Boolean = false
)

data class Output(val totalAmount: Double, val categoryAmounts: List<CategoryAmount>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,22 @@ import com.ivy.wallet.domain.action.exchange.ExchangeAct
import com.ivy.wallet.domain.action.exchange.actInput
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.domain.pure.data.IncomeExpensePair
import com.ivy.wallet.domain.pure.data.IncomeExpenseTransferPair
import com.ivy.wallet.domain.pure.transaction.WalletValueFunctions
import com.ivy.wallet.domain.pure.transaction.foldTransactionsSuspend
import javax.inject.Inject

class CalcTrnsIncomeExpenseAct @Inject constructor(
private val exchangeAct: ExchangeAct
) : FPAction<CalcTrnsIncomeExpenseAct.Input, IncomeExpensePair>() {
override suspend fun Input.compose(): suspend () -> IncomeExpensePair = suspend {
) : FPAction<CalcTrnsIncomeExpenseAct.Input, IncomeExpenseTransferPair>() {
override suspend fun Input.compose(): suspend () -> IncomeExpenseTransferPair = suspend {
foldTransactionsSuspend(
transactions = transactions,
valueFunctions = nonEmptyListOf(
WalletValueFunctions::income,
WalletValueFunctions::expense,
WalletValueFunctions::transferIncome,
WalletValueFunctions::transferExpenses
),
arg = WalletValueFunctions.Argument(
accounts = accounts,
Expand All @@ -30,9 +32,11 @@ class CalcTrnsIncomeExpenseAct @Inject constructor(
)
)
} then { values ->
IncomeExpensePair(
IncomeExpenseTransferPair(
income = values[0],
expense = values[1]
expense = values[1],
transferIncome = values[2],
transferExpense = values[3]
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class TrnsWithRangeAndAccFiltersAct @Inject constructor(
transactionDao.findAllBetween(range.from(), range.to())
.map { it.toDomain() }
} thenFilter {
accountIdFilterSet.contains(it.accountId)
accountIdFilterSet.contains(it.accountId) || accountIdFilterSet.contains(it.toAccountId)
}

data class Input(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ivy.wallet.domain.pure.data

import java.math.BigDecimal

data class IncomeExpenseTransferPair(
val income: BigDecimal,
val expense: BigDecimal,
val transferIncome: BigDecimal,
val transferExpense: BigDecimal
) {
companion object {
fun zero(): IncomeExpenseTransferPair = IncomeExpenseTransferPair(
BigDecimal.ZERO,
BigDecimal.ZERO,
BigDecimal.ZERO,
BigDecimal.ZERO
)
}
}
Loading

0 comments on commit bcca593

Please sign in to comment.