Files
mail-hub/lib/ui/widgets/folder_tree.dart
T
hz4th_coder 6c1225f588 MailHub v1.0.0: 跨平台邮箱客户端(纯 Dart IMAP/SMTP/MIME 协议栈)
- 登录: QQ/163/126/189/搜狐/Gmail/Outlook/Yahoo/iCloud/企业邮/自定义, 自动识别+测试连接
- 收信: IMAP 多文件夹同步/未读/星标/搜索(本地+服务器)
- 读信: HTML渲染/CID内嵌图/原文查看/附件下载
- 写信: 收件人/抄送/密送/附件/富文本/草稿/回复转发
- 存储: SQLite + 系统安全存储(DPAPI/Keychain/Keystore)
- 测试: 单元测试 + Mock IMAP/SMTP 服务器集成测试(23项全过)
- 可迁移: Windows/Android/iOS/macOS/Linux 一套代码
2026-08-18 01:10:11 +08:00

179 lines
4.7 KiB
Dart

import 'package:flutter/material.dart';
import '../../core/models/email.dart';
import '../app_state.dart';
/// 文件夹树(一个账户)
class FolderTree extends StatelessWidget {
final int accountId;
final List<MailFolder> folders;
final String? selected;
final ValueChanged<String> onSelect;
const FolderTree({
super.key,
required this.accountId,
required this.folders,
required this.selected,
required this.onSelect,
});
@override
Widget build(BuildContext context) {
// 排序: INBOX 最前, 然后按类型
final sorted = [...folders]..sort((a, b) {
final ra = _rank(a);
final rb = _rank(b);
if (ra != rb) return ra.compareTo(rb);
return a.name.toLowerCase().compareTo(b.name.toLowerCase());
});
return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 4),
itemCount: sorted.length,
itemBuilder: (context, i) {
final f = sorted[i];
final unread = AppState.instance.unreadByFolder[accountId]?[f.name] ?? 0;
final isSel = f.name == selected;
return _FolderTile(
folder: f,
unread: unread,
selected: isSel,
onTap: () => onSelect(f.name),
);
},
);
}
int _rank(MailFolder f) {
switch (f.kind) {
case 'inbox':
return 0;
case 'starred':
return 1;
case 'sent':
return 2;
case 'drafts':
return 3;
case 'junk':
return 4;
case 'trash':
return 5;
default:
return 6;
}
}
}
class _FolderTile extends StatelessWidget {
final MailFolder folder;
final int unread;
final bool selected;
final VoidCallback onTap;
const _FolderTile({
required this.folder,
required this.unread,
required this.selected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final icon = _iconFor(folder);
final label = _labelFor(folder);
return Material(
color: selected ? scheme.primaryContainer.withValues(alpha: 0.6) : Colors.transparent,
borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
child: Row(
children: [
Icon(icon, size: 17, color: selected ? scheme.primary : scheme.onSurfaceVariant),
const SizedBox(width: 8),
Expanded(
child: Text(
label,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13.5,
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
color: selected ? scheme.primary : scheme.onSurface,
),
),
),
if (unread > 0)
Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
decoration: BoxDecoration(
color: scheme.primary,
borderRadius: BorderRadius.circular(10),
),
child: Text(
unread > 999 ? '999+' : '$unread',
style: TextStyle(fontSize: 11, color: scheme.onPrimary),
),
),
],
),
),
),
);
}
IconData _iconFor(MailFolder f) {
switch (f.kind) {
case 'inbox':
return Icons.inbox_outlined;
case 'starred':
return Icons.star_outline;
case 'sent':
return Icons.send_outlined;
case 'drafts':
return Icons.edit_outlined;
case 'junk':
return Icons.report_gmailerrorred_outlined;
case 'trash':
return Icons.delete_outline;
default:
return Icons.folder_outlined;
}
}
String _labelFor(MailFolder f) {
// 取路径最后一段
final parts = f.name.split(f.delim);
final last = parts.last;
switch (last.toUpperCase()) {
case 'INBOX':
return '收件箱';
case 'SENT':
case 'SENT ITEMS':
case '已发送':
return '已发送';
case 'DRAFTS':
case '草稿':
case '草稿箱':
return '草稿箱';
case 'TRASH':
case 'DELETED ITEMS':
case '已删除':
return '已删除';
case 'SPAM':
case 'JUNK':
case '垃圾邮件':
return '垃圾邮件';
case 'STARRED':
case '星标':
return '星标邮件';
default:
return last;
}
}
}