feat: reminders data layer (Android) — model, settings, repository
CalEvent gains `reminders` (parsed from the server); AppSettings/SettingsStore gain `defaultReminderMinutes` (null = off) and it's sent via updateSettings; createLocalEvent/updateLocalEvent/eventBody and CalendarViewModel.saveEvent thread `reminders` through. UI (editor + settings picker) and the local notification scheduling follow next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Int>? = 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<Int>? = 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<Int>? = 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))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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<Int> = 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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,20 +401,21 @@ class CalendarViewModel @Inject constructor(
|
||||
description: String,
|
||||
color: String?,
|
||||
isPrivate: Boolean,
|
||||
reminders: List<Int> = 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<Int> = 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)
|
||||
|
||||
Reference in New Issue
Block a user