From ec7b696eb9aa2c425b03888a428c2b84caa97e37 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Sat, 27 Jun 2026 23:27:49 +0200 Subject: [PATCH] Draw core-time from floret-kit via submodule + composite build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 of the shared-kit plan: floret-kit is embedded as a git submodule and wired in with Gradle includeBuild("floret-kit"), so the app depends on de.jeanlucmakiola.floret:core-time and Gradle substitutes the local source module (built from source — F-Droid-safe). Proves the submodule + composite-build pipeline before any design layers move. - Remove app copies of DayWindow + Instant date/time formatting (now in core-time); repoint imports to de.jeanlucmakiola.floret.time. - DateTimeField gains an explicit import (was same-package). - CI checkouts fetch submodules recursively. - Clean composite build + unit tests + debug assemble green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitmodules | 3 ++ .../agendula/domain/DayWindow.kt | 18 ---------- .../agendula/ui/common/DateTimeFormat.kt | 28 --------------- .../agendula/domain/DayWindowTest.kt | 35 ------------------- floret-kit | 1 + 5 files changed, 4 insertions(+), 81 deletions(-) create mode 100644 .gitmodules delete mode 100644 app/src/main/java/de/jeanlucmakiola/agendula/domain/DayWindow.kt delete mode 100644 app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeFormat.kt delete mode 100644 app/src/test/java/de/jeanlucmakiola/agendula/domain/DayWindowTest.kt create mode 160000 floret-kit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..103c066 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "floret-kit"] + path = floret-kit + url = https://gitea.jeanlucmakiola.de/makiolaj/floret-kit.git diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/domain/DayWindow.kt b/app/src/main/java/de/jeanlucmakiola/agendula/domain/DayWindow.kt deleted file mode 100644 index fed4eeb..0000000 --- a/app/src/main/java/de/jeanlucmakiola/agendula/domain/DayWindow.kt +++ /dev/null @@ -1,18 +0,0 @@ -package de.jeanlucmakiola.agendula.domain - -import java.time.ZoneId -import kotlin.time.Instant - -/** Local-day boundaries used by the smart-list predicates. Pure + testable. */ -object DayWindow { - - /** Returns `[startOfToday, startOfTomorrow)` in [zone] for the instant [now]. */ - fun today(now: Instant, zone: ZoneId): Pair { - val date = java.time.Instant.ofEpochMilli(now.toEpochMilliseconds()) - .atZone(zone) - .toLocalDate() - val start = date.atStartOfDay(zone).toInstant().toEpochMilli() - val end = date.plusDays(1).atStartOfDay(zone).toInstant().toEpochMilli() - return Instant.fromEpochMilliseconds(start) to Instant.fromEpochMilliseconds(end) - } -} diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeFormat.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeFormat.kt deleted file mode 100644 index 82898c7..0000000 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeFormat.kt +++ /dev/null @@ -1,28 +0,0 @@ -package de.jeanlucmakiola.agendula.ui.common - -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import kotlin.time.Instant - -/** - * Locale-aware display formatting for the [kotlin.time.Instant]s the domain - * uses. Kept tiny and dependency-free; the data layer stores instants, the UI - * renders them in the device's zone and locale. - */ -private val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) -private val timeFormatter: DateTimeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) - -private fun Instant.atSystemZone() = - java.time.Instant.ofEpochMilli(toEpochMilliseconds()) - .atZone(ZoneId.systemDefault()) - -/** Medium localized date, e.g. "18 Jun 2026". */ -fun Instant.formatDate(): String = atSystemZone().format(dateFormatter) - -/** Short localized time, e.g. "14:30". */ -fun Instant.formatTime(): String = atSystemZone().format(timeFormatter) - -/** Date alone for all-day items, otherwise date + time. */ -fun Instant.formatDateTime(allDay: Boolean): String = - if (allDay) formatDate() else "${formatDate()} · ${formatTime()}" diff --git a/app/src/test/java/de/jeanlucmakiola/agendula/domain/DayWindowTest.kt b/app/src/test/java/de/jeanlucmakiola/agendula/domain/DayWindowTest.kt deleted file mode 100644 index 0e1d1e0..0000000 --- a/app/src/test/java/de/jeanlucmakiola/agendula/domain/DayWindowTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package de.jeanlucmakiola.agendula.domain - -import com.google.common.truth.Truth.assertThat -import org.junit.jupiter.api.Test -import java.time.ZoneId -import kotlin.time.Instant - -class DayWindowTest { - - @Test - fun `today spans exactly 24h in UTC and contains now`() { - val zone = ZoneId.of("UTC") - val now = Instant.fromEpochMilliseconds(1_750_000_000_000L) - val (start, end) = DayWindow.today(now, zone) - - assertThat(end.toEpochMilliseconds() - start.toEpochMilliseconds()).isEqualTo(86_400_000L) - assertThat(now >= start).isTrue() - assertThat(now < end).isTrue() - // In UTC, midnight aligns to a multiple of one day since the epoch. - assertThat(start.toEpochMilliseconds() % 86_400_000L).isEqualTo(0L) - } - - @Test - fun `start is local midnight in a non-UTC zone`() { - val zone = ZoneId.of("Europe/Berlin") - val now = Instant.fromEpochMilliseconds(1_750_000_000_000L) - val (start, end) = DayWindow.today(now, zone) - - val startLocal = java.time.Instant.ofEpochMilli(start.toEpochMilliseconds()).atZone(zone) - assertThat(startLocal.hour).isEqualTo(0) - assertThat(startLocal.minute).isEqualTo(0) - assertThat(now >= start).isTrue() - assertThat(now < end).isTrue() - } -} diff --git a/floret-kit b/floret-kit new file mode 160000 index 0000000..d3ae83f --- /dev/null +++ b/floret-kit @@ -0,0 +1 @@ +Subproject commit d3ae83f1a49ebe8f5e5e5ae65894e78e0f93c08b