Drag events to reschedule them (#68)
Long-press an event block in the week or day timeline and drag it to another time or day column; drag a chip in the month grid onto another date. The drop runs through the edit screen's own write path, so recurring writes, reminders and guests behave the same as a save. Two provider traps the drag had to close: - RRULE is written verbatim while DTSTART moves, so dragging a FREQ=WEEKLY;BYDAY=MO occurrence to a Wednesday under "all events" left a Wednesday anchor under a Monday rule and the series stayed put. realignRecurrence re-derives BYDAY/BYMONTHDAY/BYMONTH, and refuses rules one moved occurrence can't resolve (BYDAY=MO,WE, 2TH, BYSETPOS) — those may only move the single occurrence. - Nothing below the UI refuses a write, so allowsEventMove gates the gesture. Not isEventTarget: a managed contact-mirror event is editable but must never move. The pickup is hand-rolled rather than detectDragGesturesAfterLongPress: during the 500ms hold the block consumes nothing, so the scroll and page swipe claim at touch slop and kill the press, and the stock detector also cancels as soon as the finger leaves a block that may only be 10dp tall. Ours holds with a 6dp tolerance and never cancels on leaving bounds. Agenda is out of scope (list rows, no positioned blocks), as is resize-by-edge-drag. Month drags don't auto-scroll or page yet.
This commit is contained in:
@@ -108,6 +108,37 @@ on-device):
|
||||
UTC, so zones ahead of UTC can't leak an extra occurrence.
|
||||
- All-day events are normalised to UTC midnights with an exclusive end.
|
||||
|
||||
### Drag to reschedule
|
||||
|
||||
Dropping an event on another slot (#68) is not a second write path: the drop
|
||||
loads the event, prefills the **same** `toEditForm` the edit screen uses, shifts
|
||||
it (`EventShift.kt`), and dispatches through the same three repository calls a
|
||||
save does — so recurring writes, reminder reconciliation and attendee
|
||||
preservation behave identically. The full form is carried through, never a
|
||||
stripped one: `updateOccurrence` reconciles reminders *and* attendees onto the
|
||||
new exception row, and a partial form would wipe them.
|
||||
|
||||
Two things the drag has to get right that the edit screen sidesteps:
|
||||
|
||||
- **`RRULE` day parts go stale.** `buildEventUpdateValues` writes the rule
|
||||
verbatim while `DTSTART` moves, so dragging a `FREQ=WEEKLY;BYDAY=MO` occurrence
|
||||
onto a Wednesday under *All events* leaves a Wednesday anchor under a Monday
|
||||
rule and the series does not move. `realignRecurrence` re-derives `BYDAY` /
|
||||
`BYMONTHDAY` / `BYMONTH` from the new date, and returns **null** for rules one
|
||||
moved occurrence cannot resolve (`BYDAY=MO,WE`, `2TH`, `BYSETPOS`) — the drop
|
||||
then offers only *this event*, whose exception row carries no rule at all. The
|
||||
same staleness is reachable from the edit screen; wiring it there too is a
|
||||
separate change.
|
||||
- **The eligibility gate is load-bearing.** Nothing below the UI refuses a
|
||||
write, so `CalendarSource.allowsEventMove` is what keeps read-only and
|
||||
contact-managed events from being dragged. It is deliberately *not*
|
||||
`isEventTarget`: a managed event is editable (reminders, notes) yet must never
|
||||
move, while a switched-off calendar renders nothing to grab anyway.
|
||||
|
||||
`EventForm.problems()` runs before the write, so a drop that would push a series
|
||||
past its own `UNTIL` — which makes the provider generate zero occurrences and the
|
||||
event vanish — is refused rather than written.
|
||||
|
||||
### Event time zones
|
||||
|
||||
`EventForm.timezone` is the zone its wall-clock times mean, and **null means
|
||||
@@ -144,6 +175,19 @@ changes to untouched fields survive either way. Fields the form cannot
|
||||
write (attendees, status, reminder methods) are excluded so sync noise
|
||||
can't fake a conflict.
|
||||
|
||||
A **dropped** event gets no conflict dialog, deliberately. Its blast radius is
|
||||
already bounded by the same dirty check — only `ALL_DAY`, `EVENT_TIMEZONE`,
|
||||
`DTSTART` and `DTEND`/`DURATION`/`RRULE` are written — so a concurrent remote
|
||||
edit to the title, notes or guests survives untouched. What a drop *can* clobber
|
||||
is a concurrent remote **time** change, and parking a one-gesture action behind a
|
||||
modal would cost more than that case is worth; the undo in the confirmation
|
||||
snackbar is the answer instead. Undo restores semantics, not the row's byte
|
||||
shape (`DURATION` normalises to `P<n>S`/`P<n>D`, `EVENT_TIMEZONE` is stamped
|
||||
concrete), and it is offered only where the inverse is one symmetric write — a
|
||||
one-off event or a whole-series shift. *This event* leaves an exception row
|
||||
behind and *this and following* splits the series; neither is undone by shifting
|
||||
back, so both get a plain confirmation.
|
||||
|
||||
## Reminder delivery
|
||||
|
||||
Calendula plans and fires its own reminders. It reads the offsets in
|
||||
|
||||
Reference in New Issue
Block a user