Backup & restore built its rows straight from the calendar list. That list
starts empty, catches to empty on a provider error, and can legitimately load
with nothing exportable and nothing importable — all three rendered as a blank
page with no buttons at all.
- `BackupUiState` (Loading / Failure / Ready) on `CalendarsViewModel`, mirroring
the `FilterUiState` pattern already used elsewhere.
- The restore row stays on screen whenever the screen shows anything at all.
When no calendar can receive events it says so and routes to the calendar
manager — whose row labels already explain per calendar *why* (read-only,
managed, not synced) — instead of disappearing.
- New `FailureReason.NoImportTarget` for the case the list cannot explain on its
own: calendars exist and are visible, but none can take an event.
- Export stays available on an all-hidden device. It reads the provider
directly, so `calendarListFailure` on its own would have hidden a working
action; the failure is raised only when both halves are dead.
- `LocalManageCalendars` is now provided around `BackupScreen` (it sits directly
over the manager, so the way out is popping back).
Also adds `IcsGoogleImportTest` over a Google Calendar export fixture:
`VTIMEZONE` with `X-LIC-LOCATION`, `TZID`-qualified `DTSTART`/`EXDATE`, folded
`DESCRIPTION` and `ATTENDEE` lines, `RECURRENCE-ID` overrides as separate
`VEVENT`s, escaped commas, all-day alarms. All 13 pass against the parser
unchanged — the read side needed no fix.
**Does not close #304.** The blank screen this fixes is real and reachable, but
it needs the device to have no usable calendar at all; a working Google calendar
satisfies `isEventTarget`, so that reporter would have seen the restore row. The
actual cause there is still open — questions posted on the issue. This stands on
its own as the missing loading and failure states.
Refs #304
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/314