十、导出支持自定义文件名
10.1 需求背景
根据项目规范:
“导出文件的文件名必须使用随机生成的八位小写字母,禁止使用明文或可预测的文件名,以增强安全性。”
但我更希望能够自定义文件名,同时默认填充随机名称作为建议。
10.2 实现方案
方案取舱:
- ✅ 默认用随机名填充输入框,可编辑
- ❌ 强制随机名,不允许修改(体验不好)
- ❌ 默认空白,使用时必须输入(增加操作步骤)
10.3 代码修改
1. file_utils.dart - 新增函数:
/// 生成随机名称(不含扩展名)
/// 返回 8 位小写字母
String generateRandomName() {
final random = Random.secure();
return String.fromCharCodes(
List.generate(8, (_) => random.nextInt(26) + 97), // a-z
);
}
/// 生成随机文件名(复用上面的函数)
String generateRandomFileName(String extension) {
final name = generateRandomName();
return '$name.$extension';
}2. _DownloadJob - 新增字段:
class _DownloadJob {
// ... 现有字段 ...
// 导出相关字段
final bool isExport;
final String? exportPassword;
final String? exportFileName; // 新增:自定义的导出文件名(不含扩展名)
}3. enqueueExport - 新增参数:
void enqueueExport(FileMetadata file, {required String password, String? fileName}) {
// ...
_downloadQueue.add(
_DownloadJob(
id: id,
file: file,
isExport: true,
exportPassword: password,
exportFileName: fileName, // 传递自定义文件名
),
);
}4. _doExportByPath - 使用自定义文件名:
Future<void> _doExportByPath(_DownloadJob job) async {
// 使用自定义的文件名,或生成随机文件名
final baseName = job.exportFileName ?? generateRandomName();
final fileName = '$baseName.e2e';
final savePath = '${downloadDir.path}/$fileName';
// ...
}5. 导出对话框 - 添加文件名输入框:
Future<void> _showExportE2EDialog() async {
final fileNameController = TextEditingController(text: generateRandomName());
// ...
content: SingleChildScrollView(
child: Column(
children: [
Text('设置密码用于保护文件 "${widget.file.name}"'),
TextField(
controller: fileNameController,
decoration: const InputDecoration(
labelText: '文件名',
border: OutlineInputBorder(),
suffixText: '.e2e', // 显示扩展名后缀
),
),
// ... 密码输入框 ...
],
),
),
}10.4 UI 优化:修复对话框溢出
问题:键盘弹出时,对话框内容溢出(BOTTOM OVERFLOWED BY 37 PIXELS)
原因:AlertDialog 的 content 使用 Column,内容固定高度
解决:用 SingleChildScrollView 包裹 Column,让内容可滚动
// 修复前
content: Column(
mainAxisSize: MainAxisSize.min,
children: [...],
),
// 修复后
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [...],
),
),十一、文件图标支持扩展
11.1 需求
支持 APK 文件和代码文件的专属图标,与现有图标保持同一系列(Material Design Icons)。
11.2 实现
新增扩展名定义:
/// 代码文件扩展名
const _codeExtensions = {
'dart', 'py', 'go', 'java', 'kt', 'swift', 'c', 'cpp', 'h', 'hpp',
'js', 'ts', 'jsx', 'tsx', 'vue', 'rs', 'rb', 'php', 'cs', 'scala',
'sh', 'bat', 'ps1', 'sql', 'r', 'lua', 'pl', 'groovy', 'gradle',
};新增判断函数:
/// 判断是否为代码文件
bool isCodeFile(String mimeType, String fileName) {
final ext = fileName.split('.').last.toLowerCase();
return _codeExtensions.contains(ext);
}
/// 判断是否为 APK 文件
bool isApkFile(String mimeType, String fileName) {
if (mimeType.contains('android.package') || mimeType.contains('vnd.android')) {
return true;
}
final ext = fileName.split('.').last.toLowerCase();
return ext == 'apk';
}图标映射:
IconData getFileIcon(String mimeType, {bool isDir = false, String? fileName}) {
// ...
// APK 文件
if (name.isNotEmpty && isApkFile(mime, name)) {
return MdiIcons.android;
}
// 代码文件
if (name.isNotEmpty && isCodeFile(mime, name)) {
return MdiIcons.fileCode;
}
// ... 其他类型 ...
}颜色映射:
Color getFileIconColor(String mimeType, {bool isDir = false, String? fileName}) {
// ...
// APK 文件 - 浅绿色(Android 主题色)
if (name.isNotEmpty && isApkFile(mime, name)) {
return Colors.lightGreen;
}
// 代码文件 - 青色
if (name.isNotEmpty && isCodeFile(mime, name)) {
return Colors.teal;
}
// ... 其他类型 ...
}11.3 图标对照表
| 文件类型 | 图标 | 颜色 | 扩展名示例 |
|---|---|---|---|
| 文件夹 | folder | amber | - |
| 图片 | fileImage | green | jpg, png, gif |
| 视频 | fileVideo | blue | mp4, mkv, mov |
| 音频 | fileMusic | purple | mp3, wav, flac |
filePdfBox | red | ||
| 压缩包 | folderZip | grey | zip, rar, 7z |
| 文本 | fileDocumentOutline | grey | txt, md, json |
| APK | android | lightGreen | apk |
| 代码 | fileCode | teal | dart, py, go, js |
| 其他 | file | grey | - |
十二、总结
12.1 本次重构的关键改动
- 架构简化:从“独立实现”回归“接口复用”
- 代码清理:删除旧版 API 和废弃函数
- 功能增强:导出支持自定义文件名
- UI 优化:修复对话框溢出问题
- 图标扩展:支持 APK 和代码文件
12.2 修改文件清单
| 文件 | 修改类型 | 说明 |
|---|---|---|
server.go | 删除+新增 | 删除旧 API,保留简化版 |
api_client.dart | 删除+新增 | 删除旧方法,保留简化版 |
app_state.dart | 重构 | 导入导出逻辑简化 |
files_page.dart | 修改 | 导出对话框添加文件名输入 |
file_utils.dart | 新增 | 随机名函数、APK/代码图标 |
e2e_progress.dart | 重写 | 简化为结果类型 |
12.3 经验教训
- 复用优先:新功能应尽量复用现有基础设施
- 简单优于复杂:不要为了“性能优化”而过度设计
- 使用直觉:第一反应通常是正确的
- 及时清理:重构后立即删除废弃代码
更新时间: 2025-12-22 00:38