Compare commits
4 Commits
d398c72005
...
renovate/t
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b54de1bf7 | |||
| 8ddc11159b | |||
|
|
6736bae5ee | ||
|
|
21d1726d7a |
@@ -492,13 +492,7 @@ class AndroidCalendarDataSource @Inject constructor(
|
||||
return resolver.query(
|
||||
uri,
|
||||
InstanceProjection.COLUMNS,
|
||||
// Hide cancelled occurrences: "delete only this event" writes a
|
||||
// cancelled exception for the one instance (#47). A NULL status is a
|
||||
// normal, un-cancelled event, so it must survive the filter — a bare
|
||||
// `!= CANCELED` would drop it (NULL != 2 is NULL, not true).
|
||||
"${CalendarContract.Instances.STATUS} IS NULL OR " +
|
||||
"${CalendarContract.Instances.STATUS} != ${CalendarContract.Events.STATUS_CANCELED}",
|
||||
null,
|
||||
null, null,
|
||||
CalendarContract.Instances.BEGIN + " ASC",
|
||||
)?.use { c -> c.mapAllNotNull { CursorColumnReader(c).toEventInstance() } } ?: emptyList()
|
||||
}
|
||||
@@ -1189,25 +1183,15 @@ class AndroidCalendarDataSource @Inject constructor(
|
||||
|
||||
override fun deleteOccurrence(eventId: Long, beginMillis: Long) {
|
||||
// A cancelled exception row hides exactly this occurrence; the sync
|
||||
// adapter turns it into an EXDATE/cancelled VEVENT upstream. It has to
|
||||
// carry the full time set (DTSTART + DURATION + zone), not just STATUS:
|
||||
// the provider only demotes the cloned exception to a single instance —
|
||||
// clearing the inherited RRULE — when it can derive that instance from
|
||||
// those columns. A STATUS-only cancel left the RRULE standing and
|
||||
// cancelled the whole series, wiping every other occurrence (#47), the
|
||||
// same trap the edit path documents (Codeberg #16).
|
||||
val row = querySeriesRow(eventId)
|
||||
val values = buildOccurrenceCancelValues(
|
||||
originalInstanceMillis = beginMillis,
|
||||
dtStartMillis = beginMillis,
|
||||
duration = row.duration,
|
||||
timezone = row.timezone,
|
||||
allDay = row.allDay,
|
||||
)
|
||||
// adapter turns it into an EXDATE/cancelled VEVENT upstream.
|
||||
val values = ContentValues().apply {
|
||||
put(CalendarContract.Events.ORIGINAL_INSTANCE_TIME, beginMillis)
|
||||
put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CANCELED)
|
||||
}
|
||||
val uri = ContentUris.withAppendedId(
|
||||
CalendarContract.Events.CONTENT_EXCEPTION_URI, eventId,
|
||||
)
|
||||
resolver.insert(uri, values.toContentValues())
|
||||
resolver.insert(uri, values)
|
||||
?: throw WriteFailedException("cancel occurrence event id=$eventId begin=$beginMillis")
|
||||
}
|
||||
|
||||
|
||||
@@ -182,33 +182,6 @@ internal fun buildOccurrenceExceptionValues(
|
||||
putAll(eventColorColumns(form.colorKey, form.color))
|
||||
}
|
||||
|
||||
/**
|
||||
* Column values for a *cancelled*-occurrence exception row ("delete only this
|
||||
* event"): inserting them at `Events.CONTENT_EXCEPTION_URI/<id>` makes the
|
||||
* provider clone the series row and cancel exactly this one instance.
|
||||
*
|
||||
* As with [buildOccurrenceExceptionValues], the occurrence must be anchored with
|
||||
* DTSTART + DURATION so the provider derives a single instance and clears the
|
||||
* inherited RRULE. A STATUS-only cancel skips that: the clone keeps the RRULE, so
|
||||
* the *whole series* is cancelled and every other occurrence disappears
|
||||
* (Codeberg #47). The occurrence's length/zone come straight from the series row
|
||||
* — cancelling never changes them.
|
||||
*/
|
||||
internal fun buildOccurrenceCancelValues(
|
||||
originalInstanceMillis: Long,
|
||||
dtStartMillis: Long,
|
||||
duration: String?,
|
||||
timezone: String?,
|
||||
allDay: Int,
|
||||
): Map<String, Any?> = buildMap {
|
||||
put(CalendarContract.Events.ORIGINAL_INSTANCE_TIME, originalInstanceMillis)
|
||||
put(CalendarContract.Events.DTSTART, dtStartMillis)
|
||||
put(CalendarContract.Events.DURATION, duration)
|
||||
put(CalendarContract.Events.EVENT_TIMEZONE, timezone)
|
||||
put(CalendarContract.Events.ALL_DAY, allDay)
|
||||
put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CANCELED)
|
||||
}
|
||||
|
||||
/**
|
||||
* The `EVENT_COLOR` / `EVENT_COLOR_KEY` columns for a colour selection. A
|
||||
* [colorKey] writes the key alone (the provider derives `EVENT_COLOR` from the
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
<string name="settings_language">App-Sprache</string>
|
||||
<string name="settings_language_auto">Systemstandard</string>
|
||||
<!-- Hub category subtitles -->
|
||||
<string name="settings_appearance_subtitle">Design, Standartansicht, Wochenstart</string>
|
||||
<string name="settings_appearance_subtitle">Design, Standardansicht, Wochenstart</string>
|
||||
<string name="settings_event_form_subtitle">Standardfelder für neue Termine</string>
|
||||
<string name="settings_notifications_subtitle">Termin-Erinnerungen</string>
|
||||
<string name="settings_section_about">Über</string>
|
||||
@@ -474,4 +474,20 @@
|
||||
<string name="special_dates_default_title_birthday">Geburtstag von {name} ({year})</string>
|
||||
<string name="special_dates_default_title_anniversary">Jahrestag von {name} ({year})</string>
|
||||
<string name="special_dates_default_title_custom">{name}</string>
|
||||
<string name="settings_week_numbers">Kalenderwochen</string>
|
||||
<string name="settings_week_numbers_summary">Kalenderwochen der Monatsansicht anzeigen</string>
|
||||
<string name="calendars_export_title">Kalender exportieren</string>
|
||||
<string name="calendars_export_hint">Wähle aus, welche Kalender in die .ics-Datei aufgenommen werden sollen.</string>
|
||||
<string name="calendars_export_action">Exportieren</string>
|
||||
<string name="calendars_restore_header">Wiederherstellen</string>
|
||||
<string name="calendars_restore_action">Aus .ics-Datei wiederherstellen</string>
|
||||
<string name="calendars_restore_hint">Importiere Ereignisse aus einem Backup oder einer anderen Kalender-App.</string>
|
||||
<string name="import_done_dedup_note">Bereits im Kalender vorhandene Ereignisse wurden übersprungen.</string>
|
||||
<string name="import_done_added_label">Hinzugefügt</string>
|
||||
<string name="import_done_skipped_label">Duplikate</string>
|
||||
<string name="import_button">Importieren</string>
|
||||
<plurals name="import_title_count">
|
||||
<item quantity="one">%d Ereignis wird importiert</item>
|
||||
<item quantity="other">%d Ereignisse werden importiert</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
<string name="state_failure_no_calendars">Nessun calendario configurato.</string>
|
||||
<string name="state_failure_no_calendars_action">Apri le impostazioni calendario di sistema</string>
|
||||
<string name="state_failure_provider">Errore nella lettura del calendario.</string>
|
||||
<string name="permission_rationale_title">Guarda tutti i tuoi eventi, magnificamente</string>
|
||||
<string name="permission_rationale_body">Calendula ha bisogno di accedere al tuo calendario per mostrare e gestire gli eventi. È l\'unica cosa che ti chiederà.</string>
|
||||
<string name="permission_rationale_title">Gestisci magnificamente tutti i tuoi eventi</string>
|
||||
<string name="permission_rationale_body">Calendula ha bisogno di accedere al tuo calendario per mostrare e gestire gli eventi. È l\'unica cosa di cui ha bisogno e nessuna informazione lascerà mai il tuo dispositivo.</string>
|
||||
<string name="permission_request_button">Concedi l\'accesso al calendario</string>
|
||||
<string name="permission_denied_title">Accesso al calendario negato</string>
|
||||
<string name="permission_denied_body">Calendula non può mostrare gli eventi senza accesso al calendario. Puoi concederlo dalle impostazioni di sistema.</string>
|
||||
<string name="permission_open_settings_button">Apri le impostazioni di sistema</string>
|
||||
<string name="permission_retry_button">Riprova</string>
|
||||
<string name="permission_benefit_private_title">Rimane sul tuo dispositivo</string>
|
||||
<string name="permission_benefit_private_body">I tuoi calendari sono letti localmente e non lasciano mai il tuo telefono.</string>
|
||||
<string name="permission_benefit_private_body">I tuoi calendari sono letti localmente e non lasciano mai il tuo dispositivo.</string>
|
||||
<string name="permission_benefit_sync_title">Tutti i tuoi calendari, insieme</string>
|
||||
<string name="permission_benefit_sync_body">Google, CalDAV, locali - qualsiasi calendario sincronizzato sul dispositivo semplicemente compare.</string>
|
||||
<string name="permission_benefit_sync_body">Google, CalDAV, locali - qualsiasi calendario sincronizzato sul dispositivo, semplicemente, compare.</string>
|
||||
<string name="permission_benefit_privacy_title">Nessun tracking, per sempre</string>
|
||||
<string name="permission_benefit_privacy_body">Zero telemetria, zero analytics, zero pubblicità.</string>
|
||||
<string name="permission_privacy_footnote">Rimane sul tuo dispositivo - nessun accesso a internet</string>
|
||||
@@ -38,7 +38,7 @@
|
||||
<string name="month_action_settings">Impostazioni</string>
|
||||
<string name="month_a11y_today_prefix">Oggi</string>
|
||||
<string name="week_today_action">Questa settimana</string>
|
||||
<string name="week_number_label">Wk</string>
|
||||
<string name="week_number_label">Sett.</string>
|
||||
<string name="day_today_action">Oggi</string>
|
||||
<string name="event_detail_back">Indietro</string>
|
||||
<string name="event_detail_edit">Modifica</string>
|
||||
@@ -73,14 +73,14 @@
|
||||
<string name="event_edit_remove_reminder">Rimuovi promemoria</string>
|
||||
<string name="event_edit_attendees">Partecipanti</string>
|
||||
<string name="event_edit_add_guest">Aggiungi partecipante</string>
|
||||
<string name="event_edit_add_guest_hint">Aggiungi un partecipante via email…</string>
|
||||
<string name="event_edit_add_guest_hint">Aggiungi un partecipante tramite indirizzo email…</string>
|
||||
<string name="event_edit_add_guest_from_contacts">Aggiungi dai contatti</string>
|
||||
<string name="event_edit_location_from_contacts">Scegli un indirizzo dai contatti</string>
|
||||
<string name="event_edit_remove_guest">Rimuovi partecipante</string>
|
||||
<string name="event_edit_attendee_required">Obbligatorio</string>
|
||||
<string name="event_edit_attendee_optional">Facoltativo</string>
|
||||
<string name="event_edit_attendees_note_synced">Calendula non manda gli inviti agli eventi. Il tuo account calendario potrà inviare le email ai partecipanti al momento della sincronizzazione.</string>
|
||||
<string name="event_edit_attendees_note_local">Salvato sul dispositivo. Nessuno ha ricevuto notifiche.</string>
|
||||
<string name="event_edit_attendees_note_local">Salvato sul dispositivo. Nessuno dei partecipanti ha ricevuto notifiche.</string>
|
||||
<string name="event_edit_reminder_custom">Personalizza</string>
|
||||
<string name="reminder_unit_minutes">minuti</string>
|
||||
<string name="reminder_unit_hours">ore</string>
|
||||
@@ -89,7 +89,7 @@
|
||||
<string name="event_edit_availability">Disponibilità</string>
|
||||
<string name="event_edit_visibility">Visibilità</string>
|
||||
<string name="event_edit_color">Colore</string>
|
||||
<string name="event_edit_color_default">Colore calendario</string>
|
||||
<string name="event_edit_color_default">Colore del calendario</string>
|
||||
<string name="event_edit_color_custom">Colore personalizzato</string>
|
||||
<string name="event_edit_color_reset">Reset</string>
|
||||
<string name="event_edit_color_unsupported">Non disponibile per questo calendario</string>
|
||||
@@ -99,13 +99,13 @@
|
||||
<string name="event_edit_conflict_body">Durante la modifica l\'evento è stato cambiato, tramite sincronizzazione o tramite altra app. Cosa fare con le modifiche in corso?</string>
|
||||
<string name="event_edit_conflict_overwrite">Salva le modifiche</string>
|
||||
<string name="event_edit_conflict_overwrite_hint">Sovrascrivi solo i campi modificati</string>
|
||||
<string name="event_edit_conflict_discard">Annulla le mie modifiche</string>
|
||||
<string name="event_edit_conflict_discard_hint">Mantieni l\'evento com\'è ora</string>
|
||||
<string name="event_edit_conflict_discard">Annulla le modifiche</string>
|
||||
<string name="event_edit_conflict_discard_hint">Lascia l\'evento invariato</string>
|
||||
<string name="event_edit_gone_title">Evento eliminato</string>
|
||||
<string name="event_edit_gone_body">Nel frattempo, questo evento è stato eliminato, ad esempio su un altro dispositivo. Le modifiche non possono più essere salvate.</string>
|
||||
<string name="event_edit_recurrence_none">Non si ripete</string>
|
||||
<string name="event_edit_recurrence_custom">Personalizza</string>
|
||||
<string name="event_edit_recurrence_every">La serie</string>
|
||||
<string name="event_edit_recurrence_every">Ogni</string>
|
||||
<string name="recurrence_unit_days">giorni</string>
|
||||
<string name="recurrence_unit_weeks">settimane</string>
|
||||
<string name="recurrence_unit_months">mesi</string>
|
||||
@@ -113,7 +113,7 @@
|
||||
<string name="event_edit_recurrence_ends">Fine</string>
|
||||
<string name="event_edit_recurrence_end_never">Mai</string>
|
||||
<string name="event_edit_recurrence_end_until">Fino alla data</string>
|
||||
<string name="event_edit_recurrence_end_count">Dopo un certo numero di occorrenze</string>
|
||||
<string name="event_edit_recurrence_end_count">Numero di occorrenze</string>
|
||||
<string name="event_edit_recurrence_times">occorrenze</string>
|
||||
<string name="event_edit_error_recurrence_ends_before_start">Fine antecedente l\'inizio</string>
|
||||
<string name="event_availability_busy">Occupato</string>
|
||||
@@ -125,7 +125,7 @@
|
||||
<string name="event_detail_location">Luogo</string>
|
||||
<string name="event_detail_description">Descrizione</string>
|
||||
<string name="event_detail_attendees">Partecipanti</string>
|
||||
<string name="recurrence_daily">Ogni giorni</string>
|
||||
<string name="recurrence_daily">Ogni giorno</string>
|
||||
<string name="recurrence_weekly">Ogni settimana</string>
|
||||
<string name="recurrence_monthly">Ogni mese</string>
|
||||
<string name="recurrence_yearly">Ogni anno</string>
|
||||
@@ -204,7 +204,7 @@
|
||||
<string name="reminder_benefit_reversible_body">Lo switch è nelle Impostazioni, sezione Notifiche.</string>
|
||||
<string name="reminder_onboarding_enable_button">Attiva i promemoria</string>
|
||||
<string name="reminder_onboarding_skip_button">Non ora</string>
|
||||
<string name="reminder_action_snooze">Posponi</string>
|
||||
<string name="reminder_action_snooze">Posticipa</string>
|
||||
<string name="reminder_action_dismiss">Ignora</string>
|
||||
<string name="view_month">Mese</string>
|
||||
<string name="view_week">Settimana</string>
|
||||
@@ -418,16 +418,16 @@
|
||||
<string name="settings_default_reminder">Promemoria standard</string>
|
||||
<string name="settings_notifications_subtitle">Promemoria evento</string>
|
||||
<string name="shortcut_new_event_short">Nuovo evento</string>
|
||||
<string name="event_edit_managed_hint">Gestito da “%1$s” - titolo, data e ripetizioni sono sincronizzate dai tuoi contatti. Promemoria e note sono modificabili dall\'utente</string>
|
||||
<string name="settings_font_headings">Carattere dell\'intestazione</string>
|
||||
<string name="event_edit_managed_hint">Gestito da “%1$s” - titolo, data e ripetizioni sono sincronizzate dai tuoi contatti. Promemoria e note sono modificabili dall\'utente.</string>
|
||||
<string name="settings_font_headings">Carattere del titolo</string>
|
||||
<string name="settings_font_body">Carattere del corpo</string>
|
||||
<string name="settings_font_system">Predefinito di sistema</string>
|
||||
<string name="settings_font_choose_file">Scegli file…</string>
|
||||
<string name="settings_font_custom_selected">Carattere personalizzato</string>
|
||||
<string name="settings_font_custom_selected">Font personalizzato</string>
|
||||
<string name="settings_font_import_failed">Impossibile leggere il file come font</string>
|
||||
<string name="settings_section_views">Viste</string>
|
||||
<string name="settings_quick_switch_header">Pulsante di cambio rapido</string>
|
||||
<string name="settings_quick_switch_hint">Scegli attraverso quali viste scorrere mediante il pulsante in alto a destra, trascinale per riordinarle. Le viste disattivate rimangono raggiungibili dal menù di navigazione.</string>
|
||||
<string name="settings_quick_switch_hint">Scegli attraverso quali viste scorrere col pulsante in alto a destra, trascinale per riordinarle. Le viste disattivate rimangono raggiungibili dal menù di navigazione.</string>
|
||||
<string name="settings_drawer_order_header">Menù di navigazione</string>
|
||||
<string name="settings_drawer_order_hint">Trascina per riordinare le viste elencate nel menù di navigazione.</string>
|
||||
<string name="reorder_drag_handle">Trascina per riordinare</string>
|
||||
@@ -463,4 +463,7 @@
|
||||
<string name="special_dates_calendar_custom">Date speciali</string>
|
||||
<string name="special_dates_default_title_birthday">Compleanno di {name} ({year})</string>
|
||||
<string name="special_dates_default_title_anniversary">Anniversario di {name} ({year})</string>
|
||||
<string name="font_lora">Lora</string>
|
||||
<string name="font_jetbrains_mono">JetBrains Mono</string>
|
||||
<string name="special_dates_default_title_custom">{name}</string>
|
||||
</resources>
|
||||
|
||||
@@ -220,46 +220,6 @@ class EventWriteMapperTest {
|
||||
assertThat(values).containsEntry(CalendarContract.Events.DESCRIPTION, null)
|
||||
}
|
||||
|
||||
// --- buildOccurrenceCancelValues ("delete only this event") ---
|
||||
|
||||
@Test
|
||||
fun `occurrence cancel anchors a single instance and cancels only it`() {
|
||||
val values = buildOccurrenceCancelValues(
|
||||
originalInstanceMillis = 1_700_000_000_000L,
|
||||
dtStartMillis = 1_700_000_000_000L,
|
||||
duration = "P3600S",
|
||||
timezone = "Europe/Berlin",
|
||||
allDay = 0,
|
||||
)
|
||||
assertThat(values[CalendarContract.Events.ORIGINAL_INSTANCE_TIME])
|
||||
.isEqualTo(1_700_000_000_000L)
|
||||
// DTSTART + DURATION make the provider derive a single instance and drop
|
||||
// the inherited RRULE, so only this occurrence is cancelled — not the
|
||||
// whole series (#47). DTEND is never sent (the provider rejects it).
|
||||
assertThat(values[CalendarContract.Events.DTSTART]).isEqualTo(1_700_000_000_000L)
|
||||
assertThat(values[CalendarContract.Events.DURATION]).isEqualTo("P3600S")
|
||||
assertThat(values[CalendarContract.Events.EVENT_TIMEZONE]).isEqualTo("Europe/Berlin")
|
||||
assertThat(values[CalendarContract.Events.STATUS])
|
||||
.isEqualTo(CalendarContract.Events.STATUS_CANCELED)
|
||||
assertThat(values).doesNotContainKey(CalendarContract.Events.DTEND)
|
||||
assertThat(values).doesNotContainKey(CalendarContract.Events.RRULE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `all-day occurrence cancel keeps the all-day flag and utc zone`() {
|
||||
val values = buildOccurrenceCancelValues(
|
||||
originalInstanceMillis = 1_700_000_000_000L,
|
||||
dtStartMillis = 1_700_000_000_000L,
|
||||
duration = "P1D",
|
||||
timezone = "UTC",
|
||||
allDay = 1,
|
||||
)
|
||||
assertThat(values[CalendarContract.Events.ALL_DAY]).isEqualTo(1)
|
||||
assertThat(values[CalendarContract.Events.EVENT_TIMEZONE]).isEqualTo("UTC")
|
||||
assertThat(values[CalendarContract.Events.STATUS])
|
||||
.isEqualTo(CalendarContract.Events.STATUS_CANCELED)
|
||||
}
|
||||
|
||||
// --- per-event colour ---
|
||||
|
||||
@Test
|
||||
|
||||
@@ -13,14 +13,14 @@ composeBom = "2026.05.01"
|
||||
# Re-evaluate when 1.5.0 stable lands.
|
||||
material3 = "1.5.0-alpha21"
|
||||
datastore = "1.2.1"
|
||||
junit = "6.1.0"
|
||||
junitPlatform = "6.1.0"
|
||||
junit = "6.1.2"
|
||||
junitPlatform = "6.1.2"
|
||||
truth = "1.4.5"
|
||||
androidxJunit = "1.3.0"
|
||||
espressoCore = "3.7.0"
|
||||
kotlinxDatetime = "0.7.0"
|
||||
kotlinxCoroutines = "1.10.2"
|
||||
turbine = "1.2.0"
|
||||
turbine = "1.2.1"
|
||||
hiltNavigationCompose = "1.3.0"
|
||||
lifecycleCompose = "2.10.0"
|
||||
androidxTestRules = "1.7.0"
|
||||
|
||||
Reference in New Issue
Block a user