feat(edit): move an event to another calendar (#39) #73

Merged
makiolaj merged 2 commits from feat/change-event-calendar into release/v2.15.0 2026-07-13 14:30:07 +00:00
Owner

Lets you change which calendar an existing event belongs to, straight from the editor — resolves #39.

What changed

  • The calendar row in the editor is tappable when editing (not just when creating). Picking a different calendar moves the event there on save. Managed special-dates calendars stay locked.
  • New CalendarRepository.moveEvent / CalendarDataSource.moveEvent implementing the move.

How the move works

CALENDAR_ID is sync-adapter-owned and can't be updated in place, so it's copy + delete (the Google Calendar / Etar approach):

  1. Re-insert the master row on the target calendar — verbatim recurrence skeleton (DTSTART/RRULE/DURATION/RDATE/EXDATE) and UID_2445 preserved so instance times line up and backup-dedup / sync identity survive.
  2. Copy reminders (with method) and editable guests.
  3. Replay every exception of a recurring series against the new master: modified occurrences via CONTENT_EXCEPTION_URI, cancellations as STATUS_CANCELED.
  4. Apply the user's field edits via the normal series updateEvent.
  5. Post-before-delete: all new-side work happens before the source is deleted; any failure rolls back the copy, so a move is all-or-nothing and never leaves a half-copied duplicate.

A calendar change forces whole-series scope, so it skips the recurring scope dialog. Colour is intentionally not carried across (a raw/keyed colour may be invalid on the target account), matching the existing calendar-switch behaviour.

Tests

  • EventWriteMapperTest — moved-master + copied-exception value builders (recurrence skeleton, DURATION-not-DTEND, no colour).
  • CalendarRepositoryImplTestmoveEvent delegation + failure propagation.
  • EventEditViewModelTest (new) — calendar change routes to moveEvent not updateEvent; recurring move skips the scope dialog; non-move recurring edit still asks for scope.

./gradlew test lint assembleDebug green.

On-device

Verified on the emulator (single event, recurring series with modified + cancelled occurrences, calendar-change + field edit in one save).

🤖 Generated with Claude Code

Lets you change which calendar an existing event belongs to, straight from the editor — resolves #39. ## What changed - The **calendar row in the editor is tappable when editing** (not just when creating). Picking a different calendar moves the event there on save. Managed special-dates calendars stay locked. - New `CalendarRepository.moveEvent` / `CalendarDataSource.moveEvent` implementing the move. ## How the move works `CALENDAR_ID` is sync-adapter-owned and can't be updated in place, so it's **copy + delete** (the Google Calendar / Etar approach): 1. Re-insert the master row on the target calendar — verbatim recurrence skeleton (`DTSTART`/`RRULE`/`DURATION`/`RDATE`/`EXDATE`) and `UID_2445` preserved so instance times line up and backup-dedup / sync identity survive. 2. Copy reminders (with method) and editable guests. 3. **Replay every exception** of a recurring series against the new master: modified occurrences via `CONTENT_EXCEPTION_URI`, cancellations as `STATUS_CANCELED`. 4. Apply the user's field edits via the normal series `updateEvent`. 5. **Post-before-delete**: all new-side work happens before the source is deleted; any failure rolls back the copy, so a move is all-or-nothing and never leaves a half-copied duplicate. A calendar change forces whole-series scope, so it **skips the recurring scope dialog**. Colour is intentionally not carried across (a raw/keyed colour may be invalid on the target account), matching the existing calendar-switch behaviour. ## Tests - `EventWriteMapperTest` — moved-master + copied-exception value builders (recurrence skeleton, DURATION-not-DTEND, no colour). - `CalendarRepositoryImplTest` — `moveEvent` delegation + failure propagation. - `EventEditViewModelTest` (new) — calendar change routes to `moveEvent` not `updateEvent`; recurring move skips the scope dialog; non-move recurring edit still asks for scope. `./gradlew test lint assembleDebug` green. ## On-device Verified on the emulator (single event, recurring series with modified + cancelled occurrences, calendar-change + field edit in one save). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
makiolaj added 1 commit 2026-07-13 14:12:40 +00:00
feat(edit): move an event to another calendar (#39)
All checks were successful
CI / ci (pull_request) Successful in 8m40s
4503847c0d
The calendar row in the editor is now tappable when editing an existing
event: picking a different calendar moves the event there on save, rather
than forcing a delete-and-recreate.

CALENDAR_ID is sync-adapter-owned and can't be updated in place, so the
move is copy+delete: the master row is re-inserted on the target calendar
(preserving UID_2445 so backup dedup and sync identity survive), its
reminders and editable guests are copied, and — for a recurring series —
every exception is replayed against the new master (modified occurrences
via CONTENT_EXCEPTION_URI, cancellations as STATUS_CANCELED). The user's
field edits are then applied with the normal series update. Everything on
the new side is built before the source is deleted (post-before-delete),
with a rollback of the copy on any failure, so a move is all-or-nothing.

A calendar change forces whole-series scope, so it skips the recurring
scope dialog. Managed special-dates calendars stay locked. Colour is not
carried across (a raw/keyed colour may be invalid on the target account),
matching the existing calendar-switch behaviour.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
makiolaj added 1 commit 2026-07-13 14:27:12 +00:00
ci(translations): run on every PR so the required check always reports
All checks were successful
Translations / check (pull_request) Successful in 5s
CI / ci (pull_request) Successful in 2m27s
114db7939c
The release/* branch protection requires the "Translations / check" status,
but the workflow was path-filtered to translation resources. A code-only PR
targeting a release branch never touches those, so the workflow never ran,
never posted its status, and the required check stayed pending forever —
permanently blocking the merge (only PRs that happened to change strings could
satisfy it).

Drop the path filter so it runs on every PR, mirroring the always-on `ci` job.
The parity check is SDK-free and passes when the committed translations are
consistent, so running it on unrelated PRs is effectively free.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
makiolaj merged commit 86ea72d56f into release/v2.15.0 2026-07-13 14:30:07 +00:00
makiolaj deleted branch feat/change-event-calendar 2026-07-13 14:30:07 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: makiolaj/calendula#73