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>
35 lines
1.1 KiB
Dart
35 lines
1.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../models/sync_status.dart';
|
|
|
|
/// A small corner badge over a thumbnail showing device-sync status - a
|
|
/// `cloud_done` badge once a file's mirrored locally, `sync` while it's
|
|
/// actively being transferred. Nothing is drawn for `none`/`conflict`
|
|
/// (conflicts surface in the sync header, not as per-tile noise).
|
|
class SyncStatusBadge extends StatelessWidget {
|
|
final SyncItemStatus status;
|
|
|
|
const SyncStatusBadge({super.key, required this.status});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (status != SyncItemStatus.syncing && status != SyncItemStatus.synced) {
|
|
return const SizedBox.shrink();
|
|
}
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
final icon = status == SyncItemStatus.syncing
|
|
? Icons.sync_rounded
|
|
: Icons.cloud_done_rounded;
|
|
|
|
return Container(
|
|
width: 18,
|
|
height: 18,
|
|
decoration: BoxDecoration(
|
|
color: colorScheme.surface,
|
|
shape: BoxShape.circle,
|
|
border: Border.all(color: colorScheme.surface, width: 1.5),
|
|
),
|
|
child: Icon(icon, size: 13, color: colorScheme.primary),
|
|
);
|
|
}
|
|
}
|