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.domain.prediction.PersonalPredictionEngine import dev.privacyllc.period.domain.prediction.PredictionEngine import java.time.Clock 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 = PreferenceDataStoreFactory.create { context.preferencesDataStoreFile("user_preferences") } @Provides @Singleton fun userPreferencesRepository(store: DataStore) = UserPreferencesRepository(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() }