主要合入内容:
- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、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:
@@ -0,0 +1,70 @@
|
||||
/// 排序字段
|
||||
enum SortField {
|
||||
name('名称', 'name'),
|
||||
size('大小', 'size'),
|
||||
updatedAt('修改时间', 'updated_at'),
|
||||
createdAt('创建时间', 'created_at');
|
||||
|
||||
final String label;
|
||||
final String apiKey;
|
||||
const SortField(this.label, this.apiKey);
|
||||
}
|
||||
|
||||
/// 排序方向
|
||||
enum SortDirection {
|
||||
asc('升序', 'asc'),
|
||||
desc('降序', 'desc');
|
||||
|
||||
final String label;
|
||||
final String apiKey;
|
||||
const SortDirection(this.label, this.apiKey);
|
||||
}
|
||||
|
||||
/// 排序选项
|
||||
class SortOption {
|
||||
final SortField field;
|
||||
final SortDirection direction;
|
||||
|
||||
const SortOption(this.field, this.direction);
|
||||
|
||||
static const default_ = SortOption(SortField.name, SortDirection.asc);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SortOption &&
|
||||
field == other.field &&
|
||||
direction == other.direction;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(field, direction);
|
||||
|
||||
/// 持久化字符串,格式: "name_asc"
|
||||
String toKey() => '${field.apiKey}_${direction.apiKey}';
|
||||
|
||||
/// 从持久化字符串恢复
|
||||
static SortOption fromKey(String? key) {
|
||||
if (key == null) return default_;
|
||||
final parts = key.split('_');
|
||||
if (parts.length != 2) return default_;
|
||||
final field = SortField.values
|
||||
.where((f) => f.apiKey == parts[0])
|
||||
.firstOrNull;
|
||||
final dir = SortDirection.values
|
||||
.where((d) => d.apiKey == parts[1])
|
||||
.firstOrNull;
|
||||
if (field == null || dir == null) return default_;
|
||||
return SortOption(field, dir);
|
||||
}
|
||||
|
||||
/// 生成菜单项显示文本
|
||||
String get menuLabel {
|
||||
final dirLabel = switch (field) {
|
||||
SortField.name => direction == SortDirection.asc ? 'A→Z' : 'Z→A',
|
||||
SortField.size => direction == SortDirection.asc ? '小→大' : '大→小',
|
||||
SortField.updatedAt => direction == SortDirection.asc ? '旧→新' : '新→旧',
|
||||
SortField.createdAt => direction == SortDirection.asc ? '旧→新' : '新→旧',
|
||||
};
|
||||
return '${field.label} $dirLabel';
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,12 @@ class StorageKeys {
|
||||
// 同步相关
|
||||
static const String syncConfig = 'sync_config';
|
||||
static const String syncState = 'sync_state';
|
||||
static const String syncCumStats = 'sync_cum_stats';
|
||||
static const String clientId = 'client_id';
|
||||
|
||||
// 文件排序
|
||||
static const String fileSortOption = 'file_sort_option';
|
||||
|
||||
// 日志级别
|
||||
static const String logLevel = 'app_log_level';
|
||||
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
import 'dart:io';
|
||||
import 'package:external_path/external_path.dart';
|
||||
|
||||
class SyncDefaults {
|
||||
SyncDefaults._();
|
||||
|
||||
/// 默认同步目录
|
||||
/// 默认同步目录(同步版本,Android 返回空串,需用 getDefaultLocalRoot 异步获取)
|
||||
static String defaultLocalRoot() {
|
||||
if (Platform.isWindows) {
|
||||
return '${Platform.environment['USERPROFILE']}\\Documents\\Cloudreve4';
|
||||
} else if (Platform.isLinux) {
|
||||
final xdgDownload =
|
||||
Platform.environment['XDG_DOWNLOAD_DIR'] ?? ("${Platform.environment['HOME'] ?? ''}/Downloads");
|
||||
Platform.environment['XDG_DOWNLOAD_DIR'] ??
|
||||
("${Platform.environment['HOME'] ?? ''}/Downloads");
|
||||
return '$xdgDownload/Cloudreve4';
|
||||
} else if (Platform.isAndroid) {
|
||||
return ''; // Android 使用系统相册目录
|
||||
return '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/// 默认同步目录(异步版本,Android 通过 ExternalPath 获取公共目录)
|
||||
static Future<String> getDefaultLocalRoot() async {
|
||||
if (Platform.isAndroid) {
|
||||
return getDefaultAndroidLocalRoot();
|
||||
}
|
||||
return defaultLocalRoot();
|
||||
}
|
||||
|
||||
static const String defaultRemoteRoot = 'cloudreve://my';
|
||||
static const String defaultSyncMode = 'full';
|
||||
static const String defaultConflictStrategy = 'keep_both';
|
||||
@@ -24,4 +34,21 @@ class SyncDefaults {
|
||||
static const int defaultBandwidthLimitKbps = 0;
|
||||
static const int defaultMaxWorkers = 0; // 0 = CPU 核心数
|
||||
static const String defaultLogLevel = 'info';
|
||||
|
||||
// ===== Android 相册同步专用 =====
|
||||
static String get defaultAndroidLocalRoot {
|
||||
// runtime 获取 DCIM 公共目录,再拼接 /Camera
|
||||
// ExternalPath 无法 const,使用 getter 延迟求值
|
||||
throw UnsupportedError('Use getDefaultAndroidLocalRoot() instead');
|
||||
}
|
||||
|
||||
static Future<String> getDefaultAndroidLocalRoot() async {
|
||||
final dcim = await ExternalPath.getExternalStoragePublicDirectory(
|
||||
ExternalPath.DIRECTORY_DCIM,
|
||||
);
|
||||
return '$dcim/Camera';
|
||||
}
|
||||
|
||||
static const String defaultAndroidRemoteRoot = 'cloudreve://my/DCIM/Camera';
|
||||
static const String defaultAndroidSyncMode = 'album_upload';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user