This commit is contained in:
Rafal Wisniewski
2026-03-23 20:14:13 +01:00
parent 96cdd056a0
commit 916481e4e3
27 changed files with 960 additions and 154 deletions

View File

@@ -1,35 +1,39 @@
package cc.n0th1ng.tripmoney.viewmodel
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategory
import cc.n0th1ng.tripmoney.data.entity.Category
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
import cc.n0th1ng.tripmoney.data.repository.CategoryRepository
import cc.n0th1ng.tripmoney.data.repository.ExchangeRateRepository
import cc.n0th1ng.tripmoney.data.repository.ExpenseRepository
import cc.n0th1ng.tripmoney.service.ExchangeService
import cc.n0th1ng.tripmoney.data.repository.TripRepository
import cc.n0th1ng.tripmoney.utils.Currencies
import dagger.hilt.android.lifecycle.HiltViewModel
import io.ktor.client.request.get
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.time.LocalDate
import kotlinx.coroutines.runBlocking
import java.time.LocalDateTime
import javax.inject.Inject
@HiltViewModel
class ExpenseAndCategoryViewModel @Inject constructor(
open class ExpenseAndCategoryViewModel @Inject constructor(
private val expenseRepo: ExpenseRepository,
private val categoryRepo: CategoryRepository,
private val exchangeRateRepository: ExchangeRateRepository
private val exchangeRateRepository: ExchangeRateRepository,
private val tripRepo: TripRepository
) : ViewModel() {
fun getExpenses(tripId: Int): Flow<PagingData<ExpenseDto>> =
expenseRepo.getExpenses(tripId).cachedIn(viewModelScope)
expenseRepo.getExpensesPaged(tripId).cachedIn(viewModelScope)
fun save(expense: Expense) {
viewModelScope.launch {
@@ -51,9 +55,81 @@ class ExpenseAndCategoryViewModel @Inject constructor(
}
}
fun convertAmount(amount: Double, base: Currencies, target: Currencies, date: LocalDate): Flow<Double> {
return flow {
emit(amount * exchangeRateRepository.getRate(base, target, date))
@RequiresApi(Build.VERSION_CODES.O)
fun getSummaryPerCategory(tripId: Int): Flow<List<SummaryPerCategory>> {
val tripCurrency = tripRepo.getTrip(tripId)?.currency ?: Currencies.default().name
return getExpensesWithConvertedAmounts(tripId)
.map { list ->
// Compute summary
val sumOfAll = list.sumOf { it.convertedAmount }
list.groupBy { it.expenseDto.category }
.map { (category, expenses) ->
val total = expenses.sumOf { it.convertedAmount }
SummaryPerCategory(
category = category,
amount = total,
percent = (total / sumOfAll).toFloat(),
currency = Currencies.valueOf(tripCurrency)
)
}.sortedBy { it.percent }.reversed()
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun getExpensesWithConvertedAmounts(tripId: Int): Flow<List<ExpenseDtoWithConvertedAmount>> {
return expenseRepo.getExpenses(tripId)
.map { list ->
list.map { expenseDto ->
val convertedAmount =
if (expenseDto.expense.currency != expenseDto.trip.currency) {
runBlocking {
expenseDto.toExpenseDtoWithConvertedAmount()
}
} else {
expenseDto.expense.amount
}
ExpenseDtoWithConvertedAmount(expenseDto, convertedAmount)
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun getExpensesWithConvertedAmountsPaged(tripId: Int): Flow<PagingData<ExpenseDtoWithConvertedAmount>> {
return expenseRepo.getExpensesPaged(tripId)
.map { pagingData ->
pagingData.map { expenseDto ->
val convertedAmount =
if (expenseDto.expense.currency != expenseDto.trip.currency) {
runBlocking {
expenseDto.toExpenseDtoWithConvertedAmount()
}
} else {
expenseDto.expense.amount
}
ExpenseDtoWithConvertedAmount(expenseDto, convertedAmount)
}
}
}
@RequiresApi(Build.VERSION_CODES.O)
fun clearOldRates() {
viewModelScope.launch {
exchangeRateRepository.clearOldRates()
}
}
@RequiresApi(Build.VERSION_CODES.O)
suspend fun ExpenseDto.toExpenseDtoWithConvertedAmount(): Double {
return exchangeRateRepository.getRate(
Currencies.valueOf(this.expense.currency),
Currencies.valueOf(this.trip.currency),
LocalDateTime.parse(this.expense.datetime).toLocalDate()
) * this.expense.amount
}
data class ExpenseDtoWithConvertedAmount(
val expenseDto: ExpenseDto,
val convertedAmount: Double
)
}

View File

@@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import cc.n0th1ng.tripmoney.data.repository.AppTheme
import cc.n0th1ng.tripmoney.data.repository.PreferencesRepository
import cc.n0th1ng.tripmoney.utils.Currencies
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.SharingStarted
@@ -35,6 +36,17 @@ class SettingsViewModel @Inject constructor(
-1
)
val defaultCurrency = repo.defaultCurrencyFlow.stateIn(
viewModelScope, SharingStarted.WhileSubscribed(5000),
Currencies.default()
)
fun setDefaultCurrency(currency: Currencies) {
viewModelScope.launch {
repo.saveDefaultCurrency(currency)
}
}
fun setCurrentTrip(tripId: Int) {
viewModelScope.launch {
repo.saveCurrentTrip(tripId)

View File

@@ -18,6 +18,8 @@ class TripViewModel @Inject constructor(private val repository: TripRepository)
fun getTrips(): Flow<PagingData<Trip>> = repository.getTrips().cachedIn(viewModelScope)
fun getTrip(tripId: Int): Trip? = repository.getTrip(tripId)
fun delete(trip: Trip) {
viewModelScope.launch {
repository.delete(trip)