December 26, 2025
4 min read
By devshan

Table of Contents

This is a list of all the sections in this post. Click on any of them to jump to that section.

背景

files_page.dart 作为应用的核心文件列表页面,代码量达到 3035 行,包含了大量的 UI 组件和业务逻辑。随着功能不断增加,代码变得难以维护。同时,预览页面(image、video、pdf、text)也需要文件操作功能,存在代码重复问题。

一、重构目标

  1. 减少 files_page.dart 代码量 - 将可复用组件拆分到独立文件
  2. 统一文件操作逻辑 - 创建 FileOperationService 服务层
  3. 消除代码警告 - 修复所有 info 级别的 use_build_context_synchronously 警告
  4. 修复功能 bug - 菜单点击后自动关闭

二、创建 file_tiles.dart

提取的组件

从 files_page.dart 中提取 _FileListTile_FileGridTile 组件:

// lib/ui/widgets/file_tiles.dart
 
/// 文件列表项组件
class FileListTile extends StatefulWidget {
  final FileMetadata file;
  final Map<String, Uint8List?> thumbnailCache;
  final ApiClient api;
  final bool scrollIdle;
  final bool isThumbFolder;
  final bool isOrphanFolder;
  // ... 回调函数
}
 
/// 文件网格项组件  
class FileGridTile extends StatefulWidget {
  final FileMetadata file;
  final Map<String, Uint8List?> thumbnailCache;
  final ApiClient api;
  final bool scrollIdle;
  // ... 回调函数
}
 
/// 获取或生成缩略图(异步)
Future<Uint8List?> fetchOrGenerateThumbnail(
  ApiClient api,
  String fileId, {
  String? fileName,
}) async { ... }

功能特点

  • 支持图片/视频缩略图延迟加载
  • VisibilityDetector 检测可见性,减少不必要加载
  • 统一的图标和颜色处理
  • E2E 加密导出对话框

三、创建 file_dialogs.dart

提取的对话框

// lib/ui/widgets/file_dialogs.dart
 
/// 创建文件夹对话框
class CreateFolderDialog extends StatefulWidget { ... }
 
/// 创建文本文件对话框
class CreateTextDialog extends StatefulWidget { ... }
 
/// 重命名对话框
class RenameDialog extends StatefulWidget { ... }
 
/// 文件详情对话框
class FileDetailsDialog extends StatelessWidget { ... }

使用方式

// 之前(私有类)
builder: (context) => _CreateFolderDialog(),
 
// 之后(公共类)
builder: (context) => const CreateFolderDialog(),

四、修复 file_action_sheet.dart

问题

点击文件操作菜单中的按钮后,菜单不会自动关闭。

原因

_QuickActionButton_ActionListTile 组件直接调用回调,没有先关闭菜单。

解决方案

// _QuickActionButton
onTap: () {
  Navigator.pop(context);  // 先关闭菜单
  onTap();                 // 再执行操作
},
 
// _ActionListTile
onTap: () {
  Navigator.pop(context);
  onTap();
},

五、修复 use_build_context_synchronously 警告

问题

10 个 info 级别警告:在异步操作后使用 BuildContext,可能导致在 Widget 已卸载后操作 UI。

解决方案

1. StatefulWidget 中使用 mounted 检查

// chat_page.dart, send_page.dart
Future<void> someAsyncMethod() async {
  await someOperation();
  if (!mounted) return;  // 检查是否仍然挂载
  showAppToast(context, 'Done');
}

2. 非 StatefulWidget 类中使用 isMounted 回调

// FileOperationService
class FileOperationService {
  final BuildContext context;
  final bool Function()? isMounted;
 
  FileOperationService(this.context, {this.isMounted});
 
  bool get _mounted => isMounted?.call() ?? true;
 
  void downloadFile(FileMetadata file) {
    _appState.enqueueDownloadFolder(file).then((count) {
      if (!_mounted) return;  // 异步后检查
      // ignore: use_build_context_synchronously
      showAppToast(context, '已开始下载...');
    });
  }
}
 
// 调用处
final service = FileOperationService(context, isMounted: () => mounted);

3. StatelessWidget 不需要检查

StatelessWidget 的生命周期由框架管理,不需要 mounted 检查。

六、代码量变化

files_page.dart

阶段行数减少
重构前3035-
删除对话框类2750-285 (-9.4%)
删除 Tile 类1970-780 (-25.7%)
总计1970-1065 (-35%)

新增文件

文件行数说明
file_tiles.dart795Tile 组件
file_dialogs.dart~150对话框组件
file_operation_service.dart323文件操作服务

七、模块化结构

lib/
├── core/
│ └── services/
│ └── file_operation_service.dart # 文件操作服务
├── ui/
│ ├── files_page.dart # 主页面 (1970 行)
│ ├── image_preview_page.dart # 使用 FileOperationService
│ ├── video_player_page.dart # 使用 FileOperationService
│ ├── pdf_preview_page.dart # 使用 FileOperationService
│ ├── text_editor_page.dart # 使用 FileOperationService
│ └── widgets/
│ ├── file_tiles.dart # Tile 组件
│ ├── file_dialogs.dart # 对话框组件
│ ├── file_action_sheet.dart # 操作菜单
│ └── file_picker_dialog.dart # 文件选择器

八、其他页面检查

对其他页面进行了代码检查:

页面行数评估
chat_page.dart1565✅ 已拆分到 chat_widgets.dart
settings_page.dart864✅ 结构合理
s3_config_page.dart805✅ 可接受
transfers_page.dart665✅ 可接受

结论:其他页面代码结构合理,无需进一步拆分。

总结

本次重构完成了以下工作:

  1. 代码瘦身 - files_page.dart 减少 35%(1065 行)
  2. 组件复用 - 创建 file_tiles.dart、file_dialogs.dart
  3. 服务层 - FileOperationService 统一文件操作逻辑
  4. 警告清零 - 修复所有 use_build_context_synchronously 警告
  5. Bug 修复 - 菜单点击后自动关闭

代码质量检查结果:

flutter analyze: No issues found!

经验教训

  1. 删除 StatefulWidget 时需同步删除 State 类引用 - 否则会导致编译错误
  2. PowerShell 处理大文件时注意编码 - 使用 -Encoding UTF8 避免中文乱码
  3. isMounted 回调模式 - 非 StatefulWidget 类中检查 mounted 状态的优雅方式