116 lines
4.4 KiB
Kotlin
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
|