Add device sync: mirror folders/files locally, background WorkManager engine
Build APK / build (push) Successful in 7m33s

- New native sync engine (SyncEngine/SyncWorker/ConflictResolveWorker,
  WorkManager-based) that mirrors selected Nextcloud folders or files to
  app-private local storage, periodically and on-demand ("Sync now"),
  with never-auto-resolved conflict notifications.
- Single files (not just folders) can now be marked "Sync to device".
- Live sync status (syncing/synced/conflicts) pushes from native to Dart
  over a new EventChannel; the persistent header chip/panel now reflects
  device-sync status instead of the WebDAV-refresh loading state, with a
  cloud_off/cloud_sync/cloud_done/cloud_alert icon set and an expandable
  conflicts list with in-app "Keep local"/"Use server" resolution.
  Per-item cloud_done/sync badges show on Files tiles.
- Share/Download short-circuit to the local copy for already-synced
  files instead of a fresh network fetch.
- Settings gained a Device Sync section (per-folder list, "Sync
  everything", Wi-Fi-only toggle, manual "Sync now").
- Fixed two release-only bugs found via on-device testing: Android's
  HttpURLConnection silently rejects the PROPFIND method (switched to
  OkHttp), and R8 was stripping WorkManager's reflection-instantiated
  internals (broadened proguard-rules.pro to keep androidx.work.**
  wholesale rather than chasing individual classes).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-19 00:52:56 -04:00
co-authored by Claude Sonnet 5
parent 722b66f7b1
commit faaec5365f
21 changed files with 2023 additions and 42 deletions
+18
View File
@@ -0,0 +1,18 @@
# Device sync (SyncWorker/ConflictResolveWorker) - WorkManager instantiates
# a lot of its own internals via reflection (Workers by class name recorded
# at enqueue time, its bundled Room database's *_Impl class off the
# abstract database class's own name, its default InputMerger, etc). R8's
# member-level shrinking silently breaks any of these - e.g. stripping an
# "unused" no-arg constructor - even when the class itself survives a
# plain `-keep class` with no wildcard, and narrowly keeping only the
# classes hit by one test pass just means the next reflection path (a
# different WorkManager-internal class) breaks instead. Two separate
# instances of this already bit real testing (WorkDatabase, then
# OverwritingInputMerger) before landing on this broad keep - see
# android/app/build.gradle.kts (androidx.work dependency) and
# .claude/context/server.md's "Device sync" section.
-keep class androidx.work.** { *; }
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class dev.ayushya.noo.SyncWorker { *; }
-keep class dev.ayushya.noo.ConflictResolveWorker { *; }
-keep class dev.ayushya.noo.SyncConflictReceiver { *; }