From 9524bc93ca2fe6a20f97d64da3c53e74c143402c Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Tue, 4 Aug 2026 11:14:51 +0200 Subject: [PATCH] feat: show nice dates in events --- .../commonMain/kotlin/ch/parano/myice/App.kt | 14 +++++++-- .../parano/myice/ui/components/EventCard.kt | 2 +- .../myice/ui/screens/EventDetailScreen.kt | 7 ++++- .../parano/myice/ui/screens/ScheduleScreen.kt | 4 ++- .../myice/util/EventDateTimeFormatter.kt | 30 +++++++++++++++---- .../myice/util/EventDateTimeFormatterTest.kt | 13 ++++++-- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/App.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/App.kt index 7e2afdd..078f567 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myice/App.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/App.kt @@ -42,7 +42,13 @@ import com.russhwolf.settings.Settings sealed class Screen { data object Login : Screen() data object Schedule : Screen() - data class EventDetail(val gameId: String, val account: String, val eventTitle: String) : Screen() + data class EventDetail( + val gameId: String, + val account: String, + val eventTitle: String, + val eventStart: String, + val eventEnd: String, + ) : Screen() } @Composable @@ -82,14 +88,16 @@ fun App( Screen.Schedule -> ScheduleScreen( scheduleViewModel = scheduleViewModel, authViewModel = authViewModel, - onEventClick = { gameId, account, eventTitle -> - currentScreen = Screen.EventDetail(gameId, account, eventTitle) + onEventClick = { gameId, account, eventTitle, eventStart, eventEnd -> + currentScreen = Screen.EventDetail(gameId, account, eventTitle, eventStart, eventEnd) }, ) is Screen.EventDetail -> EventDetailScreen( gameId = screen.gameId, account = screen.account, eventTitle = screen.eventTitle, + eventStart = screen.eventStart, + eventEnd = screen.eventEnd, viewModel = eventDetailViewModel, onBack = { currentScreen = Screen.Schedule }, ) diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt index c4fdc98..9b472e8 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/components/EventCard.kt @@ -80,7 +80,7 @@ fun EventCard( style = MaterialTheme.typography.bodySmall, ) Text( - text = formatEventDateTime(event.start, event.end), + text = "Date: ${formatEventDateTime(event.start, event.end)}", style = MaterialTheme.typography.bodySmall, ) } diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/EventDetailScreen.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/EventDetailScreen.kt index 00224ea..ebd3842 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/EventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/EventDetailScreen.kt @@ -44,6 +44,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import ch.parano.myice.ui.components.LoadingIndicator +import ch.parano.myice.util.formatEventDate +import ch.parano.myice.util.formatEventTimeRange import ch.parano.myice.viewmodel.EventDetailViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -52,6 +54,8 @@ fun EventDetailScreen( gameId: String, account: String, eventTitle: String, + eventStart: String, + eventEnd: String, viewModel: EventDetailViewModel, onBack: () -> Unit, modifier: Modifier = Modifier, @@ -113,7 +117,8 @@ fun EventDetailScreen( ) { Text("Type: ${detail.type}") Text("Lieu: ${detail.place}") - Text("Heure: ${detail.timeStart} - ${detail.timeEnd}") + Text("Date: ${formatEventDate(eventStart)}") + Text("Heure: ${formatEventTimeRange(eventStart, eventEnd)}") } } } diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/ScheduleScreen.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/ScheduleScreen.kt index 3549d5a..efb8647 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/ScheduleScreen.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/ui/screens/ScheduleScreen.kt @@ -65,7 +65,7 @@ import ch.parano.myice.viewmodel.ScheduleViewModel fun ScheduleScreen( scheduleViewModel: ScheduleViewModel, authViewModel: AuthViewModel, - onEventClick: (gameId: String, account: String, eventTitle: String) -> Unit, + onEventClick: (gameId: String, account: String, eventTitle: String, eventStart: String, eventEnd: String) -> Unit, modifier: Modifier = Modifier, ) { val scheduleState by scheduleViewModel.state.collectAsState() @@ -250,6 +250,8 @@ fun ScheduleScreen( event.idEvent, scheduleState.selectedAccount!!, event.title, + event.start, + event.end, ) }, ) diff --git a/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt b/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt index 13d28f3..9b7e5e6 100644 --- a/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt +++ b/composeApp/src/commonMain/kotlin/ch/parano/myice/util/EventDateTimeFormatter.kt @@ -19,23 +19,43 @@ package ch.parano.myice.util import kotlinx.datetime.LocalDateTime +private val FRENCH_MONTHS = listOf( + "janvier", "février", "mars", "avril", "mai", "juin", + "juillet", "août", "septembre", "octobre", "novembre", "décembre", +) + private fun parseOrNull(iso: String): LocalDateTime? = runCatching { - LocalDateTime.parse(iso) + LocalDateTime.parse(iso.replace(' ', 'T')) }.getOrNull() private fun LocalDateTime.dateStr(): String = - "${day.toString().padStart(2, '0')}.${monthNumber.toString().padStart(2, '0')}.${year.toString().padStart(4, '0')}" + "${year.toString().padStart(4, '0')}-${monthNumber.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}" + +private fun LocalDateTime.dateStrLong(): String = + "$day ${FRENCH_MONTHS[monthNumber - 1]} $year" private fun LocalDateTime.timeStr(): String = - "${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}" + if (minute == 0) "${hour}h" else "${hour}h${minute.toString().padStart(2, '0')}" fun formatEventDateTime(start: String, end: String): String { val startDt = parseOrNull(start) val endDt = parseOrNull(end) if (startDt == null || endDt == null) return "$start - $end" return if (startDt.date == endDt.date) { - "${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()}" + "${startDt.dateStrLong()} de ${startDt.timeStr()} à ${endDt.timeStr()}" } else { - "${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()} (${endDt.dateStr()})" + "${startDt.dateStrLong()} de ${startDt.timeStr()} à ${endDt.timeStr()} (${endDt.dateStrLong()})" } } + +fun formatEventDate(start: String): String { + val dt = parseOrNull(start) + return dt?.dateStrLong() ?: start +} + +fun formatEventTimeRange(start: String, end: String): String { + val startDt = parseOrNull(start) + val endDt = parseOrNull(end) + if (startDt == null || endDt == null) return "$start - $end" + return "${startDt.timeStr()} - ${endDt.timeStr()}" +} diff --git a/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt b/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt index 2fe081a..f0d86f9 100644 --- a/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt +++ b/composeApp/src/commonTest/kotlin/ch/parano/myice/util/EventDateTimeFormatterTest.kt @@ -28,7 +28,16 @@ class EventDateTimeFormatterTest { start = "2024-11-10T14:00:00", end = "2024-11-10T16:15:00", ) - assertEquals("10.11.2024 14:00 - 16:15", formatted) + assertEquals("10 novembre 2024 de 14h à 16h15", formatted) + } + + @Test + fun formatsSameDayEventWithSpaceSeparator() { + val formatted = formatEventDateTime( + start = "2026-08-09 10:00:00", + end = "2026-08-09 12:00:00", + ) + assertEquals("9 août 2026 de 10h à 12h", formatted) } @Test @@ -37,7 +46,7 @@ class EventDateTimeFormatterTest { start = "2024-11-10T20:00:00", end = "2024-11-11T01:30:00", ) - assertEquals("10.11.2024 20:00 - 01:30 (11.11.2024)", formatted) + assertEquals("10 novembre 2024 de 20h à 1h30 (11 novembre 2024)", formatted) } @Test