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 _files = []; List _selectedFiles = []; FileViewType _viewType = FileViewType.list; bool _isLoading = false; bool _hasMore = true; String? _errorMessage; String? _contextHint; String? _highlightPath; Timer? _highlightTimer; String get currentPath => _currentPath; List get files => _files; List 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 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 filesData = response['files'] as List? ?? []; final pagination = response['pagination'] as Map? ?? {}; AppLogger.d("获取files列表: $filesData"); setState(() { _files = filesData .map((f) => FileModel.fromJson(f as Map)) .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 enterFolder(String path) async { _currentPath = path; _selectedFiles.clear(); _highlightPath = null; _highlightTimer?.cancel(); ThumbnailService.instance.clearAll(); await loadFiles(); } /// 返回上级 Future 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 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 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 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 moveFiles(List uris, String destination, {bool copy = false}) async { try { await FileService().moveFiles(uris: uris, dst: destination); 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 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 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 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 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 filesData = response['files'] as List? ?? []; final newFiles = filesData .map((f) => FileModel.fromJson(f as Map)) .toList(); final currentMap = {}; for (final file in _files) { currentMap[file.path] = file; } final newMap = {}; for (final file in newFiles) { newMap[file.path] = file; } int added = 0; int removed = 0; int updated = 0; final updatedFiles = []; 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(); } }