init
This commit is contained in:
138
app/src/main/java/cc/n0th1ng/tripmoney/data/TripDatabase.kt
Normal file
138
app/src/main/java/cc/n0th1ng/tripmoney/data/TripDatabase.kt
Normal file
@@ -0,0 +1,138 @@
|
||||
package cc.n0th1ng.tripmoney.data
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import cc.n0th1ng.tripmoney.data.dao.CategoryDao
|
||||
import cc.n0th1ng.tripmoney.data.dao.ExpenseDao
|
||||
import cc.n0th1ng.tripmoney.data.dao.TripDao
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
import cc.n0th1ng.tripmoney.utils.Icons
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDateTime
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Database(entities = [Trip::class, Expense::class, Category::class], version = 1)
|
||||
abstract class TripDatabase : RoomDatabase() {
|
||||
abstract fun tripDao(): TripDao
|
||||
abstract fun expenseDao(): ExpenseDao
|
||||
abstract fun categoryDao(): CategoryDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: TripDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): TripDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
TripDatabase::class.java
|
||||
).allowMainThreadQueries().build().also { INSTANCE = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DatabaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTripDatabase(
|
||||
@ApplicationContext context: Context
|
||||
): TripDatabase {
|
||||
return Room.inMemoryDatabaseBuilder(
|
||||
context,
|
||||
TripDatabase::class.java
|
||||
)
|
||||
.allowMainThreadQueries() // Only for in-memory DB, not for production!
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideExpenseDao(database: TripDatabase): ExpenseDao {
|
||||
return database.expenseDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTripDao(database: TripDatabase): TripDao {
|
||||
return database.tripDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideCategoryDao(database: TripDatabase): CategoryDao {
|
||||
return database.categoryDao()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDatabasePrepopulator(
|
||||
tripDao: TripDao,
|
||||
categoryDao: CategoryDao,
|
||||
expenseDao: ExpenseDao
|
||||
): DatabasePrepopulator {
|
||||
return DatabasePrepopulator(tripDao, categoryDao, expenseDao)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DatabasePrepopulator @Inject constructor(
|
||||
private val tripDao: TripDao,
|
||||
private val categoryDao: CategoryDao,
|
||||
private val expenseDao: ExpenseDao
|
||||
) {
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
suspend fun prepopulate() {
|
||||
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"))
|
||||
|
||||
val now = LocalDateTime.now()
|
||||
expenseDao.insert(Expense(amount = 120.50, currency = "PLN", note = "Hotel overnight", datetime = now.minusDays(10).toString(), categoryId = 1, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 45.75, currency = "PLN", note = "Dinner", datetime = now.minusDays(9).toString(), categoryId = 2, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 15.20, currency = "PLN", note = "Bus ticket", datetime = now.minusDays(8).toString(), categoryId = 3, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 89.99, currency = "PLN", note = "Concert tickets", datetime = now.minusDays(7).toString(), categoryId = 4, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 32.50, currency = "PLN", note = "Souvenirs", datetime = now.minusDays(6).toString(), categoryId = 5, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 180.00, currency = "PLN", note = "Hotel 3 nights", datetime = now.minusDays(5).toString(), categoryId = 1, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 67.30, currency = "PLN", note = "Lunch", datetime = now.minusDays(4).toString(), categoryId = 2, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 22.00, currency = "PLN", note = "Train ticket", datetime = now.minusDays(3).toString(), categoryId = 3, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 55.00, currency = "PLN", note = "Museum entry", datetime = now.minusDays(2).toString(), categoryId = 4, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 12.99, currency = "PLN", note = "Snacks", datetime = now.minusDays(1).toString(), categoryId = 2, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 210.00, currency = "PLN", note = "Hotel 5 nights", datetime = now.toString(), categoryId = 1, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 95.50, currency = "EUR", note = "Dinner for two", datetime = now.minusHours(12).toString(), categoryId = 2, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 30.00, currency = "EUR", note = "Taxi", datetime = now.minusHours(6).toString(), categoryId = 3, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 40.00, currency = "USD", note = "Gifts", datetime = now.minusHours(3).toString(), categoryId = 5, tripId = 1))
|
||||
expenseDao.insert(Expense(amount = 75.00, currency = "PLN", note = "Sightseeing tour", datetime = now.minusHours(1).toString(), categoryId = 4, tripId = 1))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cc.n0th1ng.tripmoney.data.dao
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
import androidx.room.Upsert
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface CategoryDao {
|
||||
@Upsert
|
||||
suspend fun insert(category: Category)
|
||||
|
||||
|
||||
@Transaction
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM category
|
||||
"""
|
||||
)
|
||||
fun categories(): Flow<List<Category>>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
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.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
||||
|
||||
@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>
|
||||
|
||||
@Delete
|
||||
suspend fun delete(expense: Expense)
|
||||
}
|
||||
22
app/src/main/java/cc/n0th1ng/tripmoney/data/dao/TripDao.kt
Normal file
22
app/src/main/java/cc/n0th1ng/tripmoney/data/dao/TripDao.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package cc.n0th1ng.tripmoney.data.dao
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.Query
|
||||
import androidx.room.Upsert
|
||||
import cc.n0th1ng.tripmoney.data.entity.Trip
|
||||
|
||||
@Dao
|
||||
interface TripDao {
|
||||
@Upsert
|
||||
suspend fun insert(trip: Trip)
|
||||
|
||||
@Query(
|
||||
"""
|
||||
SELECT * FROM trip
|
||||
"""
|
||||
)
|
||||
fun tripsPaged(): PagingSource<Int, Trip>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cc.n0th1ng.tripmoney.data.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import cc.n0th1ng.tripmoney.utils.Icons
|
||||
|
||||
@Entity(tableName = "category")
|
||||
data class Category(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
@ColumnInfo("name") val name: String,
|
||||
@ColumnInfo("icon") val icon: Icons,
|
||||
@ColumnInfo("color") val color: String
|
||||
)
|
||||
@@ -0,0 +1,33 @@
|
||||
package cc.n0th1ng.tripmoney.data.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Embedded
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
|
||||
@Entity(tableName = "expense")
|
||||
data class Expense(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
@ColumnInfo("amount") val amount: Double,
|
||||
@ColumnInfo("currency") val currency: String,
|
||||
@ColumnInfo("note") val note: String,
|
||||
@ColumnInfo("datetime") val datetime: String,
|
||||
@ColumnInfo("category_id") val categoryId: Int,
|
||||
@ColumnInfo("trip_id") val tripId: Int
|
||||
)
|
||||
|
||||
data class ExpenseDto(
|
||||
@Embedded val expense: Expense,
|
||||
@Relation(
|
||||
parentColumn = "category_id",
|
||||
entityColumn = "id"
|
||||
)
|
||||
val category: Category,
|
||||
@Relation(
|
||||
parentColumn = "trip_id",
|
||||
entityColumn = "id"
|
||||
)
|
||||
val trip: Trip
|
||||
)
|
||||
|
||||
13
app/src/main/java/cc/n0th1ng/tripmoney/data/entity/Trip.kt
Normal file
13
app/src/main/java/cc/n0th1ng/tripmoney/data/entity/Trip.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package cc.n0th1ng.tripmoney.data.entity
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@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("currency") val currency: String
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import cc.n0th1ng.tripmoney.data.dao.CategoryDao
|
||||
import cc.n0th1ng.tripmoney.data.entity.Category
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class CategoryRepository @Inject constructor(private val categoryDao: CategoryDao) {
|
||||
|
||||
@WorkerThread
|
||||
suspend fun save(category: Category) {
|
||||
categoryDao.insert(category)
|
||||
}
|
||||
|
||||
fun getCategories(): Flow<List<Category>> {
|
||||
return categoryDao.categories()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import cc.n0th1ng.tripmoney.data.dao.ExpenseDao
|
||||
import cc.n0th1ng.tripmoney.data.entity.Expense
|
||||
import cc.n0th1ng.tripmoney.data.entity.ExpenseDto
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExpenseRepository @Inject constructor(private val expenseDao: ExpenseDao) {
|
||||
|
||||
@WorkerThread
|
||||
suspend fun save(expense: Expense) {
|
||||
expenseDao.insert(expense)
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
suspend fun delete(expense: Expense) {
|
||||
expenseDao.delete(expense)
|
||||
}
|
||||
|
||||
fun getExpenses(tripId: Int): Flow<PagingData<ExpenseDto>> {
|
||||
return Pager(
|
||||
config = PagingConfig(pageSize = 50, enablePlaceholders = false),
|
||||
pagingSourceFactory = { expenseDao.expenseDto(tripId) }
|
||||
).flow
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
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 dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
val Context.preferencesDataStore by preferencesDataStore(name = "app_preferences")
|
||||
|
||||
object PreferenceKeys {
|
||||
val APP_THEME = intPreferencesKey("app_theme")
|
||||
val CURRENT_TRIP = intPreferencesKey("current_trip")
|
||||
|
||||
}
|
||||
|
||||
class PreferencesRepository @Inject constructor(@ApplicationContext private val context: Context) {
|
||||
val themeFlow: Flow<AppTheme> =
|
||||
context.preferencesDataStore.data.map { prefs ->
|
||||
val value = prefs[APP_THEME]
|
||||
?: AppTheme.SYSTEM.value
|
||||
AppTheme.fromValue(value)
|
||||
}
|
||||
|
||||
val currentTripFlow: Flow<Int> =
|
||||
context.preferencesDataStore.data.map { prefs ->
|
||||
prefs[CURRENT_TRIP] ?: -1
|
||||
}
|
||||
|
||||
suspend fun saveCurrentTrip(tripId: Int) {
|
||||
context.preferencesDataStore.edit { prefs ->
|
||||
prefs[CURRENT_TRIP] = tripId
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun saveTheme(theme: AppTheme) {
|
||||
context.preferencesDataStore.edit { prefs ->
|
||||
prefs[APP_THEME] = theme.value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class AppTheme(val value: Int) {
|
||||
LIGHT(0), DARK(1), SYSTEM(2);
|
||||
|
||||
companion object {
|
||||
fun fromValue(value: Int) =
|
||||
entries.firstOrNull { it.value == value } ?: SYSTEM
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cc.n0th1ng.tripmoney.data.repository
|
||||
|
||||
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 kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class TripRepository @Inject constructor(private val tripDao: TripDao) {
|
||||
|
||||
@WorkerThread
|
||||
suspend fun save(trip: Trip) {
|
||||
tripDao.insert(trip)
|
||||
}
|
||||
|
||||
fun getTrips(): Flow<PagingData<Trip>> {
|
||||
return Pager(
|
||||
config = PagingConfig(pageSize = 50, enablePlaceholders = false),
|
||||
pagingSourceFactory = { tripDao.tripsPaged() }
|
||||
).flow
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user