diff --git a/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt b/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt index 97d8672..7bd60b6 100644 --- a/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt +++ b/app/src/main/java/com/scarriffle/calendarr/data/CalendarRepository.kt @@ -127,6 +127,8 @@ class CalendarRepository @Inject constructor( "month_divider_color" to s.monthDividerColor, "month_label_color" to s.monthLabelColor, "private_event_visibility" to s.privateEventVisibility, + // Explicit JSON null clears it (off); jsonBody drops Kotlin nulls. + "default_reminder_minutes" to (s.defaultReminderMinutes ?: org.json.JSONObject.NULL), ) ).ensureSuccess() } @@ -298,18 +300,18 @@ class CalendarRepository @Inject constructor( suspend fun createLocalEvent( calendarId: Int, title: String, start: Instant, end: Instant, isAllDay: Boolean, location: String, description: String, color: String?, - isPrivate: Boolean = false, + isPrivate: Boolean = false, reminders: List? = null, ) = guarded { - api.createLocalEvent(eventBody(calendarId, title, start, end, isAllDay, location, description, color, isPrivate)) + api.createLocalEvent(eventBody(calendarId, title, start, end, isAllDay, location, description, color, isPrivate, reminders)) .ensureSuccess() } suspend fun updateLocalEvent( uid: String, title: String, start: Instant, end: Instant, isAllDay: Boolean, location: String, description: String, color: String?, - isPrivate: Boolean = false, + isPrivate: Boolean = false, reminders: List? = null, ) = guarded { - api.updateLocalEvent(uid, eventBody(null, title, start, end, isAllDay, location, description, color, isPrivate)) + api.updateLocalEvent(uid, eventBody(null, title, start, end, isAllDay, location, description, color, isPrivate, reminders)) .ensureSuccess() } @@ -555,7 +557,7 @@ class CalendarRepository @Inject constructor( private fun eventBody( calendarId: Int?, title: String, start: Instant, end: Instant, isAllDay: Boolean, location: String, description: String, color: String?, - isPrivate: Boolean = false, + isPrivate: Boolean = false, reminders: List? = null, ) = jsonBody( buildMap { calendarId?.let { put("calendar_id", it) } @@ -567,6 +569,7 @@ class CalendarRepository @Inject constructor( put("description", description) if (!color.isNullOrBlank()) put("color", color) put("private", isPrivate) + if (reminders != null) put("reminders", org.json.JSONArray(reminders)) } ) } diff --git a/app/src/main/java/com/scarriffle/calendarr/data/SettingsStore.kt b/app/src/main/java/com/scarriffle/calendarr/data/SettingsStore.kt index cf7ab91..1198309 100644 --- a/app/src/main/java/com/scarriffle/calendarr/data/SettingsStore.kt +++ b/app/src/main/java/com/scarriffle/calendarr/data/SettingsStore.kt @@ -34,6 +34,7 @@ class SettingsStore @Inject constructor( language = prefs.getString(K_LANGUAGE, null) ?: "de", monthDividerColor = prefs.getString(K_DIVIDER, null) ?: "#7090c0", monthLabelColor = prefs.getString(K_LABEL, null) ?: "#7090c0", + defaultReminderMinutes = prefs.getInt(K_DEFAULT_REMINDER, -1).takeIf { it >= 0 }, ) fun saveSettings(s: AppSettings) { @@ -50,6 +51,7 @@ class SettingsStore @Inject constructor( .putString(K_LANGUAGE, s.language) .putString(K_DIVIDER, s.monthDividerColor) .putString(K_LABEL, s.monthLabelColor) + .putInt(K_DEFAULT_REMINDER, s.defaultReminderMinutes ?: -1) .apply() } @@ -83,6 +85,7 @@ class SettingsStore @Inject constructor( const val K_LANGUAGE = "language" const val K_DIVIDER = "month_divider_color" const val K_LABEL = "month_label_color" + const val K_DEFAULT_REMINDER = "default_reminder_minutes" const val K_CACHE_MONTHS = "cache_months" const val K_HIDDEN = "hidden_calendar_keys" const val K_BANISHED = "banished_calendar_keys" diff --git a/app/src/main/java/com/scarriffle/calendarr/domain/model/AppSettings.kt b/app/src/main/java/com/scarriffle/calendarr/domain/model/AppSettings.kt index 7f6aad5..c908688 100644 --- a/app/src/main/java/com/scarriffle/calendarr/domain/model/AppSettings.kt +++ b/app/src/main/java/com/scarriffle/calendarr/domain/model/AppSettings.kt @@ -25,6 +25,8 @@ data class AppSettings( // How this user's private events appear to other group members: 'hidden' | 'busy'. @Json(name = "private_event_visibility") val privateEventVisibility: String = "busy", @Json(name = "group_visible_calendar_id") val groupVisibleCalendarId: Int? = null, + // Minutes-before-start applied to all events client-side; null = off. + @Json(name = "default_reminder_minutes") val defaultReminderMinutes: Int? = null, ) { val weekStartsOnMonday: Boolean get() = weekStartDay != "sunday" } diff --git a/app/src/main/java/com/scarriffle/calendarr/domain/model/CalEvent.kt b/app/src/main/java/com/scarriffle/calendarr/domain/model/CalEvent.kt index f90d9a2..5664219 100644 --- a/app/src/main/java/com/scarriffle/calendarr/domain/model/CalEvent.kt +++ b/app/src/main/java/com/scarriffle/calendarr/domain/model/CalEvent.kt @@ -34,6 +34,8 @@ data class CalEvent( // Server-decorated title for the group combined view (group icon / owner // prefix); rendered in group mode while `title` stays raw for editing. val displayTitle: String? = null, + // Reminder offsets in minutes-before-start (0 = at start). Local events only. + val reminders: List = emptyList(), ) { /** * Group view supplies a server-resolved colour (display_color); otherwise @@ -121,6 +123,9 @@ data class CalEvent( isGroupEvent = json.optBoolean("is_group_event", false), displayColor = json.strOrNull("display_color"), displayTitle = json.strOrNull("display_title"), + reminders = json.optJSONArray("reminders")?.let { arr -> + (0 until arr.length()).mapNotNull { (arr.opt(it) as? Number)?.toInt() } + } ?: emptyList(), ) } } diff --git a/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt b/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt index eb4b5fe..3cb86a3 100644 --- a/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt +++ b/app/src/main/java/com/scarriffle/calendarr/ui/calendar/CalendarViewModel.kt @@ -401,20 +401,21 @@ class CalendarViewModel @Inject constructor( description: String, color: String?, isPrivate: Boolean, + reminders: List = emptyList(), onResult: (String?) -> Unit, ) { viewModelScope.launch { val result = runCatching { if (existing != null && existing.source == calendar.source) { when (existing.source) { - "local" -> repository.updateLocalEvent(existing.id, title, start, end, isAllDay, location, description, color, isPrivate) + "local" -> repository.updateLocalEvent(existing.id, title, start, end, isAllDay, location, description, color, isPrivate, reminders) "caldav" -> repository.updateCalDAVEvent(existing.id, existing.url, calendar.numericId, title, start, end, isAllDay, location, description, color) "homeassistant" -> repository.updateHAEvent(calendar.numericId, existing.id, title, start, end, isAllDay, location, description) "google" -> repository.updateGoogleEvent(calendar.numericId, existing.id, title, start, end, isAllDay, location, description) - else -> createForSource(calendar, title, start, end, isAllDay, location, description, color, isPrivate) + else -> createForSource(calendar, title, start, end, isAllDay, location, description, color, isPrivate, reminders) } } else { - createForSource(calendar, title, start, end, isAllDay, location, description, color, isPrivate) + createForSource(calendar, title, start, end, isAllDay, location, description, color, isPrivate, reminders) } } result.onSuccess { afterMutation(); onResult(null) } @@ -425,9 +426,10 @@ class CalendarViewModel @Inject constructor( private suspend fun createForSource( calendar: WritableCalendar, title: String, start: Instant, end: Instant, isAllDay: Boolean, location: String, description: String, color: String?, isPrivate: Boolean, + reminders: List = emptyList(), ) { when (calendar.source) { - "local" -> repository.createLocalEvent(calendar.numericId, title, start, end, isAllDay, location, description, color, isPrivate) + "local" -> repository.createLocalEvent(calendar.numericId, title, start, end, isAllDay, location, description, color, isPrivate, reminders) "caldav" -> repository.createCalDAVEvent(calendar.numericId, title, start, end, isAllDay, location, description, color) "google" -> repository.createGoogleEvent(calendar.numericId, title, start, end, isAllDay, location, description) "homeassistant" -> repository.createHAEvent(calendar.numericId, title, start, end, isAllDay, location, description)