Files
tripMoney/app/src/main/java/cc/n0th1ng/tripmoney/data/TripDatabase.kt
Rafal Wisniewski e6c8cf5cd3 init
2026-04-29 15:58:20 +02:00

249 lines
7.0 KiB
Kotlin

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.room.TypeConverters
import cc.n0th1ng.tripmoney.data.dao.CategoryDao
import cc.n0th1ng.tripmoney.data.dao.ExchangeRateDao
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.ExchangeRate
import cc.n0th1ng.tripmoney.data.entity.Expense
import cc.n0th1ng.tripmoney.data.entity.Trip
import cc.n0th1ng.tripmoney.utils.Currencies
import cc.n0th1ng.tripmoney.utils.Icons
import cc.n0th1ng.tripmoney.utils.colors
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.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZoneOffset
import javax.inject.Singleton
import kotlin.random.Random
@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
abstract fun categoryDao(): CategoryDao
abstract fun exchangeRateDao(): ExchangeRateDao
}
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
@RequiresApi(Build.VERSION_CODES.O)
@Provides
@Singleton
fun provideTripDatabase(
@ApplicationContext context: Context
): TripDatabase {
// val db: TripDatabase = Room.inMemoryDatabaseBuilder(
val db: TripDatabase = Room.databaseBuilder(
name = "tripmoney_db",
context = context,
klass = TripDatabase::class.java,
)
// .allowMainThreadQueries() // TODO Remove in production!
.fallbackToDestructiveMigration() // TODO Handle schema changes during dev
.build()
CoroutineScope(Dispatchers.IO).launch {
DatabasePrepopulator(
tripDao = db.tripDao(),
categoryDao = db.categoryDao(),
expenseDao = db.expenseDao()
).prepopulate()
}
return db
}
@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 provideExchangeRateDao(database: TripDatabase): ExchangeRateDao {
return database.exchangeRateDao()
}
}
private class DatabasePrepopulator(
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 = LocalDate.parse("2026-03-01"),
endDate = LocalDate.parse("2026-03-15"),
currency = "PLN"
)
)
tripDao.insert(
Trip(
name = "Szwajcaria",
startDate = LocalDate.parse("2025-03-01"),
endDate = LocalDate.parse("2025-03-15"),
currency = "EUR"
)
)
tripDao.insert(
Trip(
name = "Portugalia",
startDate = LocalDate.parse("2025-03-01"),
endDate = LocalDate.parse("2025-03-15"),
currency = "USD"
)
)
for (category in sampleCategories) {
categoryDao.insert(category)
}
for (expense in sampleExpenses) {
expenseDao.insert(expense)
}
}
val sampleCategories = listOf(
Category(
name = "Hotel",
icon = Icons.HOTEL,
color = colors.random()
),
Category(
name = "Jedzenie",
icon = Icons.RESTAURANT,
color = colors.random()
),
Category(
name = "Transport",
icon = Icons.FLIGHT,
color = colors.random()
),
Category(
name = "Rozrywka",
icon = Icons.ATTRACTION,
color = colors.random()
),
Category(
name = "Zakupy",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy1",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy2",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy3",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy4",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy5",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy6",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy7",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy8",
icon = Icons.GROCERIES,
color = colors.random()
),
Category(
name = "Zakupy9",
icon = Icons.GROCERIES,
color = colors.random()
),
)
@RequiresApi(Build.VERSION_CODES.O)
val sampleExpenses = (0..150).map { i ->
val datetime = if (i > 4) {
val now = LocalDateTime.now()
val min = now.minusDays(10).toInstant(ZoneOffset.UTC).toEpochMilli()
val max = now.toInstant(ZoneOffset.UTC).toEpochMilli()
val randomMillis = Random.nextLong(min, max)
LocalDateTime.ofInstant(Instant.ofEpochMilli(randomMillis), ZoneOffset.UTC)
} else {
LocalDateTime.now()
}
val expense = Expense(
categoryId = Random.nextInt(1, sampleCategories.size),
tripId = 1,
amount = Random.nextDouble(0.1, 300.0),
currency = Currencies.entries.random().name,
note = if (i % 3 == 0) "Some note" else "",
datetime = datetime,
rate = if (Random.nextBoolean()) Random.nextDouble(
0.1,
5.0
) else 1.0
)
expense
}
}