feat(export): write task lists out as iCalendar
Step 3 of docs/STORAGE-AND-SYNC.md. Now that our own provider holds the data in the app's private storage, a Local-mode user's tasks exist in exactly one place and uninstalling deletes them — on Play, where most people will never have a sync engine, that is the majority case. So export is a v1 feature, not a nicety. One .ics per list, because a list is a CalDAV collection and that is the unit other clients understand; folding everything into one file would flatten the lists away, and list membership is not recoverable from a VTODO afterwards. ExportWriter can put them in a folder (ACTION_OPEN_DOCUMENT_TREE) or a single zip (ACTION_CREATE_DOCUMENT). No storage permission either way — SAF hands us a Uri the user picked. Two things needed care: Export reads the tasks table, not the instances view the rest of the app reads from. In the instances view a recurring task appears once per occurrence with its times resolved and no rule attached, so exporting from there would write the same task fifty times and lose the RRULE that generated them. And local tasks have no UID. The dmfs provider only lets a sync adapter assign one, so in Local mode every task arrives with _uid null — and a VTODO without a UID is both invalid and un-mergeable, meaning a re-imported backup would duplicate every task rather than match it. ICalendarWriter synthesises one from the row id, stable across exports and tagged so it is recognisable as synthetic. Times go out in UTC rather than with a TZID. Emitting TZID obliges us to emit a matching VTIMEZONE with its transition rules, and a TZID referencing an absent definition is what actually breaks importers. All-day values keep VALUE=DATE, the only form that survives a timezone change intact. The writer is pure Kotlin with no Android in it and is covered by 40 tests — line folding counted in octets and never splitting a UTF-8 sequence, TEXT escaping, forward references from a subtask to a parent later in the file, and CRLF endings. An export is only as good as its ability to be read back, and nothing about a malformed .ics is obvious until someone needs the backup. The SAF plumbing is marked in the storage doc as floret-kit material. Kept app-local for now on the kit's own stated principle of not extracting before a second consumer exists; the seam is in place, so moving it is a file move. Backend only — no UI yet; that comes with the frontend pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,7 @@ dependencies {
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.documentfile)
|
||||
|
||||
implementation(libs.androidx.glance.appwidget)
|
||||
implementation(libs.androidx.glance.material3)
|
||||
|
||||
Reference in New Issue
Block a user