Files
app/lib/presentation/providers/file_manager_provider.dart
T
2026-05-27 19:19:11 +08:00

411 lines
11 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import '../../data/models/file_model.dart';
import '../../services/file_service.dart';
import '../../services/thumbnail_service.dart';
import '../../core/utils/app_logger.dart';
import '../../core/utils/file_utils.dart';
/// 文件视图类型
enum FileViewType { list, grid, gallery }
/// 刷新结果
class RefreshResult {
final int added;
final int removed;
final int updated;
const RefreshResult({required this.added, required this.removed, required this.updated});
bool get isUnchanged => added == 0 && removed == 0 && updated == 0;
}
/// 文件管理Provider
class FileManagerProvider extends ChangeNotifier {
String _currentPath = '/';
List<FileModel> _files = [];
List<String> _selectedFiles = [];
FileViewType _viewType = FileViewType.list;
bool _isLoading = false;
bool _hasMore = true;
String? _errorMessage;
String? _contextHint;
String? _highlightPath;
Timer? _highlightTimer;
String get currentPath => _currentPath;
List<FileModel> get files => _files;
List<String> get selectedFiles => _selectedFiles;
FileViewType get viewType => _viewType;
bool get isLoading => _isLoading;
bool get hasMore => _hasMore;
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 {
if (refresh) {
_selectedFiles.clear();
}
setState(() {
_isLoading = true;
_errorMessage = null;
});
try {
final response = await FileService().listFiles(
uri: _currentPath,
pageSize: 50,
).timeout(timeout);
final List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
final pagination = response['pagination'] as Map<String, dynamic>? ?? {};
AppLogger.d("获取files列表: $filesData");
setState(() {
_files = filesData
.map((f) => FileModel.fromJson(f as Map<String, dynamic>))
.toList();
_hasMore = pagination['next_token'] != null;
_contextHint = response['context_hint'] as String?;
});
} on TimeoutException {
setState(() {
_errorMessage = '加载超时,请检查网络后重试';
_hasMore = false;
});
} catch (e) {
setState(() {
_errorMessage = e.toString();
_hasMore = false;
});
} finally {
setState(() {
_isLoading = false;
});
}
}
/// 进入文件夹
Future<void> enterFolder(String path) async {
_currentPath = path;
_selectedFiles.clear();
_highlightPath = null;
_highlightTimer?.cancel();
ThumbnailService.instance.clearAll();
await loadFiles();
}
/// 返回上级
Future<void> goBack() async {
if (_currentPath == '/' || _currentPath.isEmpty) return;
final parts = _currentPath.split('/');
if (parts.length > 1) {
parts.removeLast();
_currentPath = parts.join('/');
} else {
_currentPath = '/';
}
_selectedFiles.clear();
_highlightPath = null;
_highlightTimer?.cancel();
ThumbnailService.instance.clearAll();
notifyListeners();
await loadFiles();
}
/// 选择/取消选择文件
void toggleSelection(String path) {
if (_selectedFiles.contains(path)) {
_selectedFiles.remove(path);
} else {
_selectedFiles.add(path);
}
notifyListeners();
}
/// 选择所有
void selectAll() {
_selectedFiles = _files.map((f) => f.path).toList();
notifyListeners();
}
/// 清除选择
void clearSelection() {
_selectedFiles.clear();
notifyListeners();
}
/// 切换视图类型
void setViewType(FileViewType type) {
_viewType = type;
notifyListeners();
}
/// 设置错误信息
void setErrorMessage(String? message) {
_errorMessage = message;
notifyListeners();
}
/// 设置状态
void setState(VoidCallback fn) {
fn();
notifyListeners();
}
/// 删除选中的文件
Future<String?> deleteSelectedFiles() async {
if (_selectedFiles.isEmpty) return null;
try {
AppLogger.d("删除文件: ${_selectedFiles.join(', ')}");
await FileService().deleteFiles(uris: _selectedFiles);
setState(() {
_files.removeWhere((file) => _selectedFiles.contains(file.path));
});
clearSelection();
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 创建文件夹
Future<String?> createFolder(String name) async {
try {
String uri;
if (_currentPath == '/' || _currentPath.isEmpty) {
uri = '/$name';
} else {
uri = '$_currentPath/$name';
}
final response = await FileService().createFile(
uri: uri,
type: 'folder',
errOnConflict: true,
);
final newFolder = FileModel.fromJson(response);
setState(() {
_files.insert(0, newFolder);
});
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 删除单个文件(增量移除)
Future<String?> deleteFile(String path) async {
try {
await FileService().deleteFiles(uris: [path]);
setState(() {
_files.removeWhere((file) => file.path == path);
_selectedFiles.remove(path);
});
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 移动文件(增量更新)
Future<String?> moveFiles(List<String> uris, String destination, {bool copy = false}) async {
try {
await FileService().moveFiles(uris: uris, dst: destination, copy: copy);
clearSelection();
if (!copy) {
// 移动:文件离开当前目录,直接从列表移除
setState(() {
_files.removeWhere((file) => uris.contains(file.path));
});
} else {
// 复制:仅当目标是当前目录时需要刷新
final normalizedDst = FileUtils.toCloudreveUri(destination);
final normalizedCur = FileUtils.toCloudreveUri(_currentPath);
if (normalizedDst == normalizedCur) {
await loadFiles();
}
}
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 重命名文件(原地更新,不刷新列表)
Future<String?> renameFile(String path, String newName) async {
try {
final response = await FileService().renameFile(uri: path, newName: newName);
if (response.isEmpty) {
await loadFiles();
return null;
}
final updatedFile = FileModel.fromJson(response);
final index = _files.indexWhere((f) => f.path == path);
if (index != -1) {
setState(() {
_files[index] = updatedFile;
});
}
return null;
} catch (e) {
final error = e.toString();
setErrorMessage(error);
return error;
}
}
/// 通过 URI 获取文件信息并添加到列表(用于上传完成后)
Future<void> addFileByUri(String fileUri) async {
try {
final response = await FileService().getFileInfo(uri: fileUri);
final newFile = FileModel.fromJson(response);
final exists = _files.any((f) => f.id == newFile.id);
if (!exists) {
setState(() {
_files.insert(0, newFile);
});
}
} catch (e) {
AppLogger.d('获取上传文件信息失败: $e');
}
}
/// 高亮指定文件路径(3 秒后自动清除)
void setHighlightPath(String? path) {
_highlightTimer?.cancel();
_highlightPath = path;
notifyListeners();
if (path != null) {
_highlightTimer = Timer(const Duration(seconds: 3), () {
_highlightPath = null;
notifyListeners();
});
}
}
/// 导航到指定文件夹并高亮目标文件
Future<void> navigateAndHighlight(String folderPath, String filePath) async {
_currentPath = folderPath;
_selectedFiles.clear();
_highlightPath = null;
_highlightTimer?.cancel();
await loadFiles();
setHighlightPath(filePath);
}
/// 清空文件列表
void clearFiles() {
setState(() {
_files = [];
_selectedFiles = [];
_currentPath = '/';
_errorMessage = null;
});
}
/// 智能刷新 - 只更新差异部分
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 List<dynamic> filesData = response['files'] as List<dynamic>? ?? [];
final newFiles = filesData
.map((f) => FileModel.fromJson(f as Map<String, dynamic>))
.toList();
final currentMap = <String, FileModel>{};
for (final file in _files) {
currentMap[file.path] = file;
}
final newMap = <String, FileModel>{};
for (final file in newFiles) {
newMap[file.path] = file;
}
int added = 0;
int removed = 0;
int updated = 0;
final updatedFiles = <FileModel>[];
for (final file in newFiles) {
final existingFile = currentMap[file.path];
if (existingFile != null) {
if (existingFile.updatedAt != file.updatedAt ||
existingFile.size != file.size) {
updatedFiles.add(file);
updated++;
} else {
updatedFiles.add(existingFile);
}
} else {
updatedFiles.add(file);
added++;
}
}
for (final file in _files) {
if (!newMap.containsKey(file.path)) {
removed++;
}
}
setState(() {
_files = updatedFiles;
_hasMore = response['pagination']?['next_token'] != null;
_contextHint = response['context_hint'] as String?;
});
return RefreshResult(added: added, removed: removed, updated: updated);
} on TimeoutException {
setState(() {
_errorMessage = '加载超时,请检查网络后重试';
});
return const RefreshResult(added: 0, removed: 0, updated: 0);
} catch (e) {
setState(() {
_errorMessage = e.toString();
});
return const RefreshResult(added: 0, removed: 0, updated: 0);
} finally {
setState(() {
_isLoading = false;
});
}
}
@override
void dispose() {
_highlightTimer?.cancel();
super.dispose();
}
}