Merge pull request 'fix(ui): Show event dates in card view' (#1) from fix/event-card-date-display into main

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2026-08-04 07:59:29 +00:00
3 changed files with 105 additions and 7 deletions
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.material3.Card import androidx.compose.material3.Card
@@ -35,6 +34,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import ch.parano.myice.models.Event import ch.parano.myice.models.Event
import ch.parano.myice.util.formatEventDateTime
@Composable @Composable
fun EventCard( fun EventCard(
@@ -51,9 +51,7 @@ fun EventCard(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
) { ) {
Row( Row {
modifier = Modifier.height(120.dp),
) {
Box( Box(
modifier = Modifier modifier = Modifier
.width(4.dp) .width(4.dp)
@@ -62,8 +60,7 @@ fun EventCard(
) )
Column( Column(
modifier = Modifier modifier = Modifier
.padding(16.dp) .padding(16.dp),
.fillMaxHeight(),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
Text( Text(
@@ -83,7 +80,7 @@ fun EventCard(
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
) )
Text( Text(
text = "${event.start} - ${event.end}", text = formatEventDateTime(event.start, event.end),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
) )
} }
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// MyIce Kotlin Multiplatform — schedule/convocation viewer
// Copyright (C) 2026 parano.ch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ch.parano.myice.util
import kotlinx.datetime.LocalDateTime
private fun parseOrNull(iso: String): LocalDateTime? = runCatching {
LocalDateTime.parse(iso)
}.getOrNull()
private fun LocalDateTime.dateStr(): String =
"${day.toString().padStart(2, '0')}.${monthNumber.toString().padStart(2, '0')}.${year.toString().padStart(4, '0')}"
private fun LocalDateTime.timeStr(): String =
"${hour.toString().padStart(2, '0')}:${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()}"
} else {
"${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()} (${endDt.dateStr()})"
}
}
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// MyIce Kotlin Multiplatform — schedule/convocation viewer
// Copyright (C) 2026 parano.ch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ch.parano.myice.util
import kotlin.test.Test
import kotlin.test.assertEquals
class EventDateTimeFormatterTest {
@Test
fun formatsSameDayEventAsDateFollowedByTimeRange() {
val formatted = formatEventDateTime(
start = "2024-11-10T14:00:00",
end = "2024-11-10T16:15:00",
)
assertEquals("10.11.2024 14:00 - 16:15", formatted)
}
@Test
fun formatsCrossingMidnightEventWithBothDates() {
val formatted = formatEventDateTime(
start = "2024-11-10T20:00:00",
end = "2024-11-11T01:30:00",
)
assertEquals("10.11.2024 20:00 - 01:30 (11.11.2024)", formatted)
}
@Test
fun returnsRawStartWhenStartUnparseable() {
val formatted = formatEventDateTime(start = "s", end = "2024-11-10T16:15:00")
assertEquals("s - 2024-11-10T16:15:00", formatted)
}
@Test
fun returnsRawEndWhenEndUnparseable() {
val formatted = formatEventDateTime(start = "2024-11-10T14:00:00", end = "e")
assertEquals("2024-11-10T14:00:00 - e", formatted)
}
@Test
fun returnsRawWhenBothUnparseable() {
val formatted = formatEventDateTime(start = "s", end = "e")
assertEquals("s - e", formatted)
}
}