commit 52070b7ae5e6ecca07e9ebbf2926306110eb804b
parent 496efe6698f74d8d0437db9b2fb5339e665dd2d8
Author: MTRNord <MTRNord@users.noreply.github.com>
Date: Wed, 30 Jul 2025 15:25:04 +0200
Fix file location
Diffstat:
7 files changed, 677 insertions(+), 678 deletions(-)
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/DayView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/DayView.kt
@@ -1,423 +0,0 @@
-package space.midnightthoughts.nordiccalendar.components
-
-import android.annotation.SuppressLint
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.defaultMinSize
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Card
-import androidx.compose.material3.CardDefaults
-import androidx.compose.material3.HorizontalDivider
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableLongStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.StrokeCap
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.semantics.CollectionInfo
-import androidx.compose.ui.semantics.CollectionItemInfo
-import androidx.compose.ui.semantics.collectionInfo
-import androidx.compose.ui.semantics.collectionItemInfo
-import androidx.compose.ui.semantics.heading
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-import kotlinx.coroutines.delay
-import space.midnightthoughts.nordiccalendar.getCurrentAppLocale
-import space.midnightthoughts.nordiccalendar.util.Event
-import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
-import java.time.ZoneId
-import java.time.format.DateTimeFormatter
-import java.util.Date
-import java.util.PriorityQueue
-
-private fun assignColumns(events: List<Event>): List<Triple<Event, Int, Int>> {
- data class ActiveEvent(val endTime: Long, val col: Int)
-
- val sorted = events.sortedBy { it.startTime }
- val active = PriorityQueue(compareBy<ActiveEvent> { it.endTime })
- val freeColumns = PriorityQueue<Int>()
- val result = mutableListOf<Triple<Event, Int, Int>>()
- var maxColumns = 0
-
- for (event in sorted) {
- // Entferne abgelaufene Events und gib deren Spalten frei
- while (active.isNotEmpty() && active.peek()?.endTime!! <= event.startTime) {
- freeColumns.add(active.poll()?.col)
- }
- val col = if (freeColumns.isNotEmpty()) freeColumns.poll() else active.size
- active.add(ActiveEvent(event.endTime, col))
- maxColumns = maxOf(maxColumns, active.size)
- result.add(Triple(event, col, maxColumns))
- }
- return result
-}
-
-@Composable
-private fun HourLines(
- dayStart: Long,
- hourHeightDp: Dp,
- timeColumnWidth: Dp,
- hourFormat: DateTimeFormatter
-) {
- Column(
- modifier = Modifier.fillMaxWidth()
- ) {
- for (hour in 0..24) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .height(hourHeightDp)
- .padding(horizontal = 8.dp, vertical = 4.dp)
- ) {
- Text(
- Date(dayStart + hour * 60 * 60 * 1000).toInstant()
- .atZone(ZoneId.systemDefault()).toLocalDateTime().format(hourFormat),
- style = MaterialTheme.typography.labelMedium,
- modifier = Modifier.width(timeColumnWidth)
- )
- HorizontalDivider(modifier = Modifier.weight(1f))
- }
- }
- }
-}
-
-@Composable
-private fun NowBar(
- nowOffsetY: Float,
- hourHeightPx: Float,
- now: Long,
- hourFormat: DateTimeFormatter
-) {
- val lineColor = MaterialTheme.colorScheme.error
- if (nowOffsetY >= 0f && nowOffsetY <= hourHeightPx * 24.5f) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .offset { IntOffset(0, nowOffsetY.toInt()) },
- verticalAlignment = Alignment.CenterVertically
- ) {
- Box(
- modifier = Modifier
- .padding(start = 4.dp)
- .background(
- color = lineColor,
- shape = MaterialTheme.shapes.small
- )
- .padding(horizontal = 12.dp, vertical = 4.dp)
- ) {
- Text(
- Date(now).toInstant()
- .atZone(ZoneId.systemDefault()).toLocalDateTime()
- .format(hourFormat),
- color = MaterialTheme.colorScheme.onError,
- style = MaterialTheme.typography.labelSmall
- )
- }
- Canvas(
- modifier = Modifier
- .fillMaxWidth()
- .height(4.dp)
- .padding(end = 16.dp)
- ) {
- drawLine(
- color = lineColor,
- start = Offset(
- 0f,
- size.height / 2
- ),
- end = Offset(
- size.width,
- size.height / 2
- ),
- strokeWidth = size.height,
- cap = StrokeCap.Round
- )
- }
- }
- }
-}
-
-@SuppressLint("UnusedBoxWithConstraintsScope")
-@Composable
-fun DayView(
- modifier: Modifier = Modifier,
- navController: NavController,
- calendarViewModel: CalendarViewModel,
-) {
- val hourHeightDp = 64.dp
- val timeColumnWidth = 64.dp
- val density = LocalDensity.current
- val hourHeightPx = with(density) { hourHeightDp.toPx() }
- val dayStart = remember(calendarViewModel) { calendarViewModel.startMillis }.collectAsState()
- val dayEnd = remember(calendarViewModel) { calendarViewModel.endMillis }.collectAsState()
- val appLocale = getCurrentAppLocale(LocalContext.current)
- val hourFormat = DateTimeFormatter.ofPattern("HH:mm", appLocale)
- var now by remember { mutableLongStateOf(System.currentTimeMillis()) }
-
- LaunchedEffect(now) {
- now = System.currentTimeMillis()
- delay(1000)
- }
- val nowMinutes = ((now - dayStart.value) / 60000f)
- val nowOffsetY = (nowMinutes / 60f) * hourHeightPx
-
- BoxWithConstraints(
- modifier = modifier
- ) {
- val visibleHeightPx = with(density) { maxHeight.toPx() }
- val scrollTo = (nowOffsetY - visibleHeightPx / 2).toInt().coerceAtLeast(0)
- val scrollState = rememberScrollState(initial = scrollTo)
-
- val events = remember(calendarViewModel) { calendarViewModel.events }.collectAsState()
-
- BoxWithConstraints(
- modifier = Modifier
- .fillMaxSize()
- .verticalScroll(scrollState)
- .semantics {
- collectionInfo = CollectionInfo(
- rowCount = events.value.size,
- columnCount = 1,
- )
- }
- ) {
- val maxWidthPx = with(density) { maxWidth.toPx() }
-
- // Stundenraster
- HourLines(
- dayStart = dayStart.value,
- hourHeightDp = hourHeightDp,
- timeColumnWidth = timeColumnWidth,
- hourFormat = hourFormat
- )
-
- // Events (wie gehabt, optimiert)
- val eventColumns = remember(events.value, maxWidthPx) { assignColumns(events.value) }
- events.value.forEach { event ->
- val triple = eventColumns.find { it.first.id == event.id }
- if (triple != null) {
- val (event, col, maxColumns) = triple
- val shownStart = maxOf(event.startTime, dayStart.value)
- val shownEnd = minOf(event.endTime, dayEnd.value)
- val startMinutes = ((shownStart - dayStart.value) / 60000f)
- val endMinutes = ((shownEnd - dayStart.value) / 60000f)
- val offsetY = (startMinutes / 60f) * hourHeightPx + (hourHeightPx / 2f)
- val eventHeightPx = ((endMinutes - startMinutes) / 60f) * hourHeightPx
- val columnWidthPx =
- (maxWidthPx - with(density) { timeColumnWidth.toPx() }) / maxColumns
- val offsetX =
- (col * columnWidthPx).toInt() + with(density) { timeColumnWidth.toPx() }.toInt()
- val minCardHeightDp = 60.dp
- val isCompact = with(density) { eventHeightPx.toDp() } < minCardHeightDp
-
- val noTopCorners = event.startTime < dayStart.value
- val noBottomCorners = event.endTime > dayEnd.value
-
- val showStartTimeAsMidnight = shownStart == dayStart.value
- val showEndTimeAsMidnight = shownEnd == dayEnd.value
-
- key(event.id) {
- Box(
- modifier = Modifier
- .offset { IntOffset(offsetX, offsetY.toInt()) }
- .width(with(density) { columnWidthPx.toDp() })
- .height(with(density) { eventHeightPx.toDp() })
- ) {
- EventCard(
- event = event,
- isCompact = isCompact,
- onClick = {
- navController.navigate("eventDetails/${event.eventId}?tab=2") {
- launchSingleTop = true
- restoreState = true
- }
- },
- noTopCorners = noTopCorners,
- noBottomCorners = noBottomCorners,
- showStartTimeAsMidnight = showStartTimeAsMidnight,
- showEndTimeAsMidnight = showEndTimeAsMidnight,
- eventStartOverride = shownStart,
- eventEndOverride = shownEnd
- )
- }
- }
- }
- }
-
- // Jetzt-Linie (optimiert als eigene Composable)
- NowBar(
- nowOffsetY = nowOffsetY,
- hourHeightPx = hourHeightPx,
- now = now,
- hourFormat = hourFormat
- )
- }
- }
-}
-
-
-@Composable
-private fun EventCard(
- event: Event,
- isCompact: Boolean,
- modifier: Modifier = Modifier,
- onClick: (() -> Unit)? = null,
- index: Int = 0,
- noBottomCorners: Boolean = false,
- noTopCorners: Boolean = false,
- showStartTimeAsMidnight: Boolean = false,
- showEndTimeAsMidnight: Boolean = false,
- eventStartOverride: Long = 0L,
- eventEndOverride: Long = 0L,
-) {
- val appLocale = getCurrentAppLocale(LocalContext.current)
- val hourFormat = DateTimeFormatter.ofPattern("HH:mm", appLocale)
- val startDate = remember(eventStartOverride.takeIf { it > 0L } ?: event.startTime) {
- Date(eventStartOverride.takeIf { it > 0L } ?: event.startTime)
- }
- val endDate = remember(eventEndOverride.takeIf { it > 0L } ?: event.endTime) {
- Date(eventEndOverride.takeIf { it > 0L } ?: event.endTime)
- }
- val shape = if (noTopCorners && noBottomCorners) {
- MaterialTheme.shapes.medium.copy(
- topStart = androidx.compose.foundation.shape.ZeroCornerSize,
- topEnd = androidx.compose.foundation.shape.ZeroCornerSize,
- bottomStart = androidx.compose.foundation.shape.ZeroCornerSize,
- bottomEnd = androidx.compose.foundation.shape.ZeroCornerSize
- )
- } else if (noBottomCorners) {
- MaterialTheme.shapes.medium.copy(
- bottomStart = androidx.compose.foundation.shape.ZeroCornerSize,
- bottomEnd = androidx.compose.foundation.shape.ZeroCornerSize
- )
- } else if (noTopCorners) {
- MaterialTheme.shapes.medium.copy(
- topStart = androidx.compose.foundation.shape.ZeroCornerSize,
- topEnd = androidx.compose.foundation.shape.ZeroCornerSize
- )
- } else {
- MaterialTheme.shapes.medium
- }
- Card(
- modifier = modifier
- .defaultMinSize(minHeight = 24.dp)
- .padding(end = 8.dp)
- .semantics {
- collectionItemInfo = CollectionItemInfo(
- index, 0, 0, 0,
- )
- }
- .then(if (onClick != null) Modifier.clickable { onClick() } else Modifier),
- colors = CardDefaults.outlinedCardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
- border = BorderStroke(
- 1.dp,
- color = MaterialTheme.colorScheme.outline
- ),
- shape = shape
- ) {
- Column(
- modifier = Modifier
- .padding(
- horizontal = 12.dp,
- vertical = if (isCompact) 4.dp else 12.dp,
- )
- .fillMaxSize(),
- verticalArrangement = if (isCompact) Arrangement.Center else Arrangement.Top,
- ) {
- Text(
- event.title,
- overflow = TextOverflow.Ellipsis,
- style = if (isCompact) MaterialTheme.typography.bodySmall else MaterialTheme.typography.bodyLarge,
- modifier = Modifier.semantics { heading() }
- )
- if (!isCompact) {
- Spacer(modifier = Modifier.height(4.dp))
- val startTimeText = when {
- showStartTimeAsMidnight -> "00:00"
- else -> startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
- .format(hourFormat)
- }
- val endTimeText = when {
- showEndTimeAsMidnight -> "24:00"
- else -> endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
- .format(hourFormat)
- }
- Text(
- "$startTimeText - $endTimeText",
- style = MaterialTheme.typography.bodyMedium,
- color = MaterialTheme.colorScheme.primary
- )
- Spacer(modifier = Modifier.height(8.dp))
- if (!event.description.isNullOrBlank()) {
- Text(
- event.description,
- style = MaterialTheme.typography.bodyMedium,
- overflow = TextOverflow.Ellipsis
- )
- }
- }
- }
- }
-}
-
-@Preview
-@Composable
-fun EventCardPreview() {
- val sampleEvent = Event(
- id = 1L,
- eventId = 1L,
- title = "Beispiel Event",
- description = "Dies ist eine Beschreibung des Beispiel-Events.",
- startTime = System.currentTimeMillis(),
- endTime = System.currentTimeMillis() + 3600000, // 1 Stunde später
- calendarId = 1L,
- location = "Beispielort",
- allDay = false,
- organizer = "Beispielorganisator",
- calendar = space.midnightthoughts.nordiccalendar.util.Calendar(
- id = 1L,
- name = "Beispielkalender",
- color = 0xFF6200EE, // Beispiel-Farbe
- selected = true,
- accountName = "Beispielkonto",
- accountType = "com.example",
- syncEvents = true,
- visible = true,
- displayName = "Beispielkalender",
- )
- )
- EventCard(
- event = sampleEvent,
- isCompact = false,
- )
-}
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/MonthView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/MonthView.kt
@@ -1,208 +0,0 @@
-package space.midnightthoughts.nordiccalendar.components
-
-import android.annotation.SuppressLint
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.itemsIndexed
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.navigation.NavController
-import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
-import java.time.DayOfWeek
-import java.time.Instant
-import java.time.LocalDate
-import java.time.ZoneId
-import java.time.format.TextStyle
-import java.util.Locale
-
-@SuppressLint("UnusedBoxWithConstraintsScope")
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MonthView(
- modifier: Modifier = Modifier,
- navController: NavController,
- calendarViewModel: CalendarViewModel,
-) {
- val events by calendarViewModel.events.collectAsState()
- val startMillis by calendarViewModel.startMillis.collectAsState()
- val today = LocalDate.now()
- val zoneId = ZoneId.systemDefault()
- val firstDay = Instant.ofEpochMilli(startMillis).atZone(zoneId).toLocalDate()
- val daysInMonth = firstDay.lengthOfMonth()
- // Wochenstart auf Montag (1=Montag, 7=Sonntag)
- val firstDayOfWeek = 1
- val firstOfMonth = firstDay.withDayOfMonth(1)
- val firstOfMonthDayOfWeek = (firstOfMonth.dayOfWeek.value - firstDayOfWeek + 7) % 7
- val totalCells = ((daysInMonth + firstOfMonthDayOfWeek + 6) / 7) * 7
- val days = (0 until totalCells).map { cell ->
- val dayOfMonth = cell - firstOfMonthDayOfWeek + 1
- if (dayOfMonth in 1..daysInMonth) firstDay.withDayOfMonth(dayOfMonth) else null
- }
- val eventsByDay = events.groupBy { event ->
- Instant.ofEpochMilli(event.startTime).atZone(zoneId).toLocalDate()
- }
- Column(modifier) {
- // Wochentagsnamen als Grid-Zeile
- Row(Modifier.fillMaxWidth()) {
- val locale = Locale.getDefault()
- for (i in 0..6) {
- val day = DayOfWeek.of(((i + firstDayOfWeek - 1) % 7) + 1)
- Box(
- modifier = Modifier.weight(1f),
- contentAlignment = Alignment.Center
- ) {
- Text(
- day.getDisplayName(TextStyle.SHORT, locale),
- style = MaterialTheme.typography.bodySmall,
- color = MaterialTheme.colorScheme.primary
- )
- }
- }
- }
- // Monatsraster mit Linien (nur innere Linien)
- LazyVerticalGrid(
- columns = GridCells.Fixed(7),
- modifier = Modifier.fillMaxWidth(),
- userScrollEnabled = true
- ) {
- itemsIndexed(days) { idx, date ->
- val isToday = date == today
- val isCurrentMonth = date != null && date.month == firstDay.month
- val dayEvents = if (date != null) eventsByDay[date].orEmpty() else emptyList()
- Box(
- modifier = Modifier
- .fillMaxSize(),
- contentAlignment = Alignment.TopStart
- ) {
- Column(
- Modifier
- .padding(4.dp)
- .fillMaxSize(),
- verticalArrangement = Arrangement.Top
- ) {
- // Tag-Nummer
- Box(
- modifier = Modifier
- .size(32.dp)
- .aspectRatio(1f)
- .then(
- if (isToday) Modifier.background(
- MaterialTheme.colorScheme.primaryContainer,
- RoundedCornerShape(50)
- ) else Modifier
- ),
- contentAlignment = Alignment.Center
- ) {
- Text(
- text = date?.dayOfMonth?.toString() ?: "",
- color = when {
- isToday -> MaterialTheme.colorScheme.onPrimaryContainer
- isCurrentMonth -> MaterialTheme.colorScheme.onSurface
- else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
- },
- fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal,
- fontSize = 13.sp,
- lineHeight = 8.sp,
- modifier = Modifier.padding(2.dp)
- )
- }
- Spacer(Modifier.size(2.dp))
- // Events als kompakte Chips
- val maxEvents = 3
- dayEvents.take(maxEvents).forEach { event ->
- CompactChip(
- text = event.title,
- backgroundColor = Color(event.calendar.color.toInt() or 0xFF000000.toInt()),
- textColor = MaterialTheme.colorScheme.onPrimary,
- modifier = Modifier
- .padding(bottom = 1.dp)
- .clickable {
- navController.navigate("eventDetails/${event.eventId}") {
- launchSingleTop = true
- restoreState = true
- }
- }
- )
- }
- if (dayEvents.size > maxEvents) {
- CompactChip(
- text = "+${dayEvents.size - maxEvents} mehr",
- backgroundColor = Color.Transparent,
- textColor = MaterialTheme.colorScheme.primary,
- center = true,
- modifier = Modifier
- .padding(bottom = 1.dp)
- .border(
- width = 1.dp,
- color = MaterialTheme.colorScheme.primary,
- shape = RoundedCornerShape(8.dp)
- ),
- )
- }
- }
- }
- }
- }
- }
-}
-
-@Composable
-fun CompactChip(
- text: String,
- backgroundColor: Color,
- textColor: Color,
- modifier: Modifier = Modifier,
- borderColor: Color? = null,
- center: Boolean? = false
-) {
- Box(
- modifier = modifier
- .background(backgroundColor, RoundedCornerShape(4.dp))
- .height(20.dp)
- .fillMaxWidth()
- .then(
- if (borderColor != null) Modifier.border(
- width = 1.dp,
- color = borderColor,
- shape = RoundedCornerShape(6.dp)
- ) else Modifier
- ),
- contentAlignment = if (center == true) Alignment.Center else Alignment.CenterStart
- ) {
- Text(
- text = text,
- color = textColor,
- lineHeight = 12.sp,
- fontSize = 11.sp,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis,
- modifier = Modifier.padding(horizontal = 3.dp)
- )
- }
-}
-\ No newline at end of file
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/components/WeekView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/components/WeekView.kt
@@ -1,42 +0,0 @@
-package space.midnightthoughts.nordiccalendar.components
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.items
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import androidx.navigation.NavController
-import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
-
-@Composable
-fun WeekView(
- modifier: Modifier = Modifier,
- navController: NavController,
- calendarViewModel: CalendarViewModel,
-) {
- // TODO: Implement WeekView
- val events = remember(calendarViewModel) {
- calendarViewModel.events
- }.collectAsState(initial = emptyList())
-
- LazyColumn(modifier = Modifier.fillMaxSize()) {
- items(events.value) { event ->
- Text(
- event.title, modifier = Modifier
- .padding(8.dp)
- .clickable {
- navController.navigate("eventDetails/${event.eventId}?tab=1") {
- launchSingleTop = true
- restoreState = true
- }
- })
- }
- }
-
-}
-\ No newline at end of file
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt
@@ -30,9 +30,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController
import space.midnightthoughts.nordiccalendar.R
import space.midnightthoughts.nordiccalendar.components.DateRangeHeader
-import space.midnightthoughts.nordiccalendar.components.DayView
-import space.midnightthoughts.nordiccalendar.components.MonthView
-import space.midnightthoughts.nordiccalendar.components.WeekView
import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
@SuppressLint("UnusedBoxWithConstraintsScope")
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/DayView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/DayView.kt
@@ -0,0 +1,425 @@
+package space.midnightthoughts.nordiccalendar.screens
+
+import android.annotation.SuppressLint
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.ZeroCornerSize
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableLongStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.CollectionInfo
+import androidx.compose.ui.semantics.CollectionItemInfo
+import androidx.compose.ui.semantics.collectionInfo
+import androidx.compose.ui.semantics.collectionItemInfo
+import androidx.compose.ui.semantics.heading
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import kotlinx.coroutines.delay
+import space.midnightthoughts.nordiccalendar.getCurrentAppLocale
+import space.midnightthoughts.nordiccalendar.util.Calendar
+import space.midnightthoughts.nordiccalendar.util.Event
+import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.util.Date
+import java.util.PriorityQueue
+
+private fun assignColumns(events: List<Event>): List<Triple<Event, Int, Int>> {
+ data class ActiveEvent(val endTime: Long, val col: Int)
+
+ val sorted = events.sortedBy { it.startTime }
+ val active = PriorityQueue(compareBy<ActiveEvent> { it.endTime })
+ val freeColumns = PriorityQueue<Int>()
+ val result = mutableListOf<Triple<Event, Int, Int>>()
+ var maxColumns = 0
+
+ for (event in sorted) {
+ // Entferne abgelaufene Events und gib deren Spalten frei
+ while (active.isNotEmpty() && active.peek()?.endTime!! <= event.startTime) {
+ freeColumns.add(active.poll()?.col)
+ }
+ val col = if (freeColumns.isNotEmpty()) freeColumns.poll() else active.size
+ active.add(ActiveEvent(event.endTime, col))
+ maxColumns = maxOf(maxColumns, active.size)
+ result.add(Triple(event, col, maxColumns))
+ }
+ return result
+}
+
+@Composable
+private fun HourLines(
+ dayStart: Long,
+ hourHeightDp: Dp,
+ timeColumnWidth: Dp,
+ hourFormat: DateTimeFormatter
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ for (hour in 0..24) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier
+ .height(hourHeightDp)
+ .padding(horizontal = 8.dp, vertical = 4.dp)
+ ) {
+ Text(
+ Date(dayStart + hour * 60 * 60 * 1000).toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDateTime().format(hourFormat),
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier.width(timeColumnWidth)
+ )
+ HorizontalDivider(modifier = Modifier.weight(1f))
+ }
+ }
+ }
+}
+
+@Composable
+private fun NowBar(
+ nowOffsetY: Float,
+ hourHeightPx: Float,
+ now: Long,
+ hourFormat: DateTimeFormatter
+) {
+ val lineColor = MaterialTheme.colorScheme.error
+ if (nowOffsetY >= 0f && nowOffsetY <= hourHeightPx * 24.5f) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .offset { IntOffset(0, nowOffsetY.toInt()) },
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(start = 4.dp)
+ .background(
+ color = lineColor,
+ shape = MaterialTheme.shapes.small
+ )
+ .padding(horizontal = 12.dp, vertical = 4.dp)
+ ) {
+ Text(
+ Date(now).toInstant()
+ .atZone(ZoneId.systemDefault()).toLocalDateTime()
+ .format(hourFormat),
+ color = MaterialTheme.colorScheme.onError,
+ style = MaterialTheme.typography.labelSmall
+ )
+ }
+ Canvas(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(4.dp)
+ .padding(end = 16.dp)
+ ) {
+ drawLine(
+ color = lineColor,
+ start = Offset(
+ 0f,
+ size.height / 2
+ ),
+ end = Offset(
+ size.width,
+ size.height / 2
+ ),
+ strokeWidth = size.height,
+ cap = StrokeCap.Round
+ )
+ }
+ }
+ }
+}
+
+@SuppressLint("UnusedBoxWithConstraintsScope")
+@Composable
+fun DayView(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ calendarViewModel: CalendarViewModel,
+) {
+ val hourHeightDp = 64.dp
+ val timeColumnWidth = 64.dp
+ val density = LocalDensity.current
+ val hourHeightPx = with(density) { hourHeightDp.toPx() }
+ val dayStart = remember(calendarViewModel) { calendarViewModel.startMillis }.collectAsState()
+ val dayEnd = remember(calendarViewModel) { calendarViewModel.endMillis }.collectAsState()
+ val appLocale = getCurrentAppLocale(LocalContext.current)
+ val hourFormat = DateTimeFormatter.ofPattern("HH:mm", appLocale)
+ var now by remember { mutableLongStateOf(System.currentTimeMillis()) }
+
+ LaunchedEffect(now) {
+ now = System.currentTimeMillis()
+ delay(1000)
+ }
+ val nowMinutes = ((now - dayStart.value) / 60000f)
+ val nowOffsetY = (nowMinutes / 60f) * hourHeightPx
+
+ BoxWithConstraints(
+ modifier = modifier
+ ) {
+ val visibleHeightPx = with(density) { maxHeight.toPx() }
+ val scrollTo = (nowOffsetY - visibleHeightPx / 2).toInt().coerceAtLeast(0)
+ val scrollState = rememberScrollState(initial = scrollTo)
+
+ val events = remember(calendarViewModel) { calendarViewModel.events }.collectAsState()
+
+ BoxWithConstraints(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
+ .semantics {
+ collectionInfo = CollectionInfo(
+ rowCount = events.value.size,
+ columnCount = 1,
+ )
+ }
+ ) {
+ val maxWidthPx = with(density) { maxWidth.toPx() }
+
+ // Stundenraster
+ HourLines(
+ dayStart = dayStart.value,
+ hourHeightDp = hourHeightDp,
+ timeColumnWidth = timeColumnWidth,
+ hourFormat = hourFormat
+ )
+
+ // Events (wie gehabt, optimiert)
+ val eventColumns = remember(events.value, maxWidthPx) { assignColumns(events.value) }
+ events.value.forEach { event ->
+ val triple = eventColumns.find { it.first.id == event.id }
+ if (triple != null) {
+ val (event, col, maxColumns) = triple
+ val shownStart = maxOf(event.startTime, dayStart.value)
+ val shownEnd = minOf(event.endTime, dayEnd.value)
+ val startMinutes = ((shownStart - dayStart.value) / 60000f)
+ val endMinutes = ((shownEnd - dayStart.value) / 60000f)
+ val offsetY = (startMinutes / 60f) * hourHeightPx + (hourHeightPx / 2f)
+ val eventHeightPx = ((endMinutes - startMinutes) / 60f) * hourHeightPx
+ val columnWidthPx =
+ (maxWidthPx - with(density) { timeColumnWidth.toPx() }) / maxColumns
+ val offsetX =
+ (col * columnWidthPx).toInt() + with(density) { timeColumnWidth.toPx() }.toInt()
+ val minCardHeightDp = 60.dp
+ val isCompact = with(density) { eventHeightPx.toDp() } < minCardHeightDp
+
+ val noTopCorners = event.startTime < dayStart.value
+ val noBottomCorners = event.endTime > dayEnd.value
+
+ val showStartTimeAsMidnight = shownStart == dayStart.value
+ val showEndTimeAsMidnight = shownEnd == dayEnd.value
+
+ key(event.id) {
+ Box(
+ modifier = Modifier
+ .offset { IntOffset(offsetX, offsetY.toInt()) }
+ .width(with(density) { columnWidthPx.toDp() })
+ .height(with(density) { eventHeightPx.toDp() })
+ ) {
+ EventCard(
+ event = event,
+ isCompact = isCompact,
+ onClick = {
+ navController.navigate("eventDetails/${event.eventId}?tab=2") {
+ launchSingleTop = true
+ restoreState = true
+ }
+ },
+ noTopCorners = noTopCorners,
+ noBottomCorners = noBottomCorners,
+ showStartTimeAsMidnight = showStartTimeAsMidnight,
+ showEndTimeAsMidnight = showEndTimeAsMidnight,
+ eventStartOverride = shownStart,
+ eventEndOverride = shownEnd
+ )
+ }
+ }
+ }
+ }
+
+ // Jetzt-Linie (optimiert als eigene Composable)
+ NowBar(
+ nowOffsetY = nowOffsetY,
+ hourHeightPx = hourHeightPx,
+ now = now,
+ hourFormat = hourFormat
+ )
+ }
+ }
+}
+
+
+@Composable
+private fun EventCard(
+ event: Event,
+ isCompact: Boolean,
+ modifier: Modifier = Modifier,
+ onClick: (() -> Unit)? = null,
+ index: Int = 0,
+ noBottomCorners: Boolean = false,
+ noTopCorners: Boolean = false,
+ showStartTimeAsMidnight: Boolean = false,
+ showEndTimeAsMidnight: Boolean = false,
+ eventStartOverride: Long = 0L,
+ eventEndOverride: Long = 0L,
+) {
+ val appLocale = getCurrentAppLocale(LocalContext.current)
+ val hourFormat = DateTimeFormatter.ofPattern("HH:mm", appLocale)
+ val startDate = remember(eventStartOverride.takeIf { it > 0L } ?: event.startTime) {
+ Date(eventStartOverride.takeIf { it > 0L } ?: event.startTime)
+ }
+ val endDate = remember(eventEndOverride.takeIf { it > 0L } ?: event.endTime) {
+ Date(eventEndOverride.takeIf { it > 0L } ?: event.endTime)
+ }
+ val shape = if (noTopCorners && noBottomCorners) {
+ MaterialTheme.shapes.medium.copy(
+ topStart = ZeroCornerSize,
+ topEnd = ZeroCornerSize,
+ bottomStart = ZeroCornerSize,
+ bottomEnd = ZeroCornerSize
+ )
+ } else if (noBottomCorners) {
+ MaterialTheme.shapes.medium.copy(
+ bottomStart = ZeroCornerSize,
+ bottomEnd = ZeroCornerSize
+ )
+ } else if (noTopCorners) {
+ MaterialTheme.shapes.medium.copy(
+ topStart = ZeroCornerSize,
+ topEnd = ZeroCornerSize
+ )
+ } else {
+ MaterialTheme.shapes.medium
+ }
+ Card(
+ modifier = modifier
+ .defaultMinSize(minHeight = 24.dp)
+ .padding(end = 8.dp)
+ .semantics {
+ collectionItemInfo = CollectionItemInfo(
+ index, 0, 0, 0,
+ )
+ }
+ .then(if (onClick != null) Modifier.clickable { onClick() } else Modifier),
+ colors = CardDefaults.outlinedCardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant),
+ border = BorderStroke(
+ 1.dp,
+ color = MaterialTheme.colorScheme.outline
+ ),
+ shape = shape
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(
+ horizontal = 12.dp,
+ vertical = if (isCompact) 4.dp else 12.dp,
+ )
+ .fillMaxSize(),
+ verticalArrangement = if (isCompact) Arrangement.Center else Arrangement.Top,
+ ) {
+ Text(
+ event.title,
+ overflow = TextOverflow.Ellipsis,
+ style = if (isCompact) MaterialTheme.typography.bodySmall else MaterialTheme.typography.bodyLarge,
+ modifier = Modifier.semantics { heading() }
+ )
+ if (!isCompact) {
+ Spacer(modifier = Modifier.height(4.dp))
+ val startTimeText = when {
+ showStartTimeAsMidnight -> "00:00"
+ else -> startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
+ .format(hourFormat)
+ }
+ val endTimeText = when {
+ showEndTimeAsMidnight -> "24:00"
+ else -> endDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
+ .format(hourFormat)
+ }
+ Text(
+ "$startTimeText - $endTimeText",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.primary
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ if (!event.description.isNullOrBlank()) {
+ Text(
+ event.description,
+ style = MaterialTheme.typography.bodyMedium,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun EventCardPreview() {
+ val sampleEvent = Event(
+ id = 1L,
+ eventId = 1L,
+ title = "Beispiel Event",
+ description = "Dies ist eine Beschreibung des Beispiel-Events.",
+ startTime = System.currentTimeMillis(),
+ endTime = System.currentTimeMillis() + 3600000, // 1 Stunde später
+ calendarId = 1L,
+ location = "Beispielort",
+ allDay = false,
+ organizer = "Beispielorganisator",
+ calendar = Calendar(
+ id = 1L,
+ name = "Beispielkalender",
+ color = 0xFF6200EE, // Beispiel-Farbe
+ selected = true,
+ accountName = "Beispielkonto",
+ accountType = "com.example",
+ syncEvents = true,
+ visible = true,
+ displayName = "Beispielkalender",
+ )
+ )
+ EventCard(
+ event = sampleEvent,
+ isCompact = false,
+ )
+}
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/MonthView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/MonthView.kt
@@ -0,0 +1,208 @@
+package space.midnightthoughts.nordiccalendar.screens
+
+import android.annotation.SuppressLint
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.itemsIndexed
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavController
+import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
+import java.time.DayOfWeek
+import java.time.Instant
+import java.time.LocalDate
+import java.time.ZoneId
+import java.time.format.TextStyle
+import java.util.Locale
+
+@SuppressLint("UnusedBoxWithConstraintsScope")
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MonthView(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ calendarViewModel: CalendarViewModel,
+) {
+ val events by calendarViewModel.events.collectAsState()
+ val startMillis by calendarViewModel.startMillis.collectAsState()
+ val today = LocalDate.now()
+ val zoneId = ZoneId.systemDefault()
+ val firstDay = Instant.ofEpochMilli(startMillis).atZone(zoneId).toLocalDate()
+ val daysInMonth = firstDay.lengthOfMonth()
+ // Wochenstart auf Montag (1=Montag, 7=Sonntag)
+ val firstDayOfWeek = 1
+ val firstOfMonth = firstDay.withDayOfMonth(1)
+ val firstOfMonthDayOfWeek = (firstOfMonth.dayOfWeek.value - firstDayOfWeek + 7) % 7
+ val totalCells = ((daysInMonth + firstOfMonthDayOfWeek + 6) / 7) * 7
+ val days = (0 until totalCells).map { cell ->
+ val dayOfMonth = cell - firstOfMonthDayOfWeek + 1
+ if (dayOfMonth in 1..daysInMonth) firstDay.withDayOfMonth(dayOfMonth) else null
+ }
+ val eventsByDay = events.groupBy { event ->
+ Instant.ofEpochMilli(event.startTime).atZone(zoneId).toLocalDate()
+ }
+ Column(modifier) {
+ // Wochentagsnamen als Grid-Zeile
+ Row(Modifier.fillMaxWidth()) {
+ val locale = Locale.getDefault()
+ for (i in 0..6) {
+ val day = DayOfWeek.of(((i + firstDayOfWeek - 1) % 7) + 1)
+ Box(
+ modifier = Modifier.weight(1f),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ day.getDisplayName(TextStyle.SHORT, locale),
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.primary
+ )
+ }
+ }
+ }
+ // Monatsraster mit Linien (nur innere Linien)
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(7),
+ modifier = Modifier.fillMaxWidth(),
+ userScrollEnabled = true
+ ) {
+ itemsIndexed(days) { idx, date ->
+ val isToday = date == today
+ val isCurrentMonth = date != null && date.month == firstDay.month
+ val dayEvents = if (date != null) eventsByDay[date].orEmpty() else emptyList()
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.TopStart
+ ) {
+ Column(
+ Modifier
+ .padding(4.dp)
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Top
+ ) {
+ // Tag-Nummer
+ Box(
+ modifier = Modifier
+ .size(32.dp)
+ .aspectRatio(1f)
+ .then(
+ if (isToday) Modifier.background(
+ MaterialTheme.colorScheme.primaryContainer,
+ RoundedCornerShape(50)
+ ) else Modifier
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = date?.dayOfMonth?.toString() ?: "",
+ color = when {
+ isToday -> MaterialTheme.colorScheme.onPrimaryContainer
+ isCurrentMonth -> MaterialTheme.colorScheme.onSurface
+ else -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
+ },
+ fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal,
+ fontSize = 13.sp,
+ lineHeight = 8.sp,
+ modifier = Modifier.padding(2.dp)
+ )
+ }
+ Spacer(Modifier.size(2.dp))
+ // Events als kompakte Chips
+ val maxEvents = 3
+ dayEvents.take(maxEvents).forEach { event ->
+ CompactChip(
+ text = event.title,
+ backgroundColor = Color(event.calendar.color.toInt() or 0xFF000000.toInt()),
+ textColor = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier
+ .padding(bottom = 1.dp)
+ .clickable {
+ navController.navigate("eventDetails/${event.eventId}") {
+ launchSingleTop = true
+ restoreState = true
+ }
+ }
+ )
+ }
+ if (dayEvents.size > maxEvents) {
+ CompactChip(
+ text = "+${dayEvents.size - maxEvents} mehr",
+ backgroundColor = Color.Transparent,
+ textColor = MaterialTheme.colorScheme.primary,
+ center = true,
+ modifier = Modifier
+ .padding(bottom = 1.dp)
+ .border(
+ width = 1.dp,
+ color = MaterialTheme.colorScheme.primary,
+ shape = RoundedCornerShape(8.dp)
+ ),
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun CompactChip(
+ text: String,
+ backgroundColor: Color,
+ textColor: Color,
+ modifier: Modifier = Modifier,
+ borderColor: Color? = null,
+ center: Boolean? = false
+) {
+ Box(
+ modifier = modifier
+ .background(backgroundColor, RoundedCornerShape(4.dp))
+ .height(20.dp)
+ .fillMaxWidth()
+ .then(
+ if (borderColor != null) Modifier.border(
+ width = 1.dp,
+ color = borderColor,
+ shape = RoundedCornerShape(6.dp)
+ ) else Modifier
+ ),
+ contentAlignment = if (center == true) Alignment.Center else Alignment.CenterStart
+ ) {
+ Text(
+ text = text,
+ color = textColor,
+ lineHeight = 12.sp,
+ fontSize = 11.sp,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis,
+ modifier = Modifier.padding(horizontal = 3.dp)
+ )
+ }
+}
+\ No newline at end of file
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/WeekView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/WeekView.kt
@@ -0,0 +1,42 @@
+package space.midnightthoughts.nordiccalendar.screens
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavController
+import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
+
+@Composable
+fun WeekView(
+ modifier: Modifier = Modifier,
+ navController: NavController,
+ calendarViewModel: CalendarViewModel,
+) {
+ // TODO: Implement WeekView
+ val events = remember(calendarViewModel) {
+ calendarViewModel.events
+ }.collectAsState(initial = emptyList())
+
+ LazyColumn(modifier = Modifier.fillMaxSize()) {
+ items(events.value) { event ->
+ Text(
+ event.title, modifier = Modifier
+ .padding(8.dp)
+ .clickable {
+ navController.navigate("eventDetails/${event.eventId}?tab=1") {
+ launchSingleTop = true
+ restoreState = true
+ }
+ })
+ }
+ }
+
+}
+\ No newline at end of file