commit 3ec05bcefdd5614c48cb6e638531124d9b70947c
parent 900abe345637c11f288a83b84e956097dbea3d76
Author: MTRNord <MTRNord@users.noreply.github.com>
Date: Mon, 28 Jul 2025 18:50:12 +0200
Initial setup for Event Details
Diffstat:
7 files changed, 281 insertions(+), 108 deletions(-)
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/MainActivity.kt
@@ -13,7 +13,6 @@ 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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -57,6 +56,7 @@ import space.midnightthoughts.nordiccalendar.components.AppScaffold
import space.midnightthoughts.nordiccalendar.onboarding.OnBoardItem
import space.midnightthoughts.nordiccalendar.onboarding.onBoardingData
import space.midnightthoughts.nordiccalendar.screens.CalendarScreen
+import space.midnightthoughts.nordiccalendar.screens.EventDetailsView
import space.midnightthoughts.nordiccalendar.ui.theme.NordicCalendarTheme
import space.midnightthoughts.nordiccalendar.util.OnboardingPrefs
import space.midnightthoughts.nordiccalendar.viewmodels.CalendarViewModel
@@ -267,31 +267,6 @@ fun CalendarView(backStackEntry: NavBackStackEntry, navController: NavHostContro
@Composable
-fun EventDetailsView(
- backStackEntry: NavBackStackEntry,
- navController: NavHostController
-) {
- val eventId = backStackEntry.arguments?.getString("eventId")
- AppScaffold(
- title = stringResource(R.string.event_details_title),
- selectedDestination = "eventDetails",
- navController = navController,
- ) { innerPadding ->
- Column(
- modifier = innerPadding
- .fillMaxSize()
- .padding(16.dp),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Text(stringResource(R.string.event_details, eventId ?: ""))
- Spacer(modifier = Modifier.height(24.dp))
- // Weitere Event-Details hier
- }
- }
-}
-
-@Composable
fun SettingsView(navController: NavHostController) {
AppScaffold(
title = stringResource(R.string.settings),
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/data/CalendarRepository.kt
@@ -0,0 +1,49 @@
+package space.midnightthoughts.nordiccalendar.data
+
+import android.content.ContentResolver
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.core.content.edit
+import space.midnightthoughts.nordiccalendar.util.Calendar
+import space.midnightthoughts.nordiccalendar.util.CalendarData
+import space.midnightthoughts.nordiccalendar.util.Event
+
+class CalendarRepository(private val context: Context) {
+ private val calendarData = CalendarData()
+ private val prefs: SharedPreferences by lazy {
+ context.getSharedPreferences("calendar_prefs", Context.MODE_PRIVATE)
+ }
+ private val contentResolver: ContentResolver get() = context.contentResolver
+
+ fun getCalendars(): List<Calendar> {
+ val calendars = calendarData.getCalendars(contentResolver)
+ val selectedIds = prefs.getStringSet("selected_calendar_ids", null)
+ return if (selectedIds != null) {
+ calendars.map { it.copy(selected = selectedIds.contains(it.id.toString())) }
+ } else {
+ calendars
+ }
+ }
+
+ fun setCalendarSelected(calendarId: Long, selected: Boolean) {
+ val selectedIds =
+ prefs.getStringSet("selected_calendar_ids", null)?.toMutableSet() ?: mutableSetOf()
+ if (selected) {
+ selectedIds.add(calendarId.toString())
+ } else {
+ selectedIds.remove(calendarId.toString())
+ }
+ prefs.edit { putStringSet("selected_calendar_ids", selectedIds) }
+ }
+
+ fun getEventsForCalendars(
+ calendarIds: List<Long>,
+ startMillis: Long,
+ endMillis: Long
+ ): List<Event> =
+ calendarData.getEventsForCalendars(contentResolver, calendarIds, startMillis, endMillis)
+
+ fun getEventById(eventId: Long): Event? {
+ return calendarData.getEventById(contentResolver, eventId)
+ }
+}
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/CalendarScreen.kt
@@ -195,7 +195,7 @@ fun DayView(
}
val nowMinutes = ((now - dayStart) / 60000f)
val nowOffsetY = ((nowMinutes + 30f) / 60f) * hourHeightPx
- LaunchedEffect(nowOffsetY) {
+ LaunchedEffect(Unit) {
coroutineScope.launch {
val visibleHeightPx = with(density) { 600.dp.toPx() }
val scrollTo = (nowOffsetY - visibleHeightPx / 2).toInt().coerceAtLeast(0)
@@ -264,7 +264,7 @@ fun DayView(
event = event,
isCompact = isCompact,
hourFormat = hourFormat,
- onClick = { navController.navigate("eventDetails/${event.id}") },
+ onClick = { navController.navigate("eventDetails/${event.eventId}") },
modifier = Modifier
.width(with(density) { columnWidthPx.toDp() })
.defaultMinSize(minHeight = 24.dp)
@@ -466,6 +466,7 @@ fun EventCard(
fun EventCardPreview() {
val sampleEvent = Event(
id = 1L,
+ eventId = 1L,
title = "Beispiel Event",
description = "Dies ist eine Beschreibung des Beispiel-Events.",
startTime = System.currentTimeMillis(),
@@ -493,7 +494,10 @@ fun EventList(
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(events.value) { event ->
- Text(event.title, modifier = Modifier.padding(8.dp))
+ Text(
+ event.title, modifier = Modifier
+ .padding(8.dp)
+ .clickable { navController.navigate("eventDetails/${event.eventId}") })
}
}
}
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/screens/EventDetailsView.kt
@@ -0,0 +1,99 @@
+package space.midnightthoughts.nordiccalendar.screens
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavBackStackEntry
+import androidx.navigation.NavHostController
+import space.midnightthoughts.nordiccalendar.R
+import space.midnightthoughts.nordiccalendar.components.AppScaffold
+import space.midnightthoughts.nordiccalendar.viewmodels.EventDetailsViewModel
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+@Composable
+fun EventDetailsView(
+ backStackEntry: NavBackStackEntry,
+ navController: NavHostController,
+) {
+ val owner = LocalViewModelStoreOwner.current
+ val context =
+ androidx.compose.ui.platform.LocalContext.current.applicationContext as android.app.Application
+ val eventId = backStackEntry.arguments?.getString("eventId")
+ val viewModel: EventDetailsViewModel = if (owner != null && eventId != null) {
+ viewModel(
+ viewModelStoreOwner = owner,
+ key = "eventDetails_$eventId",
+ factory = object : androidx.lifecycle.ViewModelProvider.Factory {
+ override fun <T : androidx.lifecycle.ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return EventDetailsViewModel(
+ app = context,
+ savedStateHandle = androidx.lifecycle.SavedStateHandle(mapOf("eventId" to eventId))
+ ) as T
+ }
+ }
+ )
+ } else {
+ throw IllegalStateException("ViewModelStoreOwner or eventId is null")
+ }
+ val event = viewModel.event.collectAsState().value
+ val dateTimeFormat = SimpleDateFormat("dd.MM.yyyy HH:mm", Locale.getDefault())
+ AppScaffold(
+ title = stringResource(R.string.event_details_title),
+ selectedDestination = "eventDetails",
+ navController = navController,
+ ) { innerPadding ->
+ Column(
+ modifier = innerPadding
+ .fillMaxSize()
+ .padding(16.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ if (event != null) {
+ Text(event.title)
+ Spacer(modifier = Modifier.height(8.dp))
+ if (!event.description.isNullOrBlank()) {
+ Text(event.description)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ Text(
+ "Start: " + dateTimeFormat.format(java.util.Date(event.startTime))
+ )
+ Text(
+ "Ende: " + dateTimeFormat.format(java.util.Date(event.endTime))
+ )
+ if (!event.location.isNullOrBlank()) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Text("Ort: ${event.location}")
+ }
+ if (!event.organizer.isNullOrBlank()) {
+ Spacer(modifier = Modifier.height(8.dp))
+ Text("Organisator: ${event.organizer}")
+ }
+ } else {
+ Text(
+ stringResource(
+ R.string.event_details,
+ backStackEntry.arguments?.getString("eventId") ?: ""
+ )
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+ Text("Event nicht gefunden oder wird geladen...")
+ }
+ }
+ }
+}
+\ No newline at end of file
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/util/CalendarData.kt
@@ -19,7 +19,8 @@ data class Calendar(
)
data class Event(
- val id: Long,
+ val id: Long, // Instanz-ID (Instances._ID)
+ val eventId: Long, // Event-ID (Instances.EVENT_ID)
val title: String,
val description: String?,
val startTime: Long,
@@ -117,6 +118,7 @@ class CalendarData {
val INSTANCE_PROJECTION = arrayOf(
CalendarContract.Instances._ID,
+ CalendarContract.Instances.EVENT_ID,
CalendarContract.Instances.TITLE,
CalendarContract.Instances.DESCRIPTION,
CalendarContract.Instances.BEGIN,
@@ -127,14 +129,15 @@ class CalendarData {
CalendarContract.Instances.ORGANIZER
)
val PROJECTION_ID_INDEX = 0
- val PROJECTION_TITLE_INDEX = 1
- val PROJECTION_DESCRIPTION_INDEX = 2
- val PROJECTION_BEGIN_INDEX = 3
- val PROJECTION_END_INDEX = 4
- val PROJECTION_CALENDAR_ID_INDEX = 5
- val PROJECTION_EVENT_LOCATION_INDEX = 6
- val PROJECTION_ALL_DAY_INDEX = 7
- val PROJECTION_ORGANIZER_INDEX = 8
+ val PROJECTION_EVENT_ID_INDEX = 1
+ val PROJECTION_TITLE_INDEX = 2
+ val PROJECTION_DESCRIPTION_INDEX = 3
+ val PROJECTION_BEGIN_INDEX = 4
+ val PROJECTION_END_INDEX = 5
+ val PROJECTION_CALENDAR_ID_INDEX = 6
+ val PROJECTION_EVENT_LOCATION_INDEX = 7
+ val PROJECTION_ALL_DAY_INDEX = 8
+ val PROJECTION_ORGANIZER_INDEX = 9
val builder: Uri.Builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
ContentUris.appendId(builder, startMillis)
@@ -149,6 +152,7 @@ class CalendarData {
)
while (cur?.moveToNext() == true) {
val id = cur.getLong(PROJECTION_ID_INDEX)
+ val eventId = cur.getLong(PROJECTION_EVENT_ID_INDEX)
val title = cur.getString(PROJECTION_TITLE_INDEX) ?: "No Title"
val description = cur.getString(PROJECTION_DESCRIPTION_INDEX)
val startTime = cur.getLong(PROJECTION_BEGIN_INDEX)
@@ -158,11 +162,10 @@ class CalendarData {
val allDay = cur.getInt(PROJECTION_ALL_DAY_INDEX) == 1
val organizer = cur.getString(PROJECTION_ORGANIZER_INDEX)
- // Fetch attendees if needed (not included in this example for brevity)
-
events.add(
Event(
id = id,
+ eventId = eventId,
title = title,
description = description,
startTime = startTime,
@@ -235,4 +238,49 @@ class CalendarData {
)
}
}
+
+ fun getEventById(contentResolver: ContentResolver, eventId: Long): Event? {
+ val INSTANCE_PROJECTION = arrayOf(
+ CalendarContract.Instances._ID,
+ CalendarContract.Instances.EVENT_ID,
+ CalendarContract.Instances.TITLE,
+ CalendarContract.Instances.DESCRIPTION,
+ CalendarContract.Instances.BEGIN,
+ CalendarContract.Instances.END,
+ CalendarContract.Instances.CALENDAR_ID,
+ CalendarContract.Instances.EVENT_LOCATION,
+ CalendarContract.Instances.ALL_DAY,
+ CalendarContract.Instances.ORGANIZER
+ )
+ val selection = "Instances.event_id = ?"
+ val selectionArgs = arrayOf(eventId.toString())
+ val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
+ val minMillis = 0L
+ val maxMillis = Long.MAX_VALUE
+ ContentUris.appendId(builder, minMillis)
+ ContentUris.appendId(builder, maxMillis)
+ val cur = contentResolver.query(
+ builder.build(),
+ INSTANCE_PROJECTION,
+ selection,
+ selectionArgs,
+ null
+ )
+ val event = if (cur?.moveToFirst() == true) {
+ Event(
+ id = cur.getLong(0),
+ eventId = cur.getLong(1),
+ title = cur.getString(2) ?: "No Title",
+ description = cur.getString(3),
+ startTime = cur.getLong(4),
+ endTime = cur.getLong(5),
+ calendarId = cur.getLong(6),
+ location = cur.getString(7),
+ allDay = cur.getInt(8) == 1,
+ organizer = cur.getString(9)
+ )
+ } else null
+ cur?.close()
+ return event
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/CalendarViewModel.kt
@@ -1,22 +1,19 @@
package space.midnightthoughts.nordiccalendar.viewmodels
import android.app.Application
-import android.content.ContentResolver
-import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
+import space.midnightthoughts.nordiccalendar.data.CalendarRepository
import space.midnightthoughts.nordiccalendar.util.Calendar
-import space.midnightthoughts.nordiccalendar.util.CalendarData
import space.midnightthoughts.nordiccalendar.util.Event
class CalendarViewModel(app: Application) : AndroidViewModel(app) {
val isRefreshing = MutableStateFlow(false)
- private val calendarData = CalendarData()
- private val contentResolver: ContentResolver = app.contentResolver
+ private val repository = CalendarRepository(app)
private val _calendars = MutableStateFlow<List<Calendar>>(emptyList())
val calendars: StateFlow<List<Calendar>> = _calendars.asStateFlow()
@@ -39,7 +36,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
private fun loadCalendars() {
isRefreshing.value = true
viewModelScope.launch {
- val allCalendars = calendarData.getCalendars(contentResolver)
+ val allCalendars = repository.getCalendars()
_calendars.value = allCalendars
updateEvents()
isRefreshing.value = false
@@ -47,18 +44,10 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
}
fun toggleCalendar(calendar: Calendar) {
- // TODO: Store this somewhere so it is kept across app restarts
- Log.d("CalendarViewModel", "Toggling calendar: ${calendar.name}, ID: ${calendar.id}")
-
- val updatedCalendars = _calendars.value.map {
- if (it.id == calendar.id) {
- it.copy(selected = !it.selected)
- } else {
- it
- }
+ viewModelScope.launch {
+ repository.setCalendarSelected(calendar.id, !calendar.selected)
+ loadCalendars()
}
- _calendars.value = updatedCalendars
- updateEvents()
}
fun setTab(tab: Int) {
@@ -132,27 +121,21 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
}
fun updateEvents() {
- isRefreshing.value = true
- // Zeitintervall immer neu berechnen, damit Pull-to-Refresh in jedem Tab funktioniert
- val tab = _selectedTab.value
- startMillis = getDefaultStartMillis(tab)
- endMillis = getDefaultEndMillis(tab)
- Log.d(
- "CalendarViewModel",
- "Updating events for selected calendars: ${
- _calendars.value.filter { it.selected }.map { it.name }
- }"
- )
viewModelScope.launch {
- val selectedIds = _calendars.value.filter { it.selected }.map { it.id }
- Log.d("CalendarViewModel", "Selected calendar IDs: $selectedIds")
- _events.value = calendarData.getEventsForCalendars(
- contentResolver,
- selectedIds,
- startMillis,
- endMillis
- )
- isRefreshing.value = false
+ isRefreshing.value = true
+ try {
+ val tab = _selectedTab.value
+ startMillis = getDefaultStartMillis(tab)
+ endMillis = getDefaultEndMillis(tab)
+ val selectedIds = _calendars.value.filter { it.selected }.map { it.id }
+ _events.value = repository.getEventsForCalendars(
+ selectedIds,
+ startMillis,
+ endMillis
+ )
+ } finally {
+ isRefreshing.value = false
+ }
}
}
@@ -165,7 +148,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
cal.set(java.util.Calendar.MINUTE, 59)
cal.set(java.util.Calendar.SECOND, 59)
endMillis = cal.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun prevDay() {
@@ -177,7 +160,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
cal.set(java.util.Calendar.MINUTE, 59)
cal.set(java.util.Calendar.SECOND, 59)
endMillis = cal.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun nextWeek() {
@@ -190,7 +173,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
cal.set(java.util.Calendar.MINUTE, 59)
cal.set(java.util.Calendar.SECOND, 59)
endMillis = cal.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun prevWeek() {
@@ -203,7 +186,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
cal.set(java.util.Calendar.MINUTE, 59)
cal.set(java.util.Calendar.SECOND, 59)
endMillis = cal.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun nextMonth() {
@@ -220,7 +203,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
cal.set(java.util.Calendar.MINUTE, 59)
cal.set(java.util.Calendar.SECOND, 59)
endMillis = cal.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun prevMonth() {
@@ -237,30 +220,9 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
cal.set(java.util.Calendar.MINUTE, 59)
cal.set(java.util.Calendar.SECOND, 59)
endMillis = cal.timeInMillis
- updateEventsCustom()
- }
-
- private fun updateEventsCustom() {
- isRefreshing.value = true
- Log.d(
- "CalendarViewModel",
- "Updating events for selected calendars: ${
- _calendars.value.filter { it.selected }.map { it.name }
- }"
- )
- viewModelScope.launch {
- val selectedIds = _calendars.value.filter { it.selected }.map { it.id }
- Log.d("CalendarViewModel", "Selected calendar IDs: $selectedIds")
- _events.value = calendarData.getEventsForCalendars(
- contentResolver,
- selectedIds,
- startMillis,
- endMillis
- )
- isRefreshing.value = false
- }
+ updateEvents()
}
-
+
fun setTodayWeek() {
if (_selectedTab.value != 1) return
val cal = java.util.Calendar.getInstance()
@@ -276,7 +238,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
set(java.util.Calendar.MINUTE, 59)
set(java.util.Calendar.SECOND, 59)
}.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun setTodayMonth() {
@@ -297,7 +259,7 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
set(java.util.Calendar.MINUTE, 59)
set(java.util.Calendar.SECOND, 59)
}.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
fun setTodayDay() {
@@ -313,6 +275,6 @@ class CalendarViewModel(app: Application) : AndroidViewModel(app) {
set(java.util.Calendar.MINUTE, 59)
set(java.util.Calendar.SECOND, 59)
}.timeInMillis
- updateEventsCustom()
+ updateEvents()
}
}
diff --git a/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/EventDetailsViewModel.kt b/app/src/main/java/space/midnightthoughts/nordiccalendar/viewmodels/EventDetailsViewModel.kt
@@ -0,0 +1,35 @@
+package space.midnightthoughts.nordiccalendar.viewmodels
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import space.midnightthoughts.nordiccalendar.data.CalendarRepository
+import space.midnightthoughts.nordiccalendar.util.Event
+
+class EventDetailsViewModel(
+ app: Application,
+ savedStateHandle: SavedStateHandle
+) : AndroidViewModel(app) {
+ private val repository = CalendarRepository(app)
+ private val _event = MutableStateFlow<Event?>(null)
+ val event: StateFlow<Event?> = _event.asStateFlow()
+
+ init {
+ val eventId = savedStateHandle.get<String>("eventId")?.toLongOrNull()
+ if (eventId != null) {
+ loadEvent(eventId)
+ }
+ }
+
+ private fun loadEvent(eventId: Long) {
+ viewModelScope.launch {
+ val event = repository.getEventById(eventId)
+ _event.value = event
+ }
+ }
+}