init
This commit is contained in:
41
app/src/main/java/cc/n0th1ng/tripmoney/data/Converters.kt
Normal file
41
app/src/main/java/cc/n0th1ng/tripmoney/data/Converters.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
package cc.n0th1ng.tripmoney.data
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.room.TypeConverter
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
||||
class Converters {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@TypeConverter
|
||||
fun fromLocalDatetime(value: LocalDateTime): Long {
|
||||
return value
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toInstant()
|
||||
.toEpochMilli()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@TypeConverter
|
||||
fun toLocalDateTime(value: Long): LocalDateTime {
|
||||
return Instant.ofEpochMilli(value)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.toLocalDateTime()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@TypeConverter
|
||||
fun fromLocalDate(value: LocalDate): Long {
|
||||
return value.toEpochDay()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@TypeConverter
|
||||
fun toLocalDate(value: Long): LocalDate {
|
||||
return LocalDate.ofEpochDay(value)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import cc.n0th1ng.tripmoney.data.dao.CategoryDao
|
||||
import cc.n0th1ng.tripmoney.data.dao.ExchangeRateDao
|
||||
@@ -17,6 +18,7 @@ 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 cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@@ -25,11 +27,13 @@ import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Database(entities = [Trip::class, Expense::class, Category::class, ExchangeRate::class], version = 1)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class TripDatabase : RoomDatabase() {
|
||||
abstract fun tripDao(): TripDao
|
||||
abstract fun expenseDao(): ExpenseDao
|
||||
@@ -46,7 +50,8 @@ object DatabaseModule {
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTripDatabase(
|
||||
@ApplicationContext context: Context
|
||||
@ApplicationContext context: Context,
|
||||
// expenseAndCategoryViewModel: ExpenseAndCategoryViewModel
|
||||
): TripDatabase {
|
||||
|
||||
val db: TripDatabase = Room.inMemoryDatabaseBuilder(
|
||||
@@ -94,9 +99,9 @@ private class DatabasePrepopulator(
|
||||
) {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
suspend fun prepopulate() {
|
||||
tripDao.insert(Trip(name = "Włochy", startDate = "2026-03-01", currency = "PLN"))
|
||||
tripDao.insert(Trip(name = "Szwajcaria", startDate = "2025-03-01", currency = "EUR"))
|
||||
tripDao.insert(Trip(name = "Portugalia", startDate = "2025-03-01", currency = "USD"))
|
||||
tripDao.insert(Trip(name = "Włochy", startDate = LocalDate.parse("2026-03-01"), currency = "PLN"))
|
||||
tripDao.insert(Trip(name = "Szwajcaria", startDate =LocalDate.parse("2025-03-01"), currency = "EUR"))
|
||||
tripDao.insert(Trip(name = "Portugalia", startDate = LocalDate.parse("2025-03-01"), currency = "USD"))
|
||||
categoryDao.insert(Category(name = "Accomodation", icon = Icons.HOTEL, color = colors.random()))
|
||||
categoryDao.insert(Category(name = "Transport", icon = Icons.TRANSPORT, color = colors.random()))
|
||||
categoryDao.insert(Category(name = "Flight", icon = Icons.FLIGHT, color = colors.random()))
|
||||
@@ -113,7 +118,7 @@ private class DatabasePrepopulator(
|
||||
amount = 120.50,
|
||||
currency = "PLN",
|
||||
note = "Hotel overnight",
|
||||
datetime = now.minusDays(10).toString(),
|
||||
datetime = now.minusDays(10),
|
||||
categoryId = 1,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -123,7 +128,7 @@ private class DatabasePrepopulator(
|
||||
amount = 45.75,
|
||||
currency = "PLN",
|
||||
note = "Dinner",
|
||||
datetime = now.minusDays(9).toString(),
|
||||
datetime = now.minusDays(9),
|
||||
categoryId = 2,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -133,7 +138,7 @@ private class DatabasePrepopulator(
|
||||
amount = 15.20,
|
||||
currency = "PLN",
|
||||
note = "Bus ticket",
|
||||
datetime = now.minusDays(8).toString(),
|
||||
datetime = now.minusDays(8),
|
||||
categoryId = 3,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -143,7 +148,7 @@ private class DatabasePrepopulator(
|
||||
amount = 89.99,
|
||||
currency = "PLN",
|
||||
note = "Concert tickets",
|
||||
datetime = now.minusDays(7).toString(),
|
||||
datetime = now.minusDays(7),
|
||||
categoryId = 4,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -153,7 +158,7 @@ private class DatabasePrepopulator(
|
||||
amount = 32.50,
|
||||
currency = "PLN",
|
||||
note = "Souvenirs",
|
||||
datetime = now.minusDays(6).toString(),
|
||||
datetime = now.minusDays(6),
|
||||
categoryId = 5,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -163,7 +168,7 @@ private class DatabasePrepopulator(
|
||||
amount = 180.00,
|
||||
currency = "PLN",
|
||||
note = "Hotel 3 nights",
|
||||
datetime = now.minusDays(5).toString(),
|
||||
datetime = now.minusDays(5),
|
||||
categoryId = 1,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -173,7 +178,7 @@ private class DatabasePrepopulator(
|
||||
amount = 67.30,
|
||||
currency = "PLN",
|
||||
note = "Lunch",
|
||||
datetime = now.minusDays(4).toString(),
|
||||
datetime = now.minusDays(4),
|
||||
categoryId = 2,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -183,7 +188,7 @@ private class DatabasePrepopulator(
|
||||
amount = 22.00,
|
||||
currency = "PLN",
|
||||
note = "Train ticket",
|
||||
datetime = now.minusDays(3).toString(),
|
||||
datetime = now.minusDays(3),
|
||||
categoryId = 3,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -193,7 +198,7 @@ private class DatabasePrepopulator(
|
||||
amount = 55.00,
|
||||
currency = "PLN",
|
||||
note = "Museum entry",
|
||||
datetime = now.minusDays(2).toString(),
|
||||
datetime = now.minusDays(2),
|
||||
categoryId = 4,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -203,7 +208,7 @@ private class DatabasePrepopulator(
|
||||
amount = 12.99,
|
||||
currency = "PLN",
|
||||
note = "Snacks",
|
||||
datetime = now.minusDays(1).toString(),
|
||||
datetime = now.minusDays(1),
|
||||
categoryId = 2,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -213,7 +218,7 @@ private class DatabasePrepopulator(
|
||||
amount = 210.00,
|
||||
currency = "PLN",
|
||||
note = "Hotel 5 nights",
|
||||
datetime = now.toString(),
|
||||
datetime = now,
|
||||
categoryId = 1,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -223,7 +228,7 @@ private class DatabasePrepopulator(
|
||||
amount = 95.50,
|
||||
currency = "EUR",
|
||||
note = "Dinner for two",
|
||||
datetime = now.minusHours(12).toString(),
|
||||
datetime = now.minusHours(12),
|
||||
categoryId = 2,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -233,7 +238,7 @@ private class DatabasePrepopulator(
|
||||
amount = 30.00,
|
||||
currency = "EUR",
|
||||
note = "Taxi",
|
||||
datetime = now.minusHours(6).toString(),
|
||||
datetime = now.minusHours(6),
|
||||
categoryId = 3,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -243,7 +248,7 @@ private class DatabasePrepopulator(
|
||||
amount = 40.00,
|
||||
currency = "USD",
|
||||
note = "Gifts",
|
||||
datetime = now.minusHours(3).toString(),
|
||||
datetime = now.minusHours(3),
|
||||
categoryId = 5,
|
||||
tripId = 1
|
||||
)
|
||||
@@ -253,7 +258,7 @@ private class DatabasePrepopulator(
|
||||
amount = 75.00,
|
||||
currency = "PLN",
|
||||
note = "Sightseeing tour",
|
||||
datetime = now.minusHours(1).toString(),
|
||||
datetime = now.minusHours(1),
|
||||
categoryId = 4,
|
||||
tripId = 1
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ interface ExpenseDao {
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM expense WHERE trip_id = :tripId
|
||||
ORDER BY DATETIME(expense.datetime) DESC
|
||||
ORDER BY expense.datetime DESC
|
||||
"""
|
||||
)
|
||||
fun expenseDtoPaged(tripId: Int): PagingSource<Int, ExpenseDto>
|
||||
@@ -28,33 +28,11 @@ interface ExpenseDao {
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM expense WHERE trip_id = :tripId
|
||||
ORDER BY DATETIME(expense.datetime) DESC
|
||||
ORDER BY 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>>
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface TripDao {
|
||||
@@ -27,5 +28,5 @@ interface TripDao {
|
||||
@Query(
|
||||
"SELECT * FROM trip where trip.id = :tripId"
|
||||
)
|
||||
fun trip(tripId: Int): Trip?
|
||||
fun trip(tripId: Int): Flow<Trip?>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
import java.time.LocalDateTime
|
||||
|
||||
@Entity(tableName = "expense")
|
||||
data class Expense(
|
||||
@@ -12,10 +13,15 @@ data class Expense(
|
||||
@ColumnInfo("amount") val amount: Double,
|
||||
@ColumnInfo("currency") val currency: String,
|
||||
@ColumnInfo("note") val note: String,
|
||||
@ColumnInfo("datetime") val datetime: String,
|
||||
@ColumnInfo("datetime") val datetime: LocalDateTime,
|
||||
@ColumnInfo("category_id") val categoryId: Int,
|
||||
@ColumnInfo("trip_id") val tripId: Int
|
||||
)
|
||||
@ColumnInfo("trip_id") val tripId: Int,
|
||||
@ColumnInfo("rate") val rate: Double = 1.0
|
||||
) {
|
||||
fun convertedAmount(): Double {
|
||||
return this.amount * this.rate
|
||||
}
|
||||
}
|
||||
|
||||
data class ExpenseDto(
|
||||
@Embedded val expense: Expense,
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
package cc.n0th1ng.tripmoney.data.entity
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import cc.n0th1ng.tripmoney.utils.Currencies
|
||||
import java.time.LocalDate
|
||||
|
||||
@Entity(tableName = "trip")
|
||||
data class Trip(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
@ColumnInfo("name") val name: String,
|
||||
@ColumnInfo("start_date") val startDate: String,
|
||||
@ColumnInfo("start_date") val startDate: LocalDate,
|
||||
@ColumnInfo("currency") val currency: String
|
||||
)
|
||||
){
|
||||
companion object {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
val DUMMY = Trip(-1, "dummy", LocalDate.now(), Currencies.default().name)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ class ExchangeRateRepository @Inject constructor(
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
suspend fun getRate(base: Currencies, target: Currencies, date: LocalDate): Double {
|
||||
if(base == target) return 1.0
|
||||
val id = ExchangeRate.buildKey(base.name, target.name, date.toString())
|
||||
val cachedRate = exchangeRateDao.getById(id)
|
||||
return if (cachedRate != null) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
@@ -8,10 +11,16 @@ 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 cc.n0th1ng.tripmoney.utils.Currencies
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExpenseRepository @Inject constructor(private val expenseDao: ExpenseDao) {
|
||||
class ExpenseRepository @Inject constructor(
|
||||
private val expenseDao: ExpenseDao,
|
||||
private val exchangeRateRepository: ExchangeRateRepository
|
||||
) {
|
||||
|
||||
@WorkerThread
|
||||
suspend fun save(expense: Expense) {
|
||||
@@ -23,18 +32,30 @@ class ExpenseRepository @Inject constructor(private val expenseDao: ExpenseDao)
|
||||
expenseDao.delete(expense)
|
||||
}
|
||||
|
||||
fun getExpensesPaged(tripId: Int): Flow<PagingData<ExpenseDto>> {
|
||||
fun getExpensesDtoPaged(tripId: Int): Flow<PagingData<ExpenseDto>> {
|
||||
return Pager(
|
||||
config = PagingConfig(pageSize = 50, enablePlaceholders = false),
|
||||
pagingSourceFactory = { expenseDao.expenseDtoPaged(tripId) }
|
||||
).flow
|
||||
}
|
||||
|
||||
fun getExpenses(tripId: Int): Flow<List<ExpenseDto>> {
|
||||
fun getExpensesDto(tripId: Int): Flow<List<ExpenseDto>> {
|
||||
return expenseDao.expenseDto(tripId)
|
||||
}
|
||||
|
||||
fun getSummaryPerCategory(tripId: Int): Flow<List<SummaryPerCategoryRaw>> {
|
||||
return expenseDao.summaryPerCategoryRaw(tripId)
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
suspend fun recalculateTripExpenses(tripId: Int) {
|
||||
val expenses = getExpensesDto(tripId).first()
|
||||
expenses.forEach { expenseDto ->
|
||||
val newRate = exchangeRateRepository.getRate(
|
||||
Currencies.valueOf(expenseDto.expense.currency),
|
||||
Currencies.valueOf(expenseDto.trip.currency),
|
||||
expenseDto.expense.datetime.toLocalDate()
|
||||
)
|
||||
save(
|
||||
expenseDto.expense.copy(rate = newRate)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,18 +1,26 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import cc.n0th1ng.tripmoney.data.dao.TripDao
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.viewmodel.ExpenseAndCategoryViewModel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class TripRepository @Inject constructor(private val tripDao: TripDao) {
|
||||
class TripRepository @Inject constructor(
|
||||
private val tripDao: TripDao,
|
||||
private val expenseRepository: ExpenseRepository
|
||||
) {
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
@WorkerThread
|
||||
suspend fun save(trip: Trip) {
|
||||
expenseRepository.recalculateTripExpenses(trip.id)
|
||||
tripDao.insert(trip)
|
||||
}
|
||||
|
||||
@@ -23,7 +31,7 @@ class TripRepository @Inject constructor(private val tripDao: TripDao) {
|
||||
).flow
|
||||
}
|
||||
|
||||
fun getTrip(tripId: Int): Trip? {
|
||||
fun getTrip(tripId: Int): Flow<Trip?> {
|
||||
return tripDao.trip(tripId)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user