Add in-app copy/move, background downloads, and a Favorites tab
Build APK / build (push) Successful in 5m18s

- WebDAV MOVE/COPY-backed copy/move for files and folders, with a
  destination picker (independent nav state so it doesn't disturb the
  Files tab's browsing position) and a conflict-resolution sheet
  (overwrite all / keep both / decide per item).
- Downloads now use the same foreground-service + notification approach
  as uploads, surviving app closure.
- Favorites is now its own tab (account-wide, via a WebDAV SEARCH query)
  instead of a Files-tab filter toggle, which couldn't represent
  favorited items outside the currently browsed folder correctly.
- Share sheet restyled to match the app's design language, plus a
  "share file directly" action via the OS share sheet; details sheet
  tab icons resized to match the bottom nav.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 16:20:56 -04:00
co-authored by Claude Sonnet 5
parent 44a23c3632
commit b1400e399f
21 changed files with 2851 additions and 155 deletions
+12 -13
View File
@@ -1,7 +1,6 @@
import 'dart:convert';
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/material.dart';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart' as p;
@@ -11,6 +10,7 @@ import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
import '../models/nextcloud_item.dart';
import '../providers/server_provider.dart';
import '../services/download_service.dart';
import '../widgets/details/details_sheet.dart';
import '../widgets/frosted_glass_container.dart';
import '../widgets/marquee_title.dart';
@@ -189,21 +189,22 @@ class _FileViewerScreenState extends State<FileViewerScreen> {
}
}
/// Hands off to `DownloadService.kt` (see its doc comment) rather than
/// downloading in Dart then prompting `file_saver` - same reasoning as
/// `UploadService`/`ShareUploadService.kt` on the upload side: a real
/// Android Service survives the app being closed mid-download, and one
/// notification covers progress/cancel instead of blocking this screen.
Future<void> _downloadToDevice(ServerProvider provider) async {
final item = _currentItem;
setState(() => _isBusy = true);
try {
final tempPath = await _downloadToTemp(provider, item);
final ext = p.extension(item.name).replaceFirst('.', '');
final baseName = p.basenameWithoutExtension(item.name);
await FileSaver.instance.saveFile(
name: baseName,
filePath: tempPath,
ext: ext,
);
await DownloadService.startDownload(provider, [item]);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Saved ${item.name} to Downloads')),
SnackBar(
content: Text(
'Downloading ${item.name} - see the notification for progress',
),
),
);
}
} catch (e) {
@@ -212,8 +213,6 @@ class _FileViewerScreenState extends State<FileViewerScreen> {
context,
).showSnackBar(SnackBar(content: Text('Download failed: $e')));
}
} finally {
if (mounted) setState(() => _isBusy = false);
}
}