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

Commit

Permalink
Fix BudgetViewModel.kt
Browse files Browse the repository at this point in the history
  • Loading branch information
ILIYANGERMANOV committed Apr 24, 2022
1 parent 8eb42e0 commit 34605f9
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 74 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.ivy.wallet.domain.action.budget

import com.ivy.fp.action.FPAction
import com.ivy.fp.action.thenMap
import com.ivy.wallet.domain.data.core.Budget
import com.ivy.wallet.io.persistence.dao.BudgetDao
import javax.inject.Inject

class BudgetsAct @Inject constructor(
private val budgetDao: BudgetDao
) : FPAction<Unit, List<Budget>>() {
override suspend fun Unit.compose(): suspend () -> List<Budget> = suspend {
budgetDao.findAll()
} thenMap { it.toDomain() }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.ivy.wallet.domain.pure.transaction

import arrow.core.Option
import arrow.core.toOption
import com.ivy.fp.Pure
import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Transaction
import java.time.LocalDateTime

Expand All @@ -26,4 +29,10 @@ fun isUpcoming(transaction: Transaction, timeNowUTC: LocalDateTime): Boolean =

@Pure
fun isOverdue(transaction: Transaction, timeNowUTC: LocalDateTime): Boolean =
timeNowUTC.isAfter(transaction.dueDate)
timeNowUTC.isAfter(transaction.dueDate)

@Pure
fun trnCurrency(
transaction: Transaction,
accounts: List<Account>
): Option<String> = accounts.find { it.id == transaction.accountId }?.currency.toOption()
4 changes: 2 additions & 2 deletions app/src/main/java/com/ivy/wallet/ui/RootViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import com.ivy.wallet.Constants
import com.ivy.wallet.R
import com.ivy.wallet.android.billing.IvyBilling
import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.logic.PaywallLogic
import com.ivy.wallet.domain.logic.notification.TransactionReminderLogic
import com.ivy.wallet.domain.deprecated.logic.PaywallLogic
import com.ivy.wallet.domain.deprecated.logic.notification.TransactionReminderLogic
import com.ivy.wallet.io.network.IvyAnalytics
import com.ivy.wallet.io.network.IvySession
import com.ivy.wallet.io.persistence.SharedPrefs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.ivy.wallet.domain.action.settings.BaseCurrencyAct
import com.ivy.wallet.domain.action.wallet.CalcWalletBalanceAct
import com.ivy.wallet.domain.logic.PlannedPaymentsLogic
import com.ivy.wallet.domain.pure.data.WalletDAOs
import com.ivy.wallet.io.persistence.dao.SettingsDao
import com.ivy.wallet.domain.deprecated.logic.PlannedPaymentsLogic
import com.ivy.wallet.ui.IvyWalletCtx
import com.ivy.wallet.ui.onboarding.model.TimePeriod
import com.ivy.wallet.utils.dateNowUTC
Expand All @@ -19,8 +17,6 @@ import javax.inject.Inject

@HiltViewModel
class BalanceViewModel @Inject constructor(
private val walletDAOs: WalletDAOs,
private val settingsDao: SettingsDao,
private val plannedPaymentsLogic: PlannedPaymentsLogic,
private val ivyContext: IvyWalletCtx,
private val baseCurrencyAct: BaseCurrencyAct,
Expand Down
37 changes: 24 additions & 13 deletions app/src/main/java/com/ivy/wallet/ui/budget/BudgetScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
Expand All @@ -23,7 +22,7 @@ import com.ivy.wallet.R
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Budget
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.logic.model.CreateBudgetData
import com.ivy.wallet.domain.deprecated.logic.model.CreateBudgetData
import com.ivy.wallet.ui.BudgetScreen
import com.ivy.wallet.ui.IvyWalletPreview
import com.ivy.wallet.ui.budget.model.DisplayBudget
Expand All @@ -45,13 +44,13 @@ import com.ivy.wallet.utils.onScreenStart
fun BoxWithConstraintsScope.BudgetScreen(screen: BudgetScreen) {
val viewModel: BudgetViewModel = viewModel()

val timeRange by viewModel.timeRange.observeAsState()
val baseCurrency by viewModel.baseCurrencyCode.observeAsState("")
val categories by viewModel.categories.observeAsState(emptyList())
val accounts by viewModel.accounts.observeAsState(emptyList())
val budgets by viewModel.budgets.observeAsState(emptyList())
val appBudgetMax by viewModel.appBudgetMax.observeAsState(0.0)
val categoryBudgetsTotal by viewModel.categoryBudgetsTotal.observeAsState(0.0)
val timeRange by viewModel.timeRange.collectAsState()
val baseCurrency by viewModel.baseCurrencyCode.collectAsState()
val categories by viewModel.categories.collectAsState()
val accounts by viewModel.accounts.collectAsState()
val budgets by viewModel.budgets.collectAsState()
val appBudgetMax by viewModel.appBudgetMax.collectAsState()
val categoryBudgetsTotal by viewModel.categoryBudgetsTotal.collectAsState()

onScreenStart {
viewModel.start()
Expand Down Expand Up @@ -231,20 +230,32 @@ private fun Toolbar(
Spacer(Modifier.height(4.dp))

val categoryBudgetText = if (categoryBudgetsTotal > 0) {
stringResource(R.string.for_categories, categoryBudgetsTotal.format(baseCurrency), baseCurrency)
stringResource(
R.string.for_categories,
categoryBudgetsTotal.format(baseCurrency),
baseCurrency
)
} else ""

val appBudgetMaxText = if (appBudgetMax > 0) {
stringResource(R.string.app_budget, appBudgetMax.format(baseCurrency), baseCurrency)
stringResource(
R.string.app_budget,
appBudgetMax.format(baseCurrency),
baseCurrency
)
} else ""

val hasBothBudgetTypes =
categoryBudgetText.isNotBlank() && appBudgetMaxText.isNotBlank()
Text(
modifier = Modifier.testTag("budgets_info_text"),
text = if (hasBothBudgetTypes)
stringResource(R.string.budget_info_both, categoryBudgetText, appBudgetMaxText)
else stringResource(R.string.budget_info, categoryBudgetText, appBudgetMaxText),
stringResource(
R.string.budget_info_both,
categoryBudgetText,
appBudgetMaxText
)
else stringResource(R.string.budget_info, categoryBudgetText, appBudgetMaxText),
style = UI.typo.nC.style(
color = Gray,
fontWeight = FontWeight.ExtraBold
Expand Down
106 changes: 53 additions & 53 deletions app/src/main/java/com/ivy/wallet/ui/budget/BudgetViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
package com.ivy.wallet.ui.budget

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.ivy.fp.sumOfSuspend
import com.ivy.wallet.domain.action.account.AccountsAct
import com.ivy.wallet.domain.action.budget.BudgetsAct
import com.ivy.wallet.domain.action.category.CategoriesAct
import com.ivy.wallet.domain.action.exchange.ExchangeAct
import com.ivy.wallet.domain.action.settings.BaseCurrencyAct
import com.ivy.wallet.domain.action.transaction.HistoryTrnsAct
import com.ivy.wallet.domain.data.TransactionType
import com.ivy.wallet.domain.data.core.Account
import com.ivy.wallet.domain.data.core.Budget
import com.ivy.wallet.domain.data.core.Category
import com.ivy.wallet.domain.data.core.Transaction
import com.ivy.wallet.domain.logic.BudgetCreator
import com.ivy.wallet.domain.logic.WalletLogic
import com.ivy.wallet.domain.logic.currency.ExchangeRatesLogic
import com.ivy.wallet.domain.logic.model.CreateBudgetData
import com.ivy.wallet.domain.sync.item.BudgetSync
import com.ivy.wallet.domain.deprecated.logic.BudgetCreator
import com.ivy.wallet.domain.deprecated.logic.model.CreateBudgetData
import com.ivy.wallet.domain.deprecated.sync.item.BudgetSync
import com.ivy.wallet.domain.pure.exchange.ExchangeData
import com.ivy.wallet.domain.pure.transaction.trnCurrency
import com.ivy.wallet.io.persistence.SharedPrefs
import com.ivy.wallet.io.persistence.dao.AccountDao
import com.ivy.wallet.io.persistence.dao.BudgetDao
import com.ivy.wallet.io.persistence.dao.CategoryDao
import com.ivy.wallet.io.persistence.dao.SettingsDao
import com.ivy.wallet.ui.IvyWalletCtx
import com.ivy.wallet.ui.budget.model.DisplayBudget
import com.ivy.wallet.ui.onboarding.model.FromToTimeRange
import com.ivy.wallet.ui.onboarding.model.TimePeriod
import com.ivy.wallet.ui.onboarding.model.toCloseTimeRange
import com.ivy.wallet.utils.*
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -32,54 +39,50 @@ class BudgetViewModel @Inject constructor(
private val sharedPrefs: SharedPrefs,
private val settingsDao: SettingsDao,
private val budgetDao: BudgetDao,
private val walletLogic: WalletLogic,
private val categoryDao: CategoryDao,
private val accountDao: AccountDao,
private val exchangeRatesLogic: ExchangeRatesLogic,
private val budgetCreator: BudgetCreator,
private val budgetSync: BudgetSync,
private val ivyContext: IvyWalletCtx
private val ivyContext: IvyWalletCtx,
private val accountsAct: AccountsAct,
private val categoriesAct: CategoriesAct,
private val budgetsAct: BudgetsAct,
private val baseCurrencyAct: BaseCurrencyAct,
private val historyTrnsAct: HistoryTrnsAct,
private val exchangeAct: ExchangeAct
) : ViewModel() {

private val _timeRange = MutableLiveData<FromToTimeRange>()
val timeRange = _timeRange.asLiveData()
private val _timeRange = MutableStateFlow(ivyContext.selectedPeriod.toRange(1))
val timeRange = _timeRange.readOnly()

private val _baseCurrencyCode = MutableLiveData(getDefaultFIATCurrency().currencyCode)
val baseCurrencyCode = _baseCurrencyCode.asLiveData()
private val _baseCurrencyCode = MutableStateFlow(getDefaultFIATCurrency().currencyCode)
val baseCurrencyCode = _baseCurrencyCode.readOnly()

private val _budgets = MutableLiveData<List<DisplayBudget>>()
val budgets = _budgets.asLiveData()
private val _budgets = MutableStateFlow<List<DisplayBudget>>(emptyList())
val budgets = _budgets.readOnly()

private val _categories = MutableLiveData<List<Category>>()
val categories = _categories.asLiveData()
private val _categories = MutableStateFlow<List<Category>>(emptyList())
val categories = _categories.readOnly()

private val _accounts = MutableLiveData<List<Account>>()
val accounts = _accounts.asLiveData()
private val _accounts = MutableStateFlow<List<Account>>(emptyList())
val accounts = _accounts.readOnly()

private val _categoryBudgetsTotal = MutableLiveData<Double>()
val categoryBudgetsTotal = _categoryBudgetsTotal.asLiveData()
private val _categoryBudgetsTotal = MutableStateFlow(0.0)
val categoryBudgetsTotal = _categoryBudgetsTotal.readOnly()

private val _appBudgetMax = MutableLiveData<Double>()
val appBudgetMax = _appBudgetMax.asLiveData()
private val _appBudgetMax = MutableStateFlow(0.0)
val appBudgetMax = _appBudgetMax.readOnly()

fun start() {
viewModelScope.launch {
TestIdlingResource.increment()

_categories.value = ioThread {
categoryDao.findAll()
}!!
_categories.value = categoriesAct(Unit)

val accounts = ioThread {
accountDao.findAll()
}
val accounts = accountsAct(Unit)
_accounts.value = accounts

val settings = ioThread {
settingsDao.findFirst()
}

val baseCurrency = settings.currency
val baseCurrency = baseCurrencyAct(Unit)
_baseCurrencyCode.value = baseCurrency

val startDateOfMonth = ivyContext.initStartDayOfMonthInMemory(sharedPrefs = sharedPrefs)
Expand All @@ -88,13 +91,7 @@ class BudgetViewModel @Inject constructor(
).toRange(startDateOfMonth = startDateOfMonth)
_timeRange.value = timeRange

val transactions = ioThread {
walletLogic.history(range = timeRange)
}.filterIsInstance(Transaction::class.java)

val budgets = ioThread {
budgetDao.findAll()
}
val budgets = budgetsAct(Unit)

_appBudgetMax.value = budgets
.filter { it.categoryIdsSerialized.isNullOrBlank() }
Expand All @@ -110,7 +107,7 @@ class BudgetViewModel @Inject constructor(
budget = it,
spentAmount = calculateSpentAmount(
budget = it,
transactions = transactions,
transactions = historyTrnsAct(timeRange.toCloseTimeRange()),
accounts = accounts,
baseCurrencyCode = baseCurrency
)
Expand All @@ -122,25 +119,20 @@ class BudgetViewModel @Inject constructor(
}
}

private fun calculateSpentAmount(
private suspend fun calculateSpentAmount(
budget: Budget,
transactions: List<Transaction>,
baseCurrencyCode: String,
accounts: List<Account>
): Double {
//TODO: Re-work this by creating an FPAction for it
val accountsFilter = budget.parseAccountIds()
val categoryFilter = budget.parseCategoryIds()

return transactions
.filter { accountsFilter.isEmpty() || accountsFilter.contains(it.accountId) }
.filter { categoryFilter.isEmpty() || categoryFilter.contains(it.categoryId) }
.sumOf {
val amountBaseCurrency = exchangeRatesLogic.amountBaseCurrency(
transaction = it,
baseCurrency = baseCurrencyCode,
accounts = accounts
)

.sumOfSuspend {
when (it.type) {
TransactionType.INCOME -> {
//decrement spent amount if it's not global budget
Expand All @@ -149,7 +141,15 @@ class BudgetViewModel @Inject constructor(
}
TransactionType.EXPENSE -> {
//increment spent amount
amountBaseCurrency
exchangeAct(
ExchangeAct.Input(
data = ExchangeData(
baseCurrency = baseCurrencyCode,
fromCurrency = trnCurrency(it, accounts)
),
amount = it.amount
)
).orNull()?.toDouble() ?: 0.0
}
TransactionType.TRANSFER -> {
//ignore transfers for simplicity
Expand Down Expand Up @@ -203,7 +203,7 @@ class BudgetViewModel @Inject constructor(
ioThread {
newOrder.forEachIndexed { index, item ->
budgetDao.save(
item.budget.copy(
item.budget.toEntity().copy(
orderId = index.toDouble(),
isSynced = false
)
Expand Down
11 changes: 11 additions & 0 deletions ivy-fp/src/main/java/com/ivy/fp/Utils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.ivy.fp

suspend fun <A> List<A>.sumOfSuspend(
selector: suspend (A) -> Double
): Double {
var sum = 0.0
for (item in this) {
sum += selector(item)
}
return sum
}

0 comments on commit 34605f9

Please sign in to comment.