diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DataModule.kt b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DataModule.kt index eaeca73..dcbd3c6 100644 --- a/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DataModule.kt +++ b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DataModule.kt @@ -21,8 +21,8 @@ import java.io.File import javax.inject.Singleton /** - * Clockula's storage bindings. M1 wires the preference half only; the Room - * database and its DAOs join this module in M2. + * The DataStore half of Clockula's storage. The Room database and its DAOs live + * next door in `DatabaseModule`. */ @Module @InstallIn(SingletonComponent::class) diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DatabaseModule.kt b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DatabaseModule.kt new file mode 100644 index 0000000..c2f1ed7 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/DatabaseModule.kt @@ -0,0 +1,42 @@ +package de.jeanlucmakiola.clockula.data.di + +import android.content.Context +import androidx.room.Room +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import de.jeanlucmakiola.clockula.data.alarms.AlarmDao +import de.jeanlucmakiola.clockula.data.db.ClockulaDatabase +import de.jeanlucmakiola.clockula.data.stopwatch.LapDao +import de.jeanlucmakiola.clockula.data.timers.TimerDao +import de.jeanlucmakiola.clockula.data.worldclocks.WorldClockDao +import javax.inject.Singleton + +/** + * The database and its four DAOs. No destructive-migration fallback: docs/PLAN.md + * §12 wants every schema change to arrive with a tested migration, and a fallback + * would quietly drop a user's alarms instead of failing loudly in review. + */ +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + + @Provides + @Singleton + fun provideDatabase(@ApplicationContext context: Context): ClockulaDatabase = + Room.databaseBuilder(context, ClockulaDatabase::class.java, ClockulaDatabase.NAME).build() + + @Provides + fun provideAlarmDao(database: ClockulaDatabase): AlarmDao = database.alarmDao() + + @Provides + fun provideTimerDao(database: ClockulaDatabase): TimerDao = database.timerDao() + + @Provides + fun provideWorldClockDao(database: ClockulaDatabase): WorldClockDao = database.worldClockDao() + + @Provides + fun provideLapDao(database: ClockulaDatabase): LapDao = database.lapDao() +} diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/data/di/RepositoryModule.kt b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/RepositoryModule.kt new file mode 100644 index 0000000..aa12c42 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/RepositoryModule.kt @@ -0,0 +1,37 @@ +package de.jeanlucmakiola.clockula.data.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import de.jeanlucmakiola.clockula.data.alarms.AlarmRepository +import de.jeanlucmakiola.clockula.data.alarms.AlarmRepositoryImpl +import de.jeanlucmakiola.clockula.data.stopwatch.StopwatchRepository +import de.jeanlucmakiola.clockula.data.stopwatch.StopwatchRepositoryImpl +import de.jeanlucmakiola.clockula.data.timers.TimerRepository +import de.jeanlucmakiola.clockula.data.timers.TimerRepositoryImpl +import de.jeanlucmakiola.clockula.data.worldclocks.WorldClockRepository +import de.jeanlucmakiola.clockula.data.worldclocks.WorldClockRepositoryImpl +import javax.inject.Singleton + +/** The four interfaces the rest of the app sees — and the only ones it may see. */ +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + + @Binds + @Singleton + abstract fun bindAlarmRepository(impl: AlarmRepositoryImpl): AlarmRepository + + @Binds + @Singleton + abstract fun bindTimerRepository(impl: TimerRepositoryImpl): TimerRepository + + @Binds + @Singleton + abstract fun bindWorldClockRepository(impl: WorldClockRepositoryImpl): WorldClockRepository + + @Binds + @Singleton + abstract fun bindStopwatchRepository(impl: StopwatchRepositoryImpl): StopwatchRepository +} diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/data/di/TimeModule.kt b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/TimeModule.kt new file mode 100644 index 0000000..08effc3 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/clockula/data/di/TimeModule.kt @@ -0,0 +1,25 @@ +package de.jeanlucmakiola.clockula.data.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import de.jeanlucmakiola.clockula.data.time.SystemElapsedRealtimeClock +import de.jeanlucmakiola.clockula.data.time.SystemWallClock +import de.jeanlucmakiola.clockula.domain.time.ElapsedRealtimeClock +import de.jeanlucmakiola.clockula.domain.time.WallClock + +/** + * Both clocks are injected, never called statically, so the wall-clock / + * elapsed-realtime distinction can be tested from both sides. + */ +@Module +@InstallIn(SingletonComponent::class) +abstract class TimeModule { + + @Binds + abstract fun bindWallClock(impl: SystemWallClock): WallClock + + @Binds + abstract fun bindElapsedRealtimeClock(impl: SystemElapsedRealtimeClock): ElapsedRealtimeClock +} diff --git a/app/src/main/java/de/jeanlucmakiola/clockula/data/time/AndroidClocks.kt b/app/src/main/java/de/jeanlucmakiola/clockula/data/time/AndroidClocks.kt new file mode 100644 index 0000000..1cb442e --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/clockula/data/time/AndroidClocks.kt @@ -0,0 +1,23 @@ +package de.jeanlucmakiola.clockula.data.time + +import android.os.SystemClock +import de.jeanlucmakiola.clockula.domain.time.ElapsedRealtimeClock +import de.jeanlucmakiola.clockula.domain.time.WallClock +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Instant +import javax.inject.Inject + +/** The device's calendar time. Moves with the user, the network and DST. */ +class SystemWallClock @Inject constructor() : WallClock { + override fun now(): Instant = Instant.fromEpochMilliseconds(System.currentTimeMillis()) +} + +/** + * Milliseconds since boot, including deep sleep. Never decreases within a boot + * and no setting can move it — which is exactly why a running timer and the + * stopwatch anchor on it. + */ +class SystemElapsedRealtimeClock @Inject constructor() : ElapsedRealtimeClock { + override fun elapsedRealtime(): Duration = SystemClock.elapsedRealtime().milliseconds +}