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

@@ -16,6 +16,7 @@ import cc.n0th1ng.tripmoney.data.entity.ExchangeRate
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.Trip
import cc.n0th1ng.tripmoney.utils.Icons
import cc.n0th1ng.tripmoney.utils.colors
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -96,19 +97,12 @@ private class DatabasePrepopulator(
tripDao.insert(Trip(name = "Włochy", startDate = "2025-01-01", currency = "PLN"))
tripDao.insert(Trip(name = "Szwajcaria", startDate = "2025-03-01", currency = "EUR"))
tripDao.insert(Trip(name = "Portugalia", startDate = "2026-03-01", currency = "USD"))
categoryDao.insert(Category(name = "Hotel", icon = Icons.HOTEL, color = "#B3E5FC"))
categoryDao.insert(Category(name = "Jedzenie", icon = Icons.RESTAURANT, color = "#C8E6C9"))
categoryDao.insert(Category(name = "Transport", icon = Icons.FLIGHT, color = "#FFCDD2"))
categoryDao.insert(Category(name = "Rozrywka", icon = Icons.ATTRACTION, color = "#FFF9C4"))
categoryDao.insert(Category(name = "Zakupy", icon = Icons.GROCERIES, color = "#E1BEE7"))
categoryDao.insert(Category(name = "Zakupy1", icon = Icons.GROCERIES, color = "#D7CCC8"))
categoryDao.insert(Category(name = "Zakupy2", icon = Icons.GROCERIES, color = "#BBDEFB"))
categoryDao.insert(Category(name = "Zakupy3", icon = Icons.GROCERIES, color = "#D1C4E9"))
categoryDao.insert(Category(name = "Zakupy4", icon = Icons.GROCERIES, color = "#DCEDC8"))
categoryDao.insert(Category(name = "Zakupy5", icon = Icons.GROCERIES, color = "#F0F4C3"))
categoryDao.insert(Category(name = "Zakupy6", icon = Icons.GROCERIES, color = "#FFE0B2"))
categoryDao.insert(Category(name = "Zakupy7", icon = Icons.GROCERIES, color = "#D7CCC8"))
categoryDao.insert(Category(name = "Zakupy8", icon = Icons.GROCERIES, color = "#CFD8DC"))
categoryDao.insert(Category(name = "Hotel", icon = Icons.HOTEL, color = colors.random()))
categoryDao.insert(Category(name = "Jedzenie", icon = Icons.RESTAURANT, color = colors.random()))
categoryDao.insert(Category(name = "Transport", icon = Icons.FLIGHT, color = colors.random()))
categoryDao.insert(Category(name = "Rozrywka", icon = Icons.ATTRACTION, color = colors.random()))
categoryDao.insert(Category(name = "Zakupy", icon = Icons.GROCERIES,color = colors.random()))
val now = LocalDateTime.now()
expenseDao.insert(

View File

@@ -3,27 +3,58 @@ package cc.n0th1ng.tripmoney.data.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Upsert
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategoryRaw
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
import kotlinx.coroutines.flow.Flow
@Dao
interface ExpenseDao {
@Upsert
suspend fun insert(expense: Expense)
@Transaction
@Query(
"""
SELECT * FROM expense WHERE trip_id = :tripId
ORDER BY DATETIME(expense.datetime) DESC
"""
)
fun expenseDto(tripId: Int): PagingSource<Int, ExpenseDto>
fun expenseDtoPaged(tripId: Int): PagingSource<Int, ExpenseDto>
@Transaction
@Query(
"""
SELECT * FROM expense WHERE trip_id = :tripId
ORDER BY DATETIME(expense.datetime) DESC
"""
)
fun expenseDto(tripId: Int): Flow<List<ExpenseDto>>
@Delete
suspend fun delete(expense: Expense)
@Query(
"""
SELECT
c.id as categoryId,
c.name as categoryName,
c.icon as icon,
c.color as color,
SUM(e.amount) as amount,
e.currency as currency
FROM
expense e
JOIN
category c ON e.category_id = c.id
WHERE
e.trip_id = :tripId
GROUP BY
c.id, c.name, c.icon, c.color, e.currency
"""
)
fun summaryPerCategoryRaw(tripId: Int): Flow<List<SummaryPerCategoryRaw>>
}

View File

@@ -23,4 +23,9 @@ interface TripDao {
@Delete
suspend fun delete(trip: Trip)
@Query(
"SELECT * FROM trip where trip.id = :tripId"
)
fun trip(tripId: Int): Trip?
}

View File

@@ -0,0 +1,22 @@
package cc.n0th1ng.tripmoney.data.dto
import cc.n0th1ng.tripmoney.data.entity.Category
import cc.n0th1ng.tripmoney.utils.Currencies
import cc.n0th1ng.tripmoney.utils.Icons
data class SummaryPerCategory(
val category: Category,
val amount: Double,
val percent: Float,
val currency: Currencies
)
data class SummaryPerCategoryRaw(
val categoryId: Int,
val categoryName: String,
val icon: Icons,
val color: String,
val amount: Double,
val currency: String
)

View File

@@ -40,14 +40,13 @@ class ExchangeRateRepository @Inject constructor(
date = date.toString()
)
)
clearOldRates()
rate
}
}
@RequiresApi(Build.VERSION_CODES.O)
private suspend fun clearOldRates(daysToKeep: Int = 180) {
suspend fun clearOldRates(daysToKeep: Int = 180) {
val cutoffDate = LocalDate.now().minusDays(daysToKeep.toLong()).toString()
exchangeRateDao.deleteOldRates(cutoffDate)
}

View File

@@ -5,6 +5,7 @@ import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import cc.n0th1ng.tripmoney.data.dao.ExpenseDao
import cc.n0th1ng.tripmoney.data.dto.SummaryPerCategoryRaw
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
import kotlinx.coroutines.flow.Flow
@@ -22,10 +23,18 @@ class ExpenseRepository @Inject constructor(private val expenseDao: ExpenseDao)
expenseDao.delete(expense)
}
fun getExpenses(tripId: Int): Flow<PagingData<ExpenseDto>> {
fun getExpensesPaged(tripId: Int): Flow<PagingData<ExpenseDto>> {
return Pager(
config = PagingConfig(pageSize = 50, enablePlaceholders = false),
pagingSourceFactory = { expenseDao.expenseDto(tripId) }
pagingSourceFactory = { expenseDao.expenseDtoPaged(tripId) }
).flow
}
fun getExpenses(tripId: Int): Flow<List<ExpenseDto>> {
return expenseDao.expenseDto(tripId)
}
fun getSummaryPerCategory(tripId: Int): Flow<List<SummaryPerCategoryRaw>> {
return expenseDao.summaryPerCategoryRaw(tripId)
}
}

View File

@@ -7,9 +7,13 @@ import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import cc.n0th1ng.tripmoney.data.repository.PreferenceKeys.APP_THEME
import cc.n0th1ng.tripmoney.data.repository.PreferenceKeys.CURRENT_TRIP
import cc.n0th1ng.tripmoney.data.repository.PreferenceKeys.DEFAULT_CURRENCY
import cc.n0th1ng.tripmoney.utils.Currencies
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.DEFAULT_CONCURRENCY
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.Currency
import javax.inject.Inject
@@ -18,6 +22,7 @@ val Context.preferencesDataStore by preferencesDataStore(name = "app_preferences
object PreferenceKeys {
val APP_THEME = intPreferencesKey("app_theme")
val CURRENT_TRIP = intPreferencesKey("current_trip")
val DEFAULT_CURRENCY = stringPreferencesKey("default_currency")
}
@@ -34,6 +39,16 @@ class PreferencesRepository @Inject constructor(@ApplicationContext private val
prefs[CURRENT_TRIP] ?: -1
}
val defaultCurrencyFlow: Flow<Currencies> =
context.preferencesDataStore.data.map { prefs ->
Currencies.valueOf(prefs[DEFAULT_CURRENCY] ?: Currencies.default().name)
}
suspend fun saveDefaultCurrency(currency: Currencies) {
context.preferencesDataStore.edit { prefs ->
prefs[DEFAULT_CURRENCY] = currency.name
}
}
suspend fun saveCurrentTrip(tripId: Int) {
context.preferencesDataStore.edit { prefs ->
prefs[CURRENT_TRIP] = tripId

View File

@@ -23,6 +23,10 @@ class TripRepository @Inject constructor(private val tripDao: TripDao) {
).flow
}
fun getTrip(tripId: Int): Trip? {
return tripDao.trip(tripId)
}
@WorkerThread
suspend fun delete(trip: Trip) {
tripDao.delete(trip)