Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d9b38abb6 | ||
|
|
459432d065 |
No files matched your search
@@ -32,8 +32,7 @@ import java.util.Locale
|
||||
* - all-day, multi-day: "Tomorrow – 12 Jun 2026" (relative start, explicit end)
|
||||
*
|
||||
* All-day instances store UTC midnights with an exclusive end, so they are read
|
||||
* in UTC and the end day is the last *covered* day (issue #308). A timed event
|
||||
* from local midnight to local midnight reads like one too, in [zone] (#296).
|
||||
* in UTC and the end day is the last *covered* day (issue #308).
|
||||
*/
|
||||
fun reminderTimeText(
|
||||
beginMillis: Long,
|
||||
@@ -48,14 +47,11 @@ fun reminderTimeText(
|
||||
tomorrowLabel: String,
|
||||
yesterdayLabel: String,
|
||||
): String {
|
||||
val wholeDays = !isAllDay && endMillis > beginMillis &&
|
||||
isStartOfDay(beginMillis, zone) && isStartOfDay(endMillis, zone)
|
||||
if (isAllDay || wholeDays) {
|
||||
val dayZone = if (isAllDay) ZoneOffset.UTC else zone
|
||||
if (isAllDay) {
|
||||
val dateFormat = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale)
|
||||
// (atZone().toLocalDate() instead of LocalDate.ofInstant — API 34+)
|
||||
val firstDay = Instant.ofEpochMilli(beginMillis).atZone(dayZone).toLocalDate()
|
||||
val lastDay = Instant.ofEpochMilli(endMillis).atZone(dayZone).toLocalDate()
|
||||
val firstDay = Instant.ofEpochMilli(beginMillis).atZone(ZoneOffset.UTC).toLocalDate()
|
||||
val lastDay = Instant.ofEpochMilli(endMillis).atZone(ZoneOffset.UTC).toLocalDate()
|
||||
.minusDays(1)
|
||||
.coerceAtLeast(firstDay)
|
||||
val start = relativeDayPrefix(
|
||||
@@ -138,8 +134,3 @@ private fun LocalDate.previousOrSame(target: DayOfWeek): LocalDate {
|
||||
}
|
||||
|
||||
private const val RANGE = " – "
|
||||
|
||||
private fun isStartOfDay(millis: Long, zone: ZoneId): Boolean {
|
||||
val at = Instant.ofEpochMilli(millis).atZone(zone)
|
||||
return at.toLocalDate().atStartOfDay(zone).toInstant().toEpochMilli() == millis
|
||||
}
|
||||
@@ -6,20 +6,13 @@ import kotlinx.datetime.TimeZone
|
||||
fun EventInstance.displayPriority(zone: TimeZone): Int = when {
|
||||
isCrossedOut -> 3
|
||||
spansMultipleDays(zone) -> 0
|
||||
coversWholeDays(zone) -> 1
|
||||
isAllDay -> 1
|
||||
else -> 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared order for agenda and split day lists. Grid bars add their lane geometry.
|
||||
* Each event's priority is worked out once, not on every comparison.
|
||||
*/
|
||||
fun List<EventInstance>.sortedForDisplay(zone: TimeZone): List<EventInstance> =
|
||||
map { it to it.displayPriority(zone) }
|
||||
.sortedWith(
|
||||
compareBy<Pair<EventInstance, Int>> { it.second }
|
||||
.thenBy { it.first.start }
|
||||
.thenBy { it.first.title }
|
||||
.thenBy { it.first.instanceId },
|
||||
)
|
||||
.map { it.first }
|
||||
/** Shared order for agenda and split day lists. Grid bars add their lane geometry. */
|
||||
fun eventDisplayComparator(zone: TimeZone): Comparator<EventInstance> =
|
||||
compareBy<EventInstance> { it.displayPriority(zone) }
|
||||
.thenBy { it.start }
|
||||
.thenBy { it.title }
|
||||
.thenBy { it.instanceId }
|
||||
@@ -150,17 +150,6 @@ fun EventInstance.spanLastDay(zone: TimeZone): LocalDate {
|
||||
fun EventInstance.spansMultipleDays(zone: TimeZone): Boolean =
|
||||
spanFirstDay(zone) != spanLastDay(zone)
|
||||
|
||||
/**
|
||||
* Whether this event fills whole days in [zone], so the grids and lists lay it
|
||||
* out with the all-day events: an all-day event, or a timed one running from
|
||||
* local midnight to local midnight (#296). The event itself stays timed.
|
||||
*/
|
||||
fun EventInstance.coversWholeDays(zone: TimeZone): Boolean =
|
||||
isAllDay || (end > start && start.isStartOfDay(zone) && end.isStartOfDay(zone))
|
||||
|
||||
private fun Instant.isStartOfDay(zone: TimeZone): Boolean =
|
||||
this == toLocalDateTime(zone).date.atStartOfDayIn(zone)
|
||||
|
||||
data class EventDetail(
|
||||
val instance: EventInstance,
|
||||
val description: String?,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package de.jeanlucmakiola.calendula.ui.agenda
|
||||
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import de.jeanlucmakiola.calendula.domain.sortedForDisplay
|
||||
import de.jeanlucmakiola.calendula.domain.eventDisplayComparator
|
||||
import de.jeanlucmakiola.calendula.domain.FailureReason
|
||||
import de.jeanlucmakiola.calendula.domain.spanFirstDay
|
||||
import de.jeanlucmakiola.calendula.domain.spanLastDay
|
||||
@@ -19,7 +18,7 @@ import kotlin.time.Instant
|
||||
* surface only formats the instants into its own locale/24h string.
|
||||
*/
|
||||
sealed interface AgendaTimeLabel {
|
||||
/** An event filling whole days (#296), or a whole in-between day of a multi-day span. */
|
||||
/** An all-day event, or a whole in-between day of a multi-day span. */
|
||||
data object AllDay : AgendaTimeLabel
|
||||
/** The first day of a multi-day timed event: when it begins. */
|
||||
data class Starts(val start: Instant) : AgendaTimeLabel
|
||||
@@ -31,7 +30,7 @@ sealed interface AgendaTimeLabel {
|
||||
|
||||
/** The [AgendaTimeLabel] for [event] as it appears on [day], resolved in [zone]. */
|
||||
fun agendaTimeLabel(event: EventInstance, day: LocalDate, zone: TimeZone): AgendaTimeLabel {
|
||||
if (event.coversWholeDays(zone)) return AgendaTimeLabel.AllDay
|
||||
if (event.isAllDay) return AgendaTimeLabel.AllDay
|
||||
val firstDay = event.spanFirstDay(zone)
|
||||
val lastDay = event.spanLastDay(zone)
|
||||
return when {
|
||||
@@ -88,7 +87,7 @@ fun groupAgendaDays(
|
||||
return byDay.map { (date, dayEvents) ->
|
||||
AgendaDay(
|
||||
date = date,
|
||||
events = dayEvents.sortedForDisplay(zone),
|
||||
events = dayEvents.sortedWith(eventDisplayComparator(zone)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,21 +755,12 @@ class TimelineDragController {
|
||||
|
||||
/** Keep [landed]'s copy on the target as [settling] until [release]; [end] is a resize's. */
|
||||
private fun settle(landed: TimelineDrag, end: Instant? = null) {
|
||||
// Landed on midnight to midnight, the event moves to the all-day strip
|
||||
// (#296): no block will seat its copy or carry the selection.
|
||||
val leavesGrid = landed.eventSpanMin > 0 &&
|
||||
landed.eventStartMin.mod(MINUTES_PER_DAY) == 0 &&
|
||||
landed.eventSpanMin % MINUTES_PER_DAY == 0
|
||||
if (leavesGrid && !landed.event.isDraft && selection?.event?.instanceId == landed.event.instanceId) {
|
||||
selection = null
|
||||
selectionCovered = false
|
||||
}
|
||||
settlingSelected = selection?.event?.instanceId == landed.event.instanceId
|
||||
// An edge piece is a hint for the finger, not a preview of the landing
|
||||
// slot: the event belongs to a day this timeline has no column for, so
|
||||
// [noteGrid] can never seat it and it would sit at the top of today for
|
||||
// the whole settle timeout, long after the event moved off-screen.
|
||||
settling = if (landed.edgeOnly || leavesGrid) landed.copy(pieces = emptyList()) else landed
|
||||
settling = if (landed.edgeOnly) landed.copy(pieces = emptyList()) else landed
|
||||
liftedInstanceId = landed.event.instanceId
|
||||
settledOrigin = landed.event.eventId to landed.event.start
|
||||
settledAbandonedMark = abandonedTicks?.value ?: 0
|
||||
|
||||
@@ -4,7 +4,6 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import de.jeanlucmakiola.calendula.ui.common.formatTimeOfDay
|
||||
import de.jeanlucmakiola.calendula.ui.common.rememberInlineTimeWidth
|
||||
import kotlinx.datetime.TimeZone
|
||||
@@ -12,8 +11,8 @@ import java.util.Locale
|
||||
|
||||
/**
|
||||
* The start time a chip shows before its title (#219), or null when it has none
|
||||
* to show: a chip filling whole days never carries a time (#296), and neither
|
||||
* does a bar carried in from an earlier week, whose start is not in this segment.
|
||||
* to show: an all-day chip never carries a time, and neither does a bar carried
|
||||
* in from an earlier week, whose start is not in this segment.
|
||||
*/
|
||||
internal fun monthChipTime(
|
||||
event: EventInstance,
|
||||
@@ -22,7 +21,7 @@ internal fun monthChipTime(
|
||||
is24Hour: Boolean,
|
||||
locale: Locale,
|
||||
): String? {
|
||||
if (event.coversWholeDays(zone) || continuesLeft) return null
|
||||
if (event.isAllDay || continuesLeft) return null
|
||||
return formatTimeOfDay(event.start, zone, is24Hour, locale)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import de.jeanlucmakiola.calendula.data.time.currentDate
|
||||
import de.jeanlucmakiola.calendula.data.time.stateFollowingDays
|
||||
import de.jeanlucmakiola.calendula.domain.CalendarSource
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import de.jeanlucmakiola.calendula.domain.FailureReason
|
||||
import de.jeanlucmakiola.calendula.domain.calendarListFailure
|
||||
import de.jeanlucmakiola.calendula.ui.agenda.AgendaDay
|
||||
@@ -21,7 +20,7 @@ import de.jeanlucmakiola.calendula.ui.agenda.anchorTodayIfMissing
|
||||
import de.jeanlucmakiola.calendula.ui.common.CalendarSpan
|
||||
import de.jeanlucmakiola.calendula.ui.common.PageStateCache
|
||||
import de.jeanlucmakiola.calendula.domain.isCrossedOut
|
||||
import de.jeanlucmakiola.calendula.domain.sortedForDisplay
|
||||
import de.jeanlucmakiola.calendula.domain.eventDisplayComparator
|
||||
import de.jeanlucmakiola.calendula.ui.week.coversDay
|
||||
import de.jeanlucmakiola.calendula.ui.week.layoutAllDay
|
||||
import de.jeanlucmakiola.calendula.ui.week.spansMultipleDays
|
||||
@@ -492,7 +491,7 @@ internal fun layoutCalendarWeek(
|
||||
// crossing midnight into the next week row covers a single day in each, and
|
||||
// counting per row drew it as two unrelated pills (#253).
|
||||
val (bars, singles) = weekEvents.partition { ev ->
|
||||
ev.coversWholeDays(zone) || ev.spansMultipleDays(zone)
|
||||
ev.isAllDay || ev.spansMultipleDays(zone)
|
||||
}
|
||||
val spans = layoutAllDay(bars, days, zone).map { s ->
|
||||
MonthSpan(
|
||||
@@ -532,7 +531,7 @@ internal fun instancesByDay(
|
||||
days.associateWith { day ->
|
||||
instances
|
||||
.filter { it.coversDay(day, zone) }
|
||||
.sortedForDisplay(zone)
|
||||
.sortedWith(eventDisplayComparator(zone))
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,6 @@ import de.jeanlucmakiola.calendula.data.time.currentDate
|
||||
import de.jeanlucmakiola.calendula.data.time.stateFollowingDays
|
||||
import de.jeanlucmakiola.calendula.domain.CalendarSource
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import de.jeanlucmakiola.calendula.domain.displayPriority
|
||||
import de.jeanlucmakiola.calendula.domain.FailureReason
|
||||
import de.jeanlucmakiola.calendula.domain.calendarListFailure
|
||||
@@ -207,7 +206,8 @@ abstract class PagedTimelineViewModel(
|
||||
): WeekUiState {
|
||||
calendarListFailure(calendars)?.let { return WeekUiState.Failure(it) }
|
||||
val days = (0 until span.count).map { span.start.plus(it, DateTimeUnit.DAY) }
|
||||
val (allDay, timed) = instances.partition { it.coversWholeDays(zone) }
|
||||
val allDay = instances.filter { it.isAllDay }
|
||||
val timed = instances.filterNot { it.isAllDay }
|
||||
return WeekUiState.Success(
|
||||
weekStart = span.start,
|
||||
today = today,
|
||||
@@ -292,17 +292,17 @@ internal fun layoutAllDay(
|
||||
days: List<LocalDate>,
|
||||
zone: TimeZone,
|
||||
): List<AllDaySpan> {
|
||||
data class Raw(val event: EventInstance, val startCol: Int, val endCol: Int, val priority: Int)
|
||||
data class Raw(val event: EventInstance, val startCol: Int, val endCol: Int)
|
||||
|
||||
val raw = events
|
||||
.mapNotNull { ev ->
|
||||
val covered = days.indices.filter { ev.coversDay(days[it], zone) }
|
||||
if (covered.isEmpty()) null else Raw(ev, covered.first(), covered.last(), ev.displayPriority(zone))
|
||||
if (covered.isEmpty()) null else Raw(ev, covered.first(), covered.last())
|
||||
}
|
||||
// Declined bars are packed after every other one, so on any day they
|
||||
// cover they land in a lane below it (#230).
|
||||
.sortedWith(
|
||||
compareBy<Raw> { it.priority }
|
||||
compareBy<Raw> { it.event.displayPriority(zone) }
|
||||
.thenBy { it.startCol }
|
||||
.thenBy { it.endCol }
|
||||
.thenBy { it.event.start }
|
||||
@@ -444,7 +444,7 @@ internal fun layoutDay(
|
||||
data class Raw(val event: EventInstance, val startMin: Int, val endMin: Int)
|
||||
|
||||
val raw = events.asSequence()
|
||||
.filterNot { it.coversWholeDays(zone) }
|
||||
.filterNot { it.isAllDay }
|
||||
.mapNotNull { ev ->
|
||||
if (ev.start == ev.end) {
|
||||
// Instant event: keep only if the point falls inside this day.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package de.jeanlucmakiola.calendula.ui.week
|
||||
|
||||
import de.jeanlucmakiola.calendula.ui.common.startOfWeek
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -81,7 +80,7 @@ private fun sampleWeekState(
|
||||
weekStart = start,
|
||||
today = today,
|
||||
days = days,
|
||||
allDaySpans = layoutAllDay(events.filter { it.coversWholeDays(zone) }, days, zone),
|
||||
allDaySpans = layoutAllDay(events.filter { it.isAllDay }, days, zone),
|
||||
timedByDay = days.associateWith { layoutDay(events, it, zone) },
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package de.jeanlucmakiola.calendula.widget.countdown
|
||||
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.calendula.domain.EventStatus
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import de.jeanlucmakiola.calendula.domain.isCrossedOut
|
||||
import kotlinx.datetime.DateTimeUnit
|
||||
import kotlinx.datetime.TimeZone
|
||||
@@ -76,13 +75,12 @@ fun nextLabelChange(event: EventInstance, now: Instant, zone: TimeZone): Instant
|
||||
}
|
||||
|
||||
/**
|
||||
* The next-event mode's pick: timed and not filling whole days (#296), not
|
||||
* declined or cancelled, not over; of those, the one whose countdown changes
|
||||
* soonest — a running event's end, else a start.
|
||||
* The next-event mode's pick: timed, not declined or cancelled, not over; of those, the one
|
||||
* whose countdown changes soonest — a running event's end, else a start.
|
||||
*/
|
||||
fun pickNextEvent(instances: List<EventInstance>, now: Instant, zone: TimeZone): EventInstance? =
|
||||
fun pickNextEvent(instances: List<EventInstance>, now: Instant): EventInstance? =
|
||||
instances
|
||||
.filter { !it.coversWholeDays(zone) && !it.isCrossedOut && it.end > now }
|
||||
.filter { !it.isAllDay && !it.isCrossedOut && it.end > now }
|
||||
.minByOrNull { target(it, now) }
|
||||
|
||||
/** A pinned event's occurrence to count to: the running or next one that is not cancelled (#388). */
|
||||
|
||||
@@ -75,11 +75,7 @@ internal suspend fun Context.loadCountdown(
|
||||
val repository = widgetEntryPoint().calendarRepository()
|
||||
val event = when (options.mode) {
|
||||
CountdownMode.NEXT ->
|
||||
pickNextEvent(
|
||||
repository.instances(now..now + NextEventHorizon).first().withoutCalendars(load.hiddenCalendars),
|
||||
now,
|
||||
systemZone(),
|
||||
)
|
||||
pickNextEvent(repository.instances(now..now + NextEventHorizon).first().withoutCalendars(load.hiddenCalendars), now)
|
||||
CountdownMode.PINNED -> options.eventId?.let { id ->
|
||||
pickPinnedOccurrence(repository.eventOccurrences(id, now - 1.days..now + PinnedHorizon), now, systemZone())
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import androidx.glance.text.TextStyle
|
||||
import de.jeanlucmakiola.calendula.MainActivity
|
||||
import de.jeanlucmakiola.calendula.R
|
||||
import de.jeanlucmakiola.calendula.domain.EventInstance
|
||||
import de.jeanlucmakiola.calendula.domain.coversWholeDays
|
||||
import de.jeanlucmakiola.calendula.ui.common.CalendarView
|
||||
import de.jeanlucmakiola.calendula.ui.common.eventAccent
|
||||
import de.jeanlucmakiola.calendula.widget.CalendulaWidgetReceiver
|
||||
@@ -211,13 +210,12 @@ private fun whenText(context: Context, event: EventInstance, data: CountdownData
|
||||
val at = if (running) counted.end else counted.start
|
||||
val date = at.toLocalDateTime(systemZone()).date
|
||||
val day = relativeDayLabel(context, date, data.today)
|
||||
val wholeDays = event.coversWholeDays(systemZone())
|
||||
return when {
|
||||
wholeDays && running -> context.getString(
|
||||
event.isAllDay && running -> context.getString(
|
||||
R.string.widget_countdown_until,
|
||||
relativeDayLabel(context, date.minus(1, DateTimeUnit.DAY), data.today),
|
||||
)
|
||||
wholeDays -> "$day · ${context.getString(R.string.event_detail_all_day)}"
|
||||
event.isAllDay -> "$day · ${context.getString(R.string.event_detail_all_day)}"
|
||||
running -> context.getString(R.string.agenda_span_ends, "$day · ${formatWidgetTime(at, data.is24Hour)}")
|
||||
else -> "$day · ${formatWidgetTime(at, data.is24Hour)}"
|
||||
}
|
||||
|
||||
@@ -212,30 +212,4 @@ class ReminderTimeTextTest {
|
||||
)
|
||||
assertThat(text).isEqualTo("Today")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a timed event from local midnight to midnight reads like an all-day one`() {
|
||||
val oneDay = text(
|
||||
beginMillis = millisAt(LocalDateTime.of(2026, 6, 12, 0, 0), berlin),
|
||||
endMillis = millisAt(LocalDateTime.of(2026, 6, 13, 0, 0), berlin),
|
||||
today = LocalDate.of(2026, 6, 11),
|
||||
)
|
||||
assertThat(oneDay).isEqualTo("Tomorrow")
|
||||
val twoDays = text(
|
||||
beginMillis = millisAt(LocalDateTime.of(2026, 6, 11, 0, 0), berlin),
|
||||
endMillis = millisAt(LocalDateTime.of(2026, 6, 13, 0, 0), berlin),
|
||||
)
|
||||
assertThat(twoDays).startsWith("Today – ")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `midnight is read in the device zone`() {
|
||||
// Midnight in Berlin is 22:00 the day before in UTC, so it is timed there.
|
||||
val text = text(
|
||||
beginMillis = millisAt(LocalDateTime.of(2026, 6, 12, 0, 0), berlin),
|
||||
endMillis = millisAt(LocalDateTime.of(2026, 6, 13, 0, 0), berlin),
|
||||
zone = ZoneOffset.UTC,
|
||||
)
|
||||
assertThat(text).contains("22:00")
|
||||
}
|
||||
}
|
||||
@@ -82,39 +82,4 @@ class EventInstanceSpanTest {
|
||||
val timed = instance(utc(2026, 7, 19, 12), utc(2026, 7, 19, 13), isAllDay = false)
|
||||
assertThat(timed.dateZone(newYork)).isEqualTo(newYork)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a timed event from local midnight to local midnight covers whole days`() {
|
||||
// Stored as 22:00Z, the shape ICSx5 gives a date-only feed event (#296).
|
||||
val oneDay = instance(utc(2026, 7, 18, 22), utc(2026, 7, 19, 22), isAllDay = false)
|
||||
val twoDays = instance(utc(2026, 7, 18, 22), utc(2026, 7, 20, 22), isAllDay = false)
|
||||
assertThat(oneDay.coversWholeDays(berlin)).isTrue()
|
||||
assertThat(twoDays.coversWholeDays(berlin)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `local midnight is read in the device zone`() {
|
||||
val event = instance(utc(2026, 7, 18, 22), utc(2026, 7, 19, 22), isAllDay = false)
|
||||
assertThat(event.coversWholeDays(newYork)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a timed event off midnight at either end does not cover whole days`() {
|
||||
assertThat(instance(utc(2026, 7, 19), utc(2026, 7, 19, 23), isAllDay = false).coversWholeDays(TimeZone.UTC))
|
||||
.isFalse()
|
||||
assertThat(instance(utc(2026, 7, 19, 1), utc(2026, 7, 20), isAllDay = false).coversWholeDays(TimeZone.UTC))
|
||||
.isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a zero-length event at midnight does not cover whole days`() {
|
||||
assertThat(instance(utc(2026, 7, 19), utc(2026, 7, 19), isAllDay = false).coversWholeDays(TimeZone.UTC))
|
||||
.isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an all-day event covers whole days in any zone`() {
|
||||
assertThat(allDayJul19().coversWholeDays(newYork)).isTrue()
|
||||
assertThat(allDayJul19().coversWholeDays(berlin)).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -65,14 +65,4 @@ class AgendaTimeLabelTest {
|
||||
assertThat(labelOn(2026, 7, 1, e)).isEqualTo(AgendaTimeLabel.AllDay)
|
||||
assertThat(labelOn(2026, 7, 2, e)).isEqualTo(AgendaTimeLabel.Ends(e.end))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a timed event from midnight to midnight is all-day`() {
|
||||
val oneDay = event(at(2026, 7, 2), at(2026, 7, 3))
|
||||
val twoDays = event(at(2026, 7, 2), at(2026, 7, 4))
|
||||
|
||||
assertThat(labelOn(2026, 7, 2, oneDay)).isEqualTo(AgendaTimeLabel.AllDay)
|
||||
assertThat(labelOn(2026, 7, 2, twoDays)).isEqualTo(AgendaTimeLabel.AllDay)
|
||||
assertThat(labelOn(2026, 7, 3, twoDays)).isEqualTo(AgendaTimeLabel.AllDay)
|
||||
}
|
||||
}
|
||||
@@ -321,18 +321,4 @@ class MonthLayoutTest {
|
||||
private fun declinedTimed(date: LocalDate) =
|
||||
timed(date, 7, 8, id = 4L, title = "Declined early")
|
||||
.copy(response = EventResponse.Declined)
|
||||
|
||||
@Test
|
||||
fun `a timed event from midnight to midnight is a bar, not a timed chip`() {
|
||||
val ev = timedEvent(
|
||||
LocalDate(2026, 6, 10), 0, 0, zone = zone, id = 3L,
|
||||
endDate = LocalDate(2026, 6, 11),
|
||||
)
|
||||
val week = layoutCalendarWeek(weekOf8th, listOf(ev), zone)
|
||||
|
||||
assertThat(week.spans.map { it.event }).containsExactly(ev)
|
||||
assertThat(week.spans.single().startCol).isEqualTo(2)
|
||||
assertThat(week.spans.single().endCol).isEqualTo(2)
|
||||
assertThat(week.timedByDay.values.flatten()).isEmpty()
|
||||
}
|
||||
}
|
||||
@@ -300,11 +300,4 @@ class WeekLayoutTest {
|
||||
val second = event(Instant.parse("2026-10-25T01:10:00Z"), Instant.parse("2026-10-25T01:40:00Z"))
|
||||
assertThat(layoutDay(listOf(second), day, brussels).single().endMin).isEqualTo(2 * 60 + 40)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a timed event from midnight to midnight stays out of the time grid`() {
|
||||
val ev = testEvent(start = at(wed, 0), end = at(wed.plus(1, DateTimeUnit.DAY), 0))
|
||||
|
||||
assertThat(layoutDay(listOf(ev), wed, zone)).isEmpty()
|
||||
}
|
||||
}
|
||||
@@ -71,16 +71,9 @@ class CountdownTest {
|
||||
val over = event(now - 2.hours, now - 1.hours, id = 3)
|
||||
val later = event(now + 3.hours, id = 4)
|
||||
val running = event(now - 1.hours, now + 2.hours, id = 5)
|
||||
assertThat(pickNextEvent(listOf(allDay, declined, over, later), now, zone)?.eventId).isEqualTo(4)
|
||||
assertThat(pickNextEvent(listOf(allDay, declined, over, later), now)?.eventId).isEqualTo(4)
|
||||
// A running event ending before the next one starts comes first.
|
||||
assertThat(pickNextEvent(listOf(later, running), now, zone)?.eventId).isEqualTo(5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `next-event mode skips a timed event filling whole days`() {
|
||||
val wholeDay = event(Instant.parse("2026-10-22T00:00:00Z"), Instant.parse("2026-10-23T00:00:00Z"), id = 1)
|
||||
val later = event(now + 30.hours, id = 2)
|
||||
assertThat(pickNextEvent(listOf(wholeDay, later), now, zone)?.eventId).isEqualTo(2)
|
||||
assertThat(pickNextEvent(listOf(later, running), now)?.eventId).isEqualTo(5)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in new issue
Block a user