Privacy-Period-Tracker/app/src/main/kotlin/dev/privacyllc/period/di/DataModule.kt

116 lines
4.4 KiB
Kotlin

package dev.privacyllc.period.di
import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStoreFile
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.privacyllc.period.core.data.CycleData
import dev.privacyllc.period.core.data.CycleRepository
import dev.privacyllc.period.core.datastore.UserPreferencesRepository
import dev.privacyllc.period.core.notifications.ReminderScheduler
import dev.privacyllc.period.core.security.AppLockRepository
import dev.privacyllc.period.domain.prediction.PersonalPredictionEngine
import dev.privacyllc.period.domain.prediction.PredictionEngine
import java.time.Clock
import javax.inject.Qualifier
import javax.inject.Singleton
/**
* Where the app learns what a file path is, and the only place it does.
*
* Note what is absent: no `PeriodDatabase`, no DAO, no Room import anywhere in
* this module or anywhere above it. `CycleData.repository` hands back a
* repository and keeps the storage to itself — see
* docs/architecture/README.md.
*/
@Module
@InstallIn(SingletonComponent::class)
object DataModule {
/**
* Singleton because Room and DataStore both are: two instances over one
* file is a corruption bug that only shows up under concurrency.
*/
@Provides
@Singleton
fun cycleRepository(
@ApplicationContext context: Context,
engine: PredictionEngine,
clock: Clock,
): CycleRepository = CycleData.repository(context, engine, clock)
@Provides
@Singleton
fun preferencesDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
PreferenceDataStoreFactory.create {
context.preferencesDataStoreFile("user_preferences")
}
@Provides
@Singleton
fun userPreferencesRepository(store: DataStore<Preferences>) = UserPreferencesRepository(store)
/**
* The app lock's own store, in its own file.
*
* Qualified because it is a second `DataStore<Preferences>` and Hilt would
* otherwise have two bindings for one type — but the separate *file* is the
* real point, not the qualifier. `UserPreferencesRepository.resetToDefaults()`
* is `edit { it.clear() }`; a verifier record sharing that store would be one
* future caller away from silent removal, and removing it locks the user out
* of their own history with no way back.
*/
@Provides
@Singleton
@AppLockStore
fun appLockDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
PreferenceDataStoreFactory.create {
context.preferencesDataStoreFile("app_lock")
}
@Provides
@Singleton
fun appLockRepository(@AppLockStore store: DataStore<Preferences>): AppLockRepository =
AppLockRepository(store)
/**
* The engine, and the one line that decides which one the product ships.
*
* Everything else depends on [PredictionEngine] rather than an
* implementation, which is what made this swap a single line and what makes
* the next one a single line too.
*
* `BaselinePredictionEngine` stays in the tree. It stopped being the product
* and became the control: `EngineComparisonTest` scores both over the §51
* fixtures every build, so "the new engine is better" is a number rather
* than an opinion. At the swap it was **mean absolute error 0.67 against
* 1.00, and the window contained the actual start 9 times out of 9 against
* 7** — the second mattering more, because a window that misses is a broken
* promise rather than a tight forecast.
*/
@Provides
@Singleton
fun predictionEngine(): PredictionEngine = PersonalPredictionEngine()
/** Injected rather than read from the environment, so §50's date edge cases stay testable. */
@Provides
@Singleton
fun clock(): Clock = Clock.systemDefaultZone()
@Provides
@Singleton
fun reminderScheduler(@ApplicationContext context: Context): ReminderScheduler =
ReminderScheduler(context)
}
/** Distinguishes the lock's store from the settings store; they are different files. */
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AppLockStore