主要合入内容:

- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、Ctrl/Cmd+F搜索、Esc 关闭搜索、鼠标右滑返回上级。
- 移动端同步状态页、同步统计卡片、同步任务/累计统计事件、同步页布局更新。
- Android 相册同步默认路径改为 DCIM/Camera,下载路径改用 external_path。
- Rust sync-core 更新:Android 日志依赖、AlbumUpload/AlbumDownload、MirrorWcf 统计修复、Linux FUSE镜像挂载与读写同步支持。

- 依赖更新:external_path、fl_chart、pdfrx 升级,并修了 pdfrx 新版本的deprecated API。
- 新增文件包括:sort_options.dart、sync_page_android.dart、sync_stats_card.dart、platform/fuse.rs、sync_engine/fuse.rs。
This commit is contained in:
2026-06-04 07:11:43 +08:00
parent 5ee6ba1e28
commit b02daf1448
56 changed files with 7589 additions and 1200 deletions
+37 -25
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:background_downloader/background_downloader.dart' as bd;
import 'package:flutter/foundation.dart';
import 'package:external_path/external_path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import '../core/constants/storage_keys.dart';
@@ -27,7 +28,7 @@ class DownloadService {
// 回调处理器
static Function(String taskId, DownloadStatus status, int progress)?
_callbackHandler;
_callbackHandler;
final FileService _fileService = FileService();
final Map<String, StreamController<DownloadTaskModel>> _progressControllers =
@@ -36,7 +37,8 @@ class DownloadService {
/// 设置回调处理器
static void setCallbackHandler(
Function(String taskId, DownloadStatus status, int progress) handler) {
Function(String taskId, DownloadStatus status, int progress) handler,
) {
_callbackHandler = handler;
}
@@ -65,7 +67,10 @@ class DownloadService {
throw Exception('存储权限被拒绝');
}
final directory = Directory('/storage/emulated/0/Download');
final downloadPath = await ExternalPath.getExternalStoragePublicDirectory(
ExternalPath.DIRECTORY_DOWNLOAD,
);
final directory = Directory(downloadPath);
if (!await directory.exists()) {
await directory.create(recursive: true);
}
@@ -103,22 +108,23 @@ class DownloadService {
/// 读取 WiFi-only 下载设置
Future<bool> isWifiOnlyEnabled() async {
return await StorageService.instance
.getBool(StorageKeys.downloadWifiOnly) ??
return await StorageService.instance.getBool(
StorageKeys.downloadWifiOnly,
) ??
false;
}
/// 读取重试次数设置
Future<int> getRetries() async {
return await StorageService.instance
.getInt(StorageKeys.downloadRetries) ??
return await StorageService.instance.getInt(StorageKeys.downloadRetries) ??
3;
}
/// 初始化下载器
Future<void> initialize(
{Function(String taskId, DownloadStatus status, int progress)?
callbackHandler}) async {
Future<void> initialize({
Function(String taskId, DownloadStatus status, int progress)?
callbackHandler,
}) async {
if (callbackHandler != null) {
setCallbackHandler(callbackHandler);
AppLogger.d('回调处理器已更新');
@@ -133,9 +139,10 @@ class DownloadService {
if (Platform.isAndroid) {
bd.FileDownloader().configureNotification(
running: const bd.TaskNotification(
'正在下载', '文件: {filename} - {progress}'),
complete:
const bd.TaskNotification('下载完成', '文件: {filename} 已保存'),
'正在下载',
'文件: {filename} - {progress}',
),
complete: const bd.TaskNotification('下载完成', '文件: {filename} 已保存'),
error: const bd.TaskNotification('下载失败', '文件: {filename} 下载出错'),
paused: const bd.TaskNotification('已暂停', '文件: {filename} 已暂停'),
progressBar: true,
@@ -170,14 +177,15 @@ class DownloadService {
_internalIdToExternalTaskId[metaInternalId] = update.task.taskId;
_bdTasks[metaInternalId] = update.task as bd.DownloadTask;
AppLogger.d(
'从 metaData 恢复映射: bdTaskId=${update.task.taskId}, internalId=$metaInternalId');
'从 metaData 恢复映射: bdTaskId=${update.task.taskId}, internalId=$metaInternalId',
);
}
final resolvedInternalId =
_externalTaskIdToInternalId[update.task.taskId];
final resolvedInternalId = _externalTaskIdToInternalId[update.task.taskId];
if (resolvedInternalId == null) {
AppLogger.d(
'background_downloader 状态回调: 未找到内部任务ID, taskId=${update.task.taskId}');
'background_downloader 状态回调: 未找到内部任务ID, taskId=${update.task.taskId}',
);
return;
}
@@ -201,7 +209,8 @@ class DownloadService {
}
AppLogger.d(
'background_downloader 状态更新: taskId=${update.task.taskId}, internalId=$resolvedInternalId, status=$status');
'background_downloader 状态更新: taskId=${update.task.taskId}, internalId=$resolvedInternalId, status=$status',
);
final progress = status == DownloadStatus.completed ? 100 : 0;
_callbackHandler?.call(resolvedInternalId, status, progress);
@@ -261,7 +270,10 @@ class DownloadService {
/// 使用 background_downloader 开始下载
Future<String?> _startBdDownload(
DownloadTaskModel task, String url, Directory dir) async {
DownloadTaskModel task,
String url,
Directory dir,
) async {
final wifiOnly = await isWifiOnlyEnabled();
final retries = await getRetries();
final bdTask = bd.DownloadTask(
@@ -291,14 +303,14 @@ class DownloadService {
task.backgroundTaskId = bdTask.taskId;
AppLogger.d(
'background_downloader 任务已添加: taskId=${bdTask.taskId}, internalId=${task.id}, requiresWiFi=$wifiOnly, retries=$retries');
'background_downloader 任务已添加: taskId=${bdTask.taskId}, internalId=${task.id}, requiresWiFi=$wifiOnly, retries=$retries',
);
return bdTask.taskId;
}
/// 恢复下载(用于重启后恢复暂停的任务)
Future<String?> resumeDownloadAfterRestart(
DownloadTaskModel task) async {
Future<String?> resumeDownloadAfterRestart(DownloadTaskModel task) async {
try {
if (!_isInitialized) {
await initialize();
@@ -349,11 +361,11 @@ class DownloadService {
task.backgroundTaskId = bdTask.taskId;
// 如果有已下载的部分,尝试 resume;否则 enqueue
final partialFile =
File('${dir.path}/${task.fileName}.part');
final partialFile = File('${dir.path}/${task.fileName}.part');
if (task.downloadedBytes > 0 && await partialFile.exists()) {
AppLogger.d(
'断点续传: ${task.fileName}, 已下载 ${task.downloadedBytes} bytes');
'断点续传: ${task.fileName}, 已下载 ${task.downloadedBytes} bytes',
);
await bd.FileDownloader().resume(bdTask);
} else {
AppLogger.d('重新下载: ${task.fileName}');