- SyncEngine: accepts onProgress callback — emits uploaded/downloaded/
deleted/bytes counts atomically as each file completes
- SyncWorker: streams progress to WorkManager data so the UI can poll
it live; reports per-run counters in the completion notification;
adds pause/resume support
- HomeViewModel/PairDetailViewModel: subscribe to live WorkManager
progress and surface it via SyncProgress state
- SyncPairEntity/SyncPairDao/SyncDatabase: persist last-run counters
(uploaded, downloaded, deleted, bytesTransferred) in the DB with a
Room migration (v3→v4)
- AppModule: provides WorkManager as an injectable singleton
- .gitignore: add .kotlin/ to exclude compiler session files
Security: no new issues — all logging via Timber (debug-only), DB
queries use Room parameterized API, file sharing via FileProvider.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- WebDavProvider: replace readBytes() with streaming RequestBody
(Okio sink.writeAll) so large files (1+ GB) upload without
allocating the full file in heap — fixes PARTIAL sync status
- App icon: replace vector XML with PNG mipmaps generated directly
from the user-provided reference image at all densities
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Icon: three identical parallel arcing arrows (same bezier curve, same blue-to-teal
gradient #64C8FF→#32EDBB, same arrowhead geometry) — visually cohesive and clearly
visible against the near-black background.
FileWatchService: FileObserver is now recursive — watchDirRecursive() creates an
observer for each subdirectory at startup, and adds new watchers when CREATE events
produce new directories. Fixes files added to subdirectories not being detected.
FilesViewModel: openFile/shareFile now fall back to download-then-open when the file
is absent locally. AccountRepository + ProviderFactory injected; downloads to
context.cacheDir/syncflow_open/ with isDownloading state. Path traversal guard added
(reject relativePath containing ".."). file_paths.xml gains cache-path entry.
WebDavProvider: path-traversal guard in parsePropfind — skip any server-returned
filename containing "..", "/" or "\". Replace android.util.Log with Timber so debug
logs are stripped from release builds.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix SYNC_COMPLETED showing ↑0 ↓0 ✗0 when only deletions occurred: add ✕N
for deleted files to the summary message (↑N ↓N ✕N ✗N format)
- Fix PairDetail Activity section showing raw "SYNC_STARTED" enum names and
"remote" as a plain subtitle: replace dot-based EventRow with the same
polished icon-bubble rows as the global Log tab
- Extract shared SyncEventRow composable + iconAndTint/label helpers to
ui/shared/SyncEventRow.kt; both LogScreen and PairDetailScreen now use it
- Add Files tab (4th tab between Log and Accounts): folder browser showing
all synced files per pair, grouped by subdirectory, with file-type icons,
size, last-synced date, and a summary header (N files, total size)
- Add SyncFileStateDao.observeForPair() reactive Flow query for Files tab
- Completely redesign app icon: near-black radial gradient background with
three bold directional arrows in an S-pattern (coral → silver → teal),
each with gradient fills and tip-glow dots — entirely different from the
typical circular sync-arrow style
- Bump version to 1.0.22 (build 23)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Request POST_NOTIFICATIONS permission at runtime in MainActivity (primary fix
for notifications never appearing on Android 13+ phones including Android 16)
- Register all 4 notification channels eagerly in SyncFlowApp.onCreate() instead
of lazily inside workers
- Add FOREGROUND_SERVICE_SHORT_SERVICE permission + shortService foreground type
for Android 16 foreground service compatibility
- Add global activity Log tab (new tab 2 in main nav) showing all sync events
across all pairs, grouped by date with pair name, event icon, and file detail
- Fix FileWatchService ON_CHANGE detection: ContentObserver on SAF tree URIs only
fires for SAF-API writes, not raw filesystem writes. Now resolves primary:/*
tree URIs to /storage/emulated/0/* and uses FileObserver for reliable detection
- Bump version to 1.0.21 (build 22)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CRITICAL
- SftpProvider: replace PromiscuousVerifier with TofuHostKeyVerifier
(trust-on-first-use; stores SHA-256 fingerprints in EncryptedSharedPreferences;
rejects key changes on subsequent connections)
HIGH
- GoogleDriveProvider: replace raw string interpolation with buildJsonObject
in uploadFile, createDirectory, and moveFile to prevent JSON injection
- DropboxProvider: replace all raw JSON strings and Dropbox-API-Arg headers
with buildJsonObject for the same reason
- OAuthHelper: add cryptographically random state parameter to Dropbox and
OneDrive authorization URLs (stored alongside the PKCE verifier)
- OAuthRedirectActivity: validate returned state against stored value before
exchanging the authorization code (CSRF protection)
MEDIUM
- WebDavProvider: block cross-host redirects in the manual redirect interceptor
so Authorization headers are never forwarded to a different server
- AccountSetupScreen: set FLAG_SECURE on the window while credential fields
are visible to prevent screenshots and screen-recording capture
- libs.versions.toml: security-crypto alpha06 → stable 1.0.0;
biometric-ktx alpha05 → biometric 1.1.0 (stable, non-ktx artifact matches
the BiometricManager/BiometricPrompt API actually used in MainActivity)
- CredentialStore: migrate to security-crypto 1.0.0 API (MasterKeys.getOrCreate
+ positional create() args); add saveHostKey/getHostFingerprint for SFTP TOFU
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add FileWatchService for real-time ON_CHANGE sync (FileObserver for
direct paths, ContentObserver for SAF content:// URIs), 5s debounce
- Fix remote browser stuck spinner: cancel in-flight jobs on navigation,
reset entries immediately, add Retry button on error
- Fix browser reuse bug: LaunchedEffect key now includes initialPath
- Fix WebDavProvider: rethrow XML parse errors (no more silent Empty
folder) and URL-decode file names from href
- Notifications now use BigTextStyle showing per-file-type counts
(Uploaded/Downloaded/Deleted) matching Autosync notification style
- Wire FileWatchService into BootReceiver and HomeViewModel toggle
- Register FileWatchService in AndroidManifest
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- LocalAccessor.Saf.delete() now uses docIdCache (same as openInputStream)
and catches IllegalStateException from DocumentsContract.deleteDocument
instead of propagating it through awaitAll() and crashing the whole sync
- WebDavProvider.getFileMetadata() passes dropFirst=false to parsePropfind
since Depth:0 returns exactly 1 result (the file); drop(1) was discarding it
- SyncEngine.performSync() calls ensureRemoteDirs() before each upload so
MKCOL is issued for any missing parent directories (405=exists is success)
- Bump version to 1.0.11 (code 12)
Verified against live Nextcloud: baseline ↑0 ↓0 ✗0, upload detection ↑1 ↓0 ✗0,
download detection ↑0 ↓1 ✗0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sync change detection:
- DbConverters was using epochSecond but comparisons used epochMilli —
every file appeared modified on every scan, causing full re-sync each time
- DB migration 2→3 clears sync_file_states (all stored timestamps wrong)
- First sync after upgrade re-learns state; subsequent syncs skip unchanged files
Biometric:
- Move prompt trigger from LaunchedEffect to onResume() — guarantees
the activity is in RESUMED state when authenticate() is called
- Add bestAuthenticators(): tries BIOMETRIC_STRONG|DEVICE_CREDENTIAL first,
falls back to BIOMETRIC_WEAK|DEVICE_CREDENTIAL for side-sensor phones
- canAuthenticate() now accepts either strong or weak+credential
- onAuthenticationError always shows Unlock button (no infinite retry loop)
- isLocked/showRetry are Activity-level state, no need for Compose remember
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Supports WebDAV, SFTP, SFTPGo, Nextcloud, ownCloud, Google Drive,
Dropbox, and OneDrive. Credentials encrypted with Android Keystore.
Biometric app-lock, conflict resolution, and auto-sync via WorkManager.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>