From 4041a56ac8bb72417a2cb49bf5c47be3779d1167 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Fri, 16 Oct 2026 13:23:12 +0200 Subject: [PATCH] One dayRange and startOfWeek for the view models --- .../calendula/ui/agenda/AgendaViewModel.kt | 11 ++----- .../calendula/ui/common/DayRange.kt | 22 +++++++++++++ .../calendula/ui/month/MonthViewModel.kt | 32 +++++++------------ .../calendula/ui/week/WeekPaging.kt | 1 + .../calendula/ui/week/WeekViewModel.kt | 17 ++-------- .../calendula/ui/week/WeekViewPreview.kt | 1 + .../ui/month/ContinuousMonthIndexTest.kt | 3 +- .../calendula/ui/month/MonthLayoutTest.kt | 9 +++--- .../calendula/ui/week/WeekLayoutTest.kt | 1 + .../calendula/ui/week/WeekPagingTest.kt | 1 + 10 files changed, 50 insertions(+), 48 deletions(-) create mode 100644 app/src/main/java/de/jeanlucmakiola/calendula/ui/common/DayRange.kt diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaViewModel.kt index 4915f16..aee15c6 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/agenda/AgendaViewModel.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.agenda +import de.jeanlucmakiola.calendula.ui.common.dayRange import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -31,11 +32,8 @@ import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate import kotlinx.datetime.TimeZone -import kotlinx.datetime.atStartOfDayIn -import kotlinx.datetime.atTime import kotlinx.datetime.minus import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import kotlin.time.Instant import javax.inject.Inject @@ -243,8 +241,5 @@ class AgendaViewModel @Inject constructor( internal const val PAST_CHUNK_DAYS = 30 /** Inclusive instant range from the start of [anchor] through [days] days ahead. */ -internal fun agendaRange(anchor: LocalDate, days: Int, zone: TimeZone): ClosedRange { - val from = anchor.atStartOfDayIn(zone) - val to = anchor.plus(days, DateTimeUnit.DAY).atTime(23, 59, 59).toInstant(zone) - return from..to -} +internal fun agendaRange(anchor: LocalDate, days: Int, zone: TimeZone): ClosedRange = + dayRange(anchor, anchor.plus(days, DateTimeUnit.DAY), zone) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/DayRange.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/DayRange.kt new file mode 100644 index 0000000..27ea2d0 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/DayRange.kt @@ -0,0 +1,22 @@ +package de.jeanlucmakiola.calendula.ui.common + +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.DayOfWeek +import kotlinx.datetime.LocalDate +import kotlinx.datetime.TimeZone +import kotlinx.datetime.atStartOfDayIn +import kotlinx.datetime.atTime +import kotlinx.datetime.minus +import kotlinx.datetime.toInstant +import kotlin.time.Instant + +/** Instant range from the start of [first] through the last second of [last] in [zone]. */ +internal fun dayRange(first: LocalDate, last: LocalDate, zone: TimeZone): ClosedRange = + first.atStartOfDayIn(zone)..last.atTime(23, 59, 59).toInstant(zone) + +/** Beginning of the week (at [weekStart]) that contains this date. */ +internal fun LocalDate.startOfWeek(weekStart: DayOfWeek): LocalDate { + // DayOfWeek.ordinal: MONDAY=0..SUNDAY=6 → identical to ISO ordering. + val offset = ((dayOfWeek.ordinal - weekStart.ordinal) + 7) % 7 + return minus(offset, DateTimeUnit.DAY) +} diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthViewModel.kt index f59c4e3..71a757b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/month/MonthViewModel.kt @@ -1,5 +1,7 @@ package de.jeanlucmakiola.calendula.ui.month +import de.jeanlucmakiola.calendula.ui.common.startOfWeek +import de.jeanlucmakiola.calendula.ui.common.dayRange import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -39,12 +41,9 @@ import kotlinx.datetime.LocalDate import kotlinx.datetime.Month import kotlinx.datetime.TimeZone import kotlinx.datetime.YearMonth -import kotlinx.datetime.atStartOfDayIn -import kotlinx.datetime.atTime import kotlinx.datetime.daysUntil import kotlinx.datetime.minus import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime import kotlin.time.Instant import javax.inject.Inject @@ -202,13 +201,13 @@ class MonthViewModel @Inject constructor( // Widened to whole grid weeks at both ends: a month block still // has to know about an event that starts in the boundary week's // clipped-off days, or a bar running into the block would vanish. - val first = firstOfMonth(yearMonthForIndex(window.first)).startOfGridWeek(ws) + val first = firstOfMonth(yearMonthForIndex(window.first)).startOfWeek(ws) val last = firstOfMonth(yearMonthForIndex(window.last)) .plus(1, DateTimeUnit.MONTH) .minus(1, DateTimeUnit.DAY) - .startOfGridWeek(ws) + .startOfWeek(ws) .plus(6, DateTimeUnit.DAY) - val range = first.atStartOfDayIn(zone)..last.atTime(23, 59, 59).toInstant(zone) + val range = dayRange(first, last, zone) combine( repository.calendars(), repository.instances(range), @@ -408,7 +407,7 @@ internal fun layoutMonthWeeks( byDay: DayIndex, zone: TimeZone, ): List { - val gridStart = firstOfMonth(ym).startOfGridWeek(weekStart) + val gridStart = firstOfMonth(ym).startOfWeek(weekStart) val weekCount = weekRowsInMonth(ym, weekStart) return (0 until weekCount).map { row -> @@ -530,11 +529,8 @@ internal fun monthGridRange( zone: TimeZone, ): ClosedRange { val firstOfMonth = LocalDate(ym.year, ym.month, 1) - val gridStart = firstOfMonth.startOfGridWeek(weekStart) - val gridEnd = gridStart.plus(41, DateTimeUnit.DAY) - val start = gridStart.atStartOfDayIn(zone) - val end = gridEnd.atTime(23, 59, 59).toInstant(zone) - return start..end + val gridStart = firstOfMonth.startOfWeek(weekStart) + return dayRange(gridStart, gridStart.plus(41, DateTimeUnit.DAY), zone) } /** @@ -646,12 +642,12 @@ private val WEEK_INDEX_EPOCH = LocalDate(1900, 1, 1) private val WEEK_INDEX_END = LocalDate(2100, 12, 31) internal fun weekIndexOf(date: LocalDate, weekStart: DayOfWeek): Int { - val base = WEEK_INDEX_EPOCH.startOfGridWeek(weekStart) - return base.daysUntil(date.startOfGridWeek(weekStart)) / 7 + val base = WEEK_INDEX_EPOCH.startOfWeek(weekStart) + return base.daysUntil(date.startOfWeek(weekStart)) / 7 } internal fun weekStartForIndex(index: Int, weekStart: DayOfWeek): LocalDate = - WEEK_INDEX_EPOCH.startOfGridWeek(weekStart).plus(index * 7, DateTimeUnit.DAY) + WEEK_INDEX_EPOCH.startOfWeek(weekStart).plus(index * 7, DateTimeUnit.DAY) /** Total weeks the Dense grid scrolls through (1900 → 2100). */ internal fun continuousWeekCount(weekStart: DayOfWeek): Int = @@ -727,9 +723,3 @@ internal fun clipWeekToMonth(week: MonthWeek, month: YearMonth): MonthWeek { countByDay = week.countByDay.filterKeys { it in own }, ) } - -internal fun LocalDate.startOfGridWeek(weekStart: DayOfWeek): LocalDate { - // DayOfWeek.ordinal: MONDAY=0..SUNDAY=6 → identical to ISO ordering. - val offset = ((dayOfWeek.ordinal - weekStart.ordinal) + 7) % 7 - return minus(offset, DateTimeUnit.DAY) -} diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekPaging.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekPaging.kt index e19ed1e..68c08ab 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekPaging.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekPaging.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.week +import de.jeanlucmakiola.calendula.ui.common.startOfWeek import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek import kotlinx.datetime.LocalDate diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewModel.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewModel.kt index 5fcbe92..3655532 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewModel.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewModel.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.week +import de.jeanlucmakiola.calendula.ui.common.dayRange import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel @@ -41,11 +42,9 @@ import kotlinx.datetime.TimeZone import kotlinx.datetime.toKotlinUtcOffset import kotlinx.datetime.toJavaZoneId import kotlinx.datetime.atStartOfDayIn -import kotlinx.datetime.atTime import kotlinx.datetime.daysUntil import kotlinx.datetime.minus import kotlinx.datetime.plus -import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime import java.util.Locale import kotlin.time.Instant @@ -342,19 +341,9 @@ internal fun layoutAllDay( private infix fun IntRange.overlaps(other: IntRange): Boolean = first <= other.last && other.first <= last -/** Beginning of the week (at [weekStart]) that contains this date. */ -internal fun LocalDate.startOfWeek(weekStart: DayOfWeek): LocalDate { - // DayOfWeek.ordinal: MONDAY=0..SUNDAY=6 → identical to ISO ordering. - val offset = ((dayOfWeek.ordinal - weekStart.ordinal) + 7) % 7 - return minus(offset, DateTimeUnit.DAY) -} - /** Instant range covering the [count] days starting at [start]. */ -internal fun spanRange(start: LocalDate, count: Int, zone: TimeZone): ClosedRange { - val from = start.atStartOfDayIn(zone) - val to = start.plus(count - 1, DateTimeUnit.DAY).atTime(23, 59, 59).toInstant(zone) - return from..to -} +internal fun spanRange(start: LocalDate, count: Int, zone: TimeZone): ClosedRange = + dayRange(start, start.plus(count - 1, DateTimeUnit.DAY), zone) /** * This instant's wall-clock time as minutes past [day]'s midnight in [zone] — diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt index 4936177..d9790ce 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekViewPreview.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.week +import de.jeanlucmakiola.calendula.ui.common.startOfWeek import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ContinuousMonthIndexTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ContinuousMonthIndexTest.kt index 71e9df2..e939b9b 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ContinuousMonthIndexTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/ContinuousMonthIndexTest.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.month +import de.jeanlucmakiola.calendula.ui.common.startOfWeek import com.google.common.truth.Truth.assertThat import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek @@ -152,7 +153,7 @@ class ContinuousMonthIndexTest { val wed = LocalDate(2026, 6, 10) DayOfWeek.entries.forEach { ws -> val index = weekIndexOf(wed, ws) - assertThat(weekStartForIndex(index, ws)).isEqualTo(wed.startOfGridWeek(ws)) + assertThat(weekStartForIndex(index, ws)).isEqualTo(wed.startOfWeek(ws)) assertThat(weekIndexOf(LocalDate(1900, 1, 1), ws)).isAtLeast(0) assertThat(index).isLessThan(continuousWeekCount(ws)) assertThat(weekIndexOf(LocalDate(2100, 12, 31), ws)) diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt index c8a63f1..54b1df5 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/month/MonthLayoutTest.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.month +import de.jeanlucmakiola.calendula.ui.common.startOfWeek import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.allDayEvent import de.jeanlucmakiola.calendula.domain.EventResponse @@ -51,12 +52,12 @@ class MonthLayoutTest { ) = allDayEvent(from, toInclusive, id = id, title = title, color = 0xFF445566.toInt()) @Test - fun `startOfGridWeek snaps back to the configured week start`() { + fun `startOfWeek snaps back to the configured week start`() { val wed = LocalDate(2026, 6, 10) - assertThat(wed.startOfGridWeek(DayOfWeek.MONDAY)).isEqualTo(jun8) - assertThat(jun8.startOfGridWeek(DayOfWeek.MONDAY)).isEqualTo(jun8) + assertThat(wed.startOfWeek(DayOfWeek.MONDAY)).isEqualTo(jun8) + assertThat(jun8.startOfWeek(DayOfWeek.MONDAY)).isEqualTo(jun8) // A Sunday-anchored week containing the 10th starts on the 7th. - assertThat(wed.startOfGridWeek(DayOfWeek.SUNDAY)).isEqualTo(LocalDate(2026, 6, 7)) + assertThat(wed.startOfWeek(DayOfWeek.SUNDAY)).isEqualTo(LocalDate(2026, 6, 7)) } @Test diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt index c47c8c6..b37a2d6 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekLayoutTest.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.week +import de.jeanlucmakiola.calendula.ui.common.startOfWeek import com.google.common.truth.Truth.assertThat import de.jeanlucmakiola.calendula.testEvent import kotlinx.datetime.DateTimeUnit diff --git a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekPagingTest.kt b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekPagingTest.kt index 0480c9f..9199ec4 100644 --- a/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekPagingTest.kt +++ b/app/src/test/java/de/jeanlucmakiola/calendula/ui/week/WeekPagingTest.kt @@ -1,5 +1,6 @@ package de.jeanlucmakiola.calendula.ui.week +import de.jeanlucmakiola.calendula.ui.common.startOfWeek import com.google.common.truth.Truth.assertThat import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.DayOfWeek