feat(data): entity and domain mappers
The translation either way, including the repeat mask's bit order and the nullable per-alarm overrides that mean "inherit the app default" rather than a concrete value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
package de.jeanlucmakiola.clockula.data.alarms
|
||||
|
||||
import de.jeanlucmakiola.clockula.domain.Alarm
|
||||
import de.jeanlucmakiola.clockula.domain.AlarmDraft
|
||||
import de.jeanlucmakiola.clockula.domain.DismissChallenge
|
||||
import de.jeanlucmakiola.clockula.domain.RepeatDays
|
||||
import de.jeanlucmakiola.clockula.domain.TimeOfDay
|
||||
import de.jeanlucmakiola.floret.prefs.toEnum
|
||||
import kotlin.time.Instant
|
||||
|
||||
/** Entity ↔ domain. Reads are **forgiving**: a corrupt row degrades, never throws. */
|
||||
object AlarmMapper {
|
||||
fun toDomain(entity: AlarmEntity): Alarm = Alarm(
|
||||
id = entity.id,
|
||||
time = TimeOfDay.clamped(entity.hour, entity.minute),
|
||||
label = entity.label,
|
||||
enabled = entity.enabled,
|
||||
repeatDays = RepeatDays.fromMask(entity.repeatDays),
|
||||
skipNextOccurrence = entity.skipNextOccurrence,
|
||||
ringtoneUri = entity.ringtoneUri?.takeIf { it.isNotBlank() },
|
||||
vibrate = entity.vibrate,
|
||||
snoozeMinutes = entity.snoozeMinutes,
|
||||
snoozeLimit = entity.snoozeLimit,
|
||||
volumeRampSeconds = entity.volumeRampSeconds,
|
||||
// An absent challenge is an *override that isn't set*, so it stays null;
|
||||
// a stored name the app no longer knows degrades to NONE.
|
||||
dismissChallenge = entity.dismissChallenge?.toEnum(DismissChallenge.NONE),
|
||||
createdAt = Instant.fromEpochMilliseconds(entity.createdAt),
|
||||
updatedAt = Instant.fromEpochMilliseconds(entity.updatedAt),
|
||||
)
|
||||
|
||||
fun toDomain(entities: List<AlarmEntity>): List<Alarm> = entities.map(::toDomain)
|
||||
|
||||
fun toEntity(alarm: Alarm): AlarmEntity = AlarmEntity(
|
||||
id = alarm.id,
|
||||
hour = alarm.time.hour,
|
||||
minute = alarm.time.minute,
|
||||
label = alarm.label,
|
||||
enabled = alarm.enabled,
|
||||
repeatDays = alarm.repeatDays.mask,
|
||||
skipNextOccurrence = alarm.skipNextOccurrence,
|
||||
ringtoneUri = alarm.ringtoneUri,
|
||||
vibrate = alarm.vibrate,
|
||||
snoozeMinutes = alarm.snoozeMinutes,
|
||||
snoozeLimit = alarm.snoozeLimit,
|
||||
volumeRampSeconds = alarm.volumeRampSeconds,
|
||||
dismissChallenge = alarm.dismissChallenge?.name,
|
||||
createdAt = alarm.createdAt.toEpochMilliseconds(),
|
||||
updatedAt = alarm.updatedAt.toEpochMilliseconds(),
|
||||
)
|
||||
|
||||
/** id = 0, createdAt = updatedAt = [now]. */
|
||||
fun toEntity(draft: AlarmDraft, now: Instant): AlarmEntity = AlarmEntity(
|
||||
id = 0L,
|
||||
hour = draft.time.hour,
|
||||
minute = draft.time.minute,
|
||||
label = draft.label,
|
||||
enabled = draft.enabled,
|
||||
repeatDays = draft.repeatDays.mask,
|
||||
skipNextOccurrence = false,
|
||||
ringtoneUri = draft.ringtoneUri,
|
||||
vibrate = draft.vibrate,
|
||||
snoozeMinutes = draft.snoozeMinutes,
|
||||
snoozeLimit = draft.snoozeLimit,
|
||||
volumeRampSeconds = draft.volumeRampSeconds,
|
||||
dismissChallenge = draft.dismissChallenge?.name,
|
||||
createdAt = now.toEpochMilliseconds(),
|
||||
updatedAt = now.toEpochMilliseconds(),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package de.jeanlucmakiola.clockula.data.stopwatch
|
||||
|
||||
import de.jeanlucmakiola.clockula.domain.Lap
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object LapMapper {
|
||||
fun toDomain(entity: LapEntity): Lap = Lap(
|
||||
index = entity.lapIndex,
|
||||
split = entity.splitMillis.coerceAtLeast(0L).milliseconds,
|
||||
cumulative = entity.cumulativeMillis.coerceAtLeast(0L).milliseconds,
|
||||
)
|
||||
|
||||
fun toDomain(entities: List<LapEntity>): List<Lap> = entities.map(::toDomain)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.jeanlucmakiola.clockula.data.timers
|
||||
|
||||
import de.jeanlucmakiola.clockula.domain.Timer
|
||||
import de.jeanlucmakiola.clockula.domain.TimerDraft
|
||||
import de.jeanlucmakiola.clockula.domain.TimerState
|
||||
import de.jeanlucmakiola.floret.prefs.toEnum
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Instant
|
||||
|
||||
object TimerMapper {
|
||||
fun toDomain(entity: TimerEntity): Timer = Timer(
|
||||
id = entity.id,
|
||||
label = entity.label,
|
||||
duration = entity.durationMillis.coerceAtLeast(0L).milliseconds,
|
||||
state = entity.state.toEnum(TimerState.IDLE),
|
||||
remaining = entity.remainingMillis.coerceAtLeast(0L).milliseconds,
|
||||
startedAtElapsedRealtime = entity.startedAtElapsedRealtimeMillis?.milliseconds,
|
||||
endsAtElapsedRealtime = entity.endsAtElapsedRealtimeMillis?.milliseconds,
|
||||
endsAtWallClock = entity.endsAtWallClockMillis?.let(Instant::fromEpochMilliseconds),
|
||||
ringtoneUri = entity.ringtoneUri?.takeIf { it.isNotBlank() },
|
||||
sortOrder = entity.sortOrder,
|
||||
createdAt = Instant.fromEpochMilliseconds(entity.createdAt),
|
||||
updatedAt = Instant.fromEpochMilliseconds(entity.updatedAt),
|
||||
)
|
||||
|
||||
fun toDomain(entities: List<TimerEntity>): List<Timer> = entities.map(::toDomain)
|
||||
|
||||
fun toEntity(timer: Timer): TimerEntity = TimerEntity(
|
||||
id = timer.id,
|
||||
label = timer.label,
|
||||
durationMillis = timer.duration.inWholeMilliseconds,
|
||||
state = timer.state.name,
|
||||
remainingMillis = timer.remaining.inWholeMilliseconds,
|
||||
startedAtElapsedRealtimeMillis = timer.startedAtElapsedRealtime?.inWholeMilliseconds,
|
||||
endsAtElapsedRealtimeMillis = timer.endsAtElapsedRealtime?.inWholeMilliseconds,
|
||||
endsAtWallClockMillis = timer.endsAtWallClock?.toEpochMilliseconds(),
|
||||
ringtoneUri = timer.ringtoneUri,
|
||||
sortOrder = timer.sortOrder,
|
||||
createdAt = timer.createdAt.toEpochMilliseconds(),
|
||||
updatedAt = timer.updatedAt.toEpochMilliseconds(),
|
||||
)
|
||||
|
||||
fun toEntity(draft: TimerDraft, sortOrder: Int, now: Instant): TimerEntity {
|
||||
val duration = draft.duration.coerceAtLeast(Duration.ZERO)
|
||||
return TimerEntity(
|
||||
id = 0L,
|
||||
label = draft.label,
|
||||
durationMillis = duration.inWholeMilliseconds,
|
||||
state = TimerState.IDLE.name,
|
||||
remainingMillis = duration.inWholeMilliseconds,
|
||||
startedAtElapsedRealtimeMillis = null,
|
||||
endsAtElapsedRealtimeMillis = null,
|
||||
endsAtWallClockMillis = null,
|
||||
ringtoneUri = draft.ringtoneUri,
|
||||
sortOrder = sortOrder,
|
||||
createdAt = now.toEpochMilliseconds(),
|
||||
updatedAt = now.toEpochMilliseconds(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package de.jeanlucmakiola.clockula.data.worldclocks
|
||||
|
||||
import de.jeanlucmakiola.clockula.domain.WorldClock
|
||||
|
||||
object WorldClockMapper {
|
||||
/**
|
||||
* A zone id the device no longer knows is passed through untouched —
|
||||
* `WorldClock.isKnownZone` says so and M8 shows the row as broken. Blanking
|
||||
* it here would silently lose what the user chose.
|
||||
*/
|
||||
fun toDomain(entity: WorldClockEntity): WorldClock = WorldClock(
|
||||
id = entity.id,
|
||||
zoneId = entity.zoneId,
|
||||
label = entity.label?.takeIf { it.isNotBlank() },
|
||||
sortOrder = entity.sortOrder,
|
||||
)
|
||||
|
||||
fun toDomain(entities: List<WorldClockEntity>): List<WorldClock> = entities.map(::toDomain)
|
||||
|
||||
fun toEntity(clock: WorldClock): WorldClockEntity = WorldClockEntity(
|
||||
id = clock.id,
|
||||
zoneId = clock.zoneId,
|
||||
label = clock.label,
|
||||
sortOrder = clock.sortOrder,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package de.jeanlucmakiola.clockula.data.alarms
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.domain.Alarm
|
||||
import de.jeanlucmakiola.clockula.domain.AlarmDraft
|
||||
import de.jeanlucmakiola.clockula.domain.DismissChallenge
|
||||
import de.jeanlucmakiola.clockula.domain.RepeatDays
|
||||
import de.jeanlucmakiola.clockula.domain.TimeOfDay
|
||||
import de.jeanlucmakiola.clockula.testing.T0
|
||||
import de.jeanlucmakiola.clockula.testing.fullAlarm
|
||||
import de.jeanlucmakiola.clockula.testing.fullAlarmEntity
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.time.Instant
|
||||
|
||||
/**
|
||||
* The mapper is the single place a corrupt row is made harmless: reads degrade,
|
||||
* they never throw, because a throw inside a cursor read is a crashed list.
|
||||
*/
|
||||
class AlarmMapperTest {
|
||||
|
||||
@Test
|
||||
fun `a fully populated row maps field by field`() {
|
||||
val alarm = AlarmMapper.toDomain(fullAlarmEntity())
|
||||
|
||||
assertThat(alarm).isEqualTo(
|
||||
Alarm(
|
||||
id = 42L,
|
||||
time = TimeOfDay(7, 5),
|
||||
label = "Gym",
|
||||
enabled = true,
|
||||
repeatDays = RepeatDays.WEEKDAYS,
|
||||
skipNextOccurrence = true,
|
||||
ringtoneUri = "content://media/42",
|
||||
vibrate = false,
|
||||
snoozeMinutes = 7,
|
||||
snoozeLimit = 2,
|
||||
volumeRampSeconds = 30,
|
||||
dismissChallenge = DismissChallenge.MATH,
|
||||
createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000L),
|
||||
updatedAt = Instant.fromEpochMilliseconds(1_700_000_060_000L),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a row round-trips through the domain model`() {
|
||||
val entity = fullAlarmEntity()
|
||||
|
||||
assertThat(AlarmMapper.toEntity(AlarmMapper.toDomain(entity))).isEqualTo(entity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an alarm round-trips through the entity`() {
|
||||
val alarm = fullAlarm()
|
||||
|
||||
assertThat(AlarmMapper.toDomain(AlarmMapper.toEntity(alarm))).isEqualTo(alarm)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown dismiss challenge degrades to none`() {
|
||||
val entity = fullAlarmEntity().copy(dismissChallenge = "TELEPORT")
|
||||
|
||||
assertThat(AlarmMapper.toDomain(entity).dismissChallenge).isEqualTo(DismissChallenge.NONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an absent dismiss challenge stays absent`() {
|
||||
val entity = fullAlarmEntity().copy(dismissChallenge = null)
|
||||
|
||||
assertThat(AlarmMapper.toDomain(entity).dismissChallenge).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an empty dismiss challenge degrades to none`() {
|
||||
val entity = fullAlarmEntity().copy(dismissChallenge = "")
|
||||
|
||||
assertThat(AlarmMapper.toDomain(entity).dismissChallenge).isEqualTo(DismissChallenge.NONE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a corrupt repeat mask is sanitised`() {
|
||||
val entity = fullAlarmEntity().copy(repeatDays = 255)
|
||||
|
||||
assertThat(AlarmMapper.toDomain(entity).repeatDays.mask).isEqualTo(127)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an out-of-range time is clamped rather than thrown`() {
|
||||
val entity = fullAlarmEntity().copy(hour = 25, minute = 88)
|
||||
|
||||
assertThat(AlarmMapper.toDomain(entity).time).isEqualTo(TimeOfDay(23, 59))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a blank ringtone uri reads as absent`() {
|
||||
val entity = fullAlarmEntity().copy(ringtoneUri = "")
|
||||
|
||||
assertThat(AlarmMapper.toDomain(entity).ringtoneUri).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a draft becomes a new row stamped with the given instant`() {
|
||||
val draft = AlarmDraft(TimeOfDay(6, 0), label = "Work", repeatDays = RepeatDays.WEEKDAYS)
|
||||
|
||||
val entity = AlarmMapper.toEntity(draft, T0)
|
||||
|
||||
assertThat(entity).isEqualTo(
|
||||
AlarmEntity(
|
||||
id = 0L,
|
||||
hour = 6,
|
||||
minute = 0,
|
||||
label = "Work",
|
||||
enabled = true,
|
||||
repeatDays = 31,
|
||||
skipNextOccurrence = false,
|
||||
ringtoneUri = null,
|
||||
vibrate = null,
|
||||
snoozeMinutes = null,
|
||||
snoozeLimit = null,
|
||||
volumeRampSeconds = null,
|
||||
dismissChallenge = null,
|
||||
createdAt = T0.toEpochMilliseconds(),
|
||||
updatedAt = T0.toEpochMilliseconds(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.jeanlucmakiola.clockula.data.stopwatch
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.domain.Lap
|
||||
import de.jeanlucmakiola.clockula.testing.lapEntity
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class LapMapperTest {
|
||||
|
||||
@Test
|
||||
fun `a row maps field by field, without its row id`() {
|
||||
val lap = LapMapper.toDomain(lapEntity())
|
||||
|
||||
assertThat(lap).isEqualTo(Lap(index = 3, split = 1_234.milliseconds, cumulative = 5_678.milliseconds))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a list maps in the order given`() {
|
||||
val entities = listOf(
|
||||
LapEntity(id = 1L, lapIndex = 1, splitMillis = 1_000L, cumulativeMillis = 1_000L),
|
||||
LapEntity(id = 2L, lapIndex = 2, splitMillis = 2_000L, cumulativeMillis = 3_000L),
|
||||
LapEntity(id = 3L, lapIndex = 3, splitMillis = 3_000L, cumulativeMillis = 6_000L),
|
||||
)
|
||||
|
||||
val laps = LapMapper.toDomain(entities)
|
||||
|
||||
assertThat(laps.map { it.index }).containsExactly(1, 2, 3).inOrder()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `negative millis are clamped to zero`() {
|
||||
val lap = LapMapper.toDomain(lapEntity().copy(splitMillis = -1L, cumulativeMillis = -1L))
|
||||
|
||||
assertThat(lap.split).isEqualTo(Duration.ZERO)
|
||||
assertThat(lap.cumulative).isEqualTo(Duration.ZERO)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an empty list maps to an empty list`() {
|
||||
assertThat(LapMapper.toDomain(emptyList())).isEmpty()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package de.jeanlucmakiola.clockula.data.timers
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.domain.TimerDraft
|
||||
import de.jeanlucmakiola.clockula.domain.TimerState
|
||||
import de.jeanlucmakiola.clockula.testing.T0
|
||||
import de.jeanlucmakiola.clockula.testing.runningTimer
|
||||
import de.jeanlucmakiola.clockula.testing.runningTimerEntity
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class TimerMapperTest {
|
||||
|
||||
@Test
|
||||
fun `a running row maps field by field`() {
|
||||
val timer = TimerMapper.toDomain(runningTimerEntity())
|
||||
|
||||
assertThat(timer).isEqualTo(runningTimer())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a row round-trips through the domain model`() {
|
||||
val entity = runningTimerEntity()
|
||||
|
||||
assertThat(TimerMapper.toEntity(TimerMapper.toDomain(entity))).isEqualTo(entity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a timer round-trips through the entity`() {
|
||||
val timer = runningTimer()
|
||||
|
||||
assertThat(TimerMapper.toDomain(TimerMapper.toEntity(timer))).isEqualTo(timer)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an unknown state degrades to idle`() {
|
||||
val entity = runningTimerEntity().copy(state = "SPINNING")
|
||||
|
||||
val timer = TimerMapper.toDomain(entity)
|
||||
|
||||
assertThat(timer.state).isEqualTo(TimerState.IDLE)
|
||||
assertThat(timer.remaining).isEqualTo(200.seconds)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an idle row has no anchors and its full remaining time`() {
|
||||
val entity = runningTimerEntity().copy(
|
||||
state = "IDLE",
|
||||
remainingMillis = 300_000L,
|
||||
startedAtElapsedRealtimeMillis = null,
|
||||
endsAtElapsedRealtimeMillis = null,
|
||||
endsAtWallClockMillis = null,
|
||||
)
|
||||
|
||||
val timer = TimerMapper.toDomain(entity)
|
||||
|
||||
assertThat(timer.startedAtElapsedRealtime).isNull()
|
||||
assertThat(timer.endsAtElapsedRealtime).isNull()
|
||||
assertThat(timer.endsAtWallClock).isNull()
|
||||
assertThat(timer.remaining).isEqualTo(timer.duration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a negative duration is clamped to zero`() {
|
||||
val entity = runningTimerEntity().copy(durationMillis = -5L, remainingMillis = -5L)
|
||||
|
||||
val timer = TimerMapper.toDomain(entity)
|
||||
|
||||
assertThat(timer.duration).isEqualTo(Duration.ZERO)
|
||||
assertThat(timer.remaining).isEqualTo(Duration.ZERO)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a draft becomes a new idle row`() {
|
||||
val draft = TimerDraft(90.seconds, label = "Tea")
|
||||
|
||||
val entity = TimerMapper.toEntity(draft, sortOrder = 2, now = T0)
|
||||
|
||||
assertThat(entity).isEqualTo(
|
||||
TimerEntity(
|
||||
id = 0L,
|
||||
label = "Tea",
|
||||
durationMillis = 90_000L,
|
||||
state = "IDLE",
|
||||
remainingMillis = 90_000L,
|
||||
startedAtElapsedRealtimeMillis = null,
|
||||
endsAtElapsedRealtimeMillis = null,
|
||||
endsAtWallClockMillis = null,
|
||||
ringtoneUri = null,
|
||||
sortOrder = 2,
|
||||
createdAt = T0.toEpochMilliseconds(),
|
||||
updatedAt = T0.toEpochMilliseconds(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package de.jeanlucmakiola.clockula.data.worldclocks
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import de.jeanlucmakiola.clockula.domain.WorldClock
|
||||
import de.jeanlucmakiola.clockula.domain.isKnownZone
|
||||
import de.jeanlucmakiola.clockula.testing.worldClockEntity
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class WorldClockMapperTest {
|
||||
|
||||
@Test
|
||||
fun `a row maps field by field`() {
|
||||
val clock = WorldClockMapper.toDomain(worldClockEntity())
|
||||
|
||||
assertThat(clock).isEqualTo(WorldClock(3L, "Europe/Berlin", "Home", 2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a row round-trips through the domain model`() {
|
||||
val entity = worldClockEntity()
|
||||
|
||||
assertThat(WorldClockMapper.toEntity(WorldClockMapper.toDomain(entity))).isEqualTo(entity)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a blank label reads as absent`() {
|
||||
assertThat(WorldClockMapper.toDomain(worldClockEntity().copy(label = "")).label).isNull()
|
||||
assertThat(WorldClockMapper.toDomain(worldClockEntity().copy(label = " ")).label).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a zone id the device no longer knows is preserved, not blanked`() {
|
||||
val entity = worldClockEntity().copy(zoneId = "Mars/Phobos")
|
||||
|
||||
assertThat(WorldClockMapper.toDomain(entity).zoneId).isEqualTo("Mars/Phobos")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isKnownZone reflects the device tzdata`() {
|
||||
assertThat(WorldClock(1L, "Europe/Berlin", null, 0).isKnownZone).isTrue()
|
||||
assertThat(WorldClock(1L, "Mars/Phobos", null, 0).isKnownZone).isFalse()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package de.jeanlucmakiola.clockula.testing
|
||||
|
||||
import de.jeanlucmakiola.clockula.data.alarms.AlarmEntity
|
||||
import de.jeanlucmakiola.clockula.data.stopwatch.LapEntity
|
||||
import de.jeanlucmakiola.clockula.data.timers.TimerEntity
|
||||
import de.jeanlucmakiola.clockula.data.worldclocks.WorldClockEntity
|
||||
import de.jeanlucmakiola.clockula.domain.Alarm
|
||||
import de.jeanlucmakiola.clockula.domain.DismissChallenge
|
||||
import de.jeanlucmakiola.clockula.domain.RepeatDays
|
||||
import de.jeanlucmakiola.clockula.domain.TimeOfDay
|
||||
import de.jeanlucmakiola.clockula.domain.Timer
|
||||
import de.jeanlucmakiola.clockula.domain.TimerState
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.Instant
|
||||
|
||||
/** The one wall-clock instant every fixture is anchored on. */
|
||||
val T0: Instant = Instant.fromEpochMilliseconds(1_700_000_000_000L)
|
||||
|
||||
/**
|
||||
* Every field a distinct, non-default value, so a mapper that crosses two
|
||||
* fields over cannot pass by coincidence.
|
||||
*/
|
||||
fun fullAlarmEntity(): AlarmEntity = AlarmEntity(
|
||||
id = 42L,
|
||||
hour = 7,
|
||||
minute = 5,
|
||||
label = "Gym",
|
||||
enabled = true,
|
||||
repeatDays = 31,
|
||||
skipNextOccurrence = true,
|
||||
ringtoneUri = "content://media/42",
|
||||
vibrate = false,
|
||||
snoozeMinutes = 7,
|
||||
snoozeLimit = 2,
|
||||
volumeRampSeconds = 30,
|
||||
dismissChallenge = "MATH",
|
||||
createdAt = 1_700_000_000_000L,
|
||||
updatedAt = 1_700_000_060_000L,
|
||||
)
|
||||
|
||||
fun fullAlarm(): Alarm = Alarm(
|
||||
id = 42L,
|
||||
time = TimeOfDay(7, 5),
|
||||
label = "Gym",
|
||||
enabled = true,
|
||||
repeatDays = RepeatDays.WEEKDAYS,
|
||||
skipNextOccurrence = true,
|
||||
ringtoneUri = "content://media/42",
|
||||
vibrate = false,
|
||||
snoozeMinutes = 7,
|
||||
snoozeLimit = 2,
|
||||
volumeRampSeconds = 30,
|
||||
dismissChallenge = DismissChallenge.MATH,
|
||||
createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000L),
|
||||
updatedAt = Instant.fromEpochMilliseconds(1_700_000_060_000L),
|
||||
)
|
||||
|
||||
fun runningTimerEntity(): TimerEntity = TimerEntity(
|
||||
id = 7L,
|
||||
label = "Pasta",
|
||||
durationMillis = 300_000L,
|
||||
state = "RUNNING",
|
||||
remainingMillis = 200_000L,
|
||||
startedAtElapsedRealtimeMillis = 1_000_000L,
|
||||
endsAtElapsedRealtimeMillis = 1_300_000L,
|
||||
endsAtWallClockMillis = 1_700_000_300_000L,
|
||||
ringtoneUri = "content://media/7",
|
||||
sortOrder = 3,
|
||||
createdAt = 1_700_000_000_000L,
|
||||
updatedAt = 1_700_000_060_000L,
|
||||
)
|
||||
|
||||
fun runningTimer(): Timer = Timer(
|
||||
id = 7L,
|
||||
label = "Pasta",
|
||||
duration = 5.minutes,
|
||||
state = TimerState.RUNNING,
|
||||
remaining = 200.seconds,
|
||||
startedAtElapsedRealtime = 1_000.seconds,
|
||||
endsAtElapsedRealtime = 1_300.seconds,
|
||||
endsAtWallClock = Instant.fromEpochMilliseconds(1_700_000_300_000L),
|
||||
ringtoneUri = "content://media/7",
|
||||
sortOrder = 3,
|
||||
createdAt = Instant.fromEpochMilliseconds(1_700_000_000_000L),
|
||||
updatedAt = Instant.fromEpochMilliseconds(1_700_000_060_000L),
|
||||
)
|
||||
|
||||
fun worldClockEntity(): WorldClockEntity = WorldClockEntity(
|
||||
id = 3L,
|
||||
zoneId = "Europe/Berlin",
|
||||
label = "Home",
|
||||
sortOrder = 2,
|
||||
)
|
||||
|
||||
fun lapEntity(): LapEntity = LapEntity(
|
||||
id = 9L,
|
||||
lapIndex = 3,
|
||||
splitMillis = 1_234L,
|
||||
cumulativeMillis = 5_678L,
|
||||
)
|
||||
Reference in New Issue
Block a user