主要合入内容:
- 文件管理器分页加载、排序偏好、桌面表头排序、移动端排序菜单、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:
@@ -3,7 +3,10 @@ import 'dart:async';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../data/models/file_model.dart';
|
||||
import '../../services/file_service.dart';
|
||||
import '../../services/storage_service.dart';
|
||||
import '../../services/thumbnail_service.dart';
|
||||
import '../../core/constants/sort_options.dart';
|
||||
import '../../core/constants/storage_keys.dart';
|
||||
import '../../core/utils/app_logger.dart';
|
||||
import '../../core/utils/file_utils.dart';
|
||||
|
||||
@@ -15,7 +18,11 @@ class RefreshResult {
|
||||
final int added;
|
||||
final int removed;
|
||||
final int updated;
|
||||
const RefreshResult({required this.added, required this.removed, required this.updated});
|
||||
const RefreshResult({
|
||||
required this.added,
|
||||
required this.removed,
|
||||
required this.updated,
|
||||
});
|
||||
bool get isUnchanged => added == 0 && removed == 0 && updated == 0;
|
||||
}
|
||||
|
||||
@@ -25,8 +32,11 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
List<FileModel> _files = [];
|
||||
List<String> _selectedFiles = [];
|
||||
FileViewType _viewType = FileViewType.list;
|
||||
SortOption _sortOption = SortOption.default_;
|
||||
bool _isLoading = false;
|
||||
bool _isLoadingMore = false;
|
||||
bool _hasMore = true;
|
||||
String? _nextPageToken;
|
||||
String? _errorMessage;
|
||||
String? _contextHint;
|
||||
String? _highlightPath;
|
||||
@@ -36,15 +46,21 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
List<FileModel> get files => _files;
|
||||
List<String> get selectedFiles => _selectedFiles;
|
||||
FileViewType get viewType => _viewType;
|
||||
SortOption get sortOption => _sortOption;
|
||||
bool get isLoading => _isLoading;
|
||||
bool get isLoadingMore => _isLoadingMore;
|
||||
bool get hasMore => _hasMore;
|
||||
String? get nextPageToken => _nextPageToken;
|
||||
String? get errorMessage => _errorMessage;
|
||||
String? get contextHint => _contextHint;
|
||||
bool get hasSelection => _selectedFiles.isNotEmpty;
|
||||
String? get highlightPath => _highlightPath;
|
||||
|
||||
/// 加载文件列表
|
||||
Future<void> loadFiles({bool refresh = false, Duration timeout = const Duration(seconds: 5)}) async {
|
||||
Future<void> loadFiles({
|
||||
bool refresh = false,
|
||||
Duration timeout = const Duration(seconds: 5),
|
||||
}) async {
|
||||
if (refresh) {
|
||||
_selectedFiles.clear();
|
||||
}
|
||||
@@ -52,13 +68,18 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
_nextPageToken = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await FileService().listFiles(
|
||||
uri: _currentPath,
|
||||
pageSize: 50,
|
||||
).timeout(timeout);
|
||||
final response = await FileService()
|
||||
.listFiles(
|
||||
uri: _currentPath,
|
||||
pageSize: 50,
|
||||
orderBy: _sortOption.field.apiKey,
|
||||
orderDirection: _sortOption.direction.apiKey,
|
||||
)
|
||||
.timeout(timeout);
|
||||
|
||||
final List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
|
||||
final pagination = response['pagination'] as Map<String, dynamic>? ?? {};
|
||||
@@ -67,7 +88,8 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
_files = filesData
|
||||
.map((f) => FileModel.fromJson(f as Map<String, dynamic>))
|
||||
.toList();
|
||||
_hasMore = pagination['next_token'] != null;
|
||||
_nextPageToken = pagination['next_token'] as String?;
|
||||
_hasMore = _nextPageToken != null;
|
||||
_contextHint = response['context_hint'] as String?;
|
||||
});
|
||||
} on TimeoutException {
|
||||
@@ -87,12 +109,62 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// 加载更多文件(分页)
|
||||
Future<void> loadMoreFiles({
|
||||
Duration timeout = const Duration(seconds: 5),
|
||||
}) async {
|
||||
if (_isLoadingMore || _nextPageToken == null) return;
|
||||
|
||||
setState(() {
|
||||
_isLoadingMore = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await FileService()
|
||||
.listFiles(
|
||||
uri: _currentPath,
|
||||
pageSize: 50,
|
||||
orderBy: _sortOption.field.apiKey,
|
||||
orderDirection: _sortOption.direction.apiKey,
|
||||
nextPageToken: _nextPageToken,
|
||||
)
|
||||
.timeout(timeout);
|
||||
|
||||
final List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
|
||||
final pagination = response['pagination'] as Map<String, dynamic>? ?? {};
|
||||
final newFiles = filesData
|
||||
.map((f) => FileModel.fromJson(f as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
setState(() {
|
||||
final existingIds = _files.map((e) => e.id).toSet();
|
||||
_files.addAll(newFiles.where((f) => !existingIds.contains(f.id)));
|
||||
_nextPageToken = pagination['next_token'] as String?;
|
||||
_hasMore = _nextPageToken != null;
|
||||
});
|
||||
} on TimeoutException {
|
||||
setState(() {
|
||||
_errorMessage = '加载更多超时,请重试';
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_errorMessage = e.toString();
|
||||
});
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoadingMore = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 进入文件夹
|
||||
Future<void> enterFolder(String path) async {
|
||||
_currentPath = path;
|
||||
_selectedFiles.clear();
|
||||
_highlightPath = null;
|
||||
_highlightTimer?.cancel();
|
||||
_nextPageToken = null;
|
||||
ThumbnailService.instance.clearAll();
|
||||
await loadFiles();
|
||||
}
|
||||
@@ -111,6 +183,7 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
_selectedFiles.clear();
|
||||
_highlightPath = null;
|
||||
_highlightTimer?.cancel();
|
||||
_nextPageToken = null;
|
||||
ThumbnailService.instance.clearAll();
|
||||
notifyListeners();
|
||||
await loadFiles();
|
||||
@@ -144,6 +217,30 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 设置排序选项并重新加载
|
||||
Future<void> setSortOption(SortOption option) async {
|
||||
if (_sortOption == option) return;
|
||||
_sortOption = option;
|
||||
notifyListeners();
|
||||
await StorageService.instance.setString(
|
||||
StorageKeys.fileSortOption,
|
||||
option.toKey(),
|
||||
);
|
||||
await loadFiles(refresh: true);
|
||||
}
|
||||
|
||||
/// 从持久化恢复排序偏好
|
||||
Future<void> restoreSortOption() async {
|
||||
final key = await StorageService.instance.getString(
|
||||
StorageKeys.fileSortOption,
|
||||
);
|
||||
final option = SortOption.fromKey(key);
|
||||
if (option != _sortOption) {
|
||||
_sortOption = option;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置错误信息
|
||||
void setErrorMessage(String? message) {
|
||||
_errorMessage = message;
|
||||
@@ -224,7 +321,11 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// 移动文件(增量更新)
|
||||
Future<String?> moveFiles(List<String> uris, String destination, {bool copy = false}) async {
|
||||
Future<String?> moveFiles(
|
||||
List<String> uris,
|
||||
String destination, {
|
||||
bool copy = false,
|
||||
}) async {
|
||||
try {
|
||||
await FileService().moveFiles(uris: uris, dst: destination, copy: copy);
|
||||
clearSelection();
|
||||
@@ -253,7 +354,10 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
/// 重命名文件(原地更新,不刷新列表)
|
||||
Future<String?> renameFile(String path, String newName) async {
|
||||
try {
|
||||
final response = await FileService().renameFile(uri: path, newName: newName);
|
||||
final response = await FileService().renameFile(
|
||||
uri: path,
|
||||
newName: newName,
|
||||
);
|
||||
if (response.isEmpty) {
|
||||
await loadFiles();
|
||||
return null;
|
||||
@@ -319,21 +423,29 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
_selectedFiles = [];
|
||||
_currentPath = '/';
|
||||
_errorMessage = null;
|
||||
_nextPageToken = null;
|
||||
_hasMore = true;
|
||||
});
|
||||
}
|
||||
|
||||
/// 智能刷新 - 只更新差异部分
|
||||
Future<RefreshResult> refreshFiles({Duration timeout = const Duration(seconds: 5)}) async {
|
||||
/// 智能刷新 - 只更新差异部分(仅刷新首页)
|
||||
Future<RefreshResult> refreshFiles({
|
||||
Duration timeout = const Duration(seconds: 5),
|
||||
}) async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await FileService().listFiles(
|
||||
uri: _currentPath,
|
||||
pageSize: 50,
|
||||
).timeout(timeout);
|
||||
final response = await FileService()
|
||||
.listFiles(
|
||||
uri: _currentPath,
|
||||
pageSize: 50,
|
||||
orderBy: _sortOption.field.apiKey,
|
||||
orderDirection: _sortOption.direction.apiKey,
|
||||
)
|
||||
.timeout(timeout);
|
||||
|
||||
final List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
|
||||
final newFiles = filesData
|
||||
@@ -378,9 +490,11 @@ class FileManagerProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
final pagination = response['pagination'] as Map<String, dynamic>?;
|
||||
setState(() {
|
||||
_files = updatedFiles;
|
||||
_hasMore = response['pagination']?['next_token'] != null;
|
||||
_nextPageToken = pagination?['next_token'] as String?;
|
||||
_hasMore = _nextPageToken != null;
|
||||
_contextHint = response['context_hint'] as String?;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user