- 登录: QQ/163/126/189/搜狐/Gmail/Outlook/Yahoo/iCloud/企业邮/自定义, 自动识别+测试连接 - 收信: IMAP 多文件夹同步/未读/星标/搜索(本地+服务器) - 读信: HTML渲染/CID内嵌图/原文查看/附件下载 - 写信: 收件人/抄送/密送/附件/富文本/草稿/回复转发 - 存储: SQLite + 系统安全存储(DPAPI/Keychain/Keystore) - 测试: 单元测试 + Mock IMAP/SMTP 服务器集成测试(23项全过) - 可迁移: Windows/Android/iOS/macOS/Linux 一套代码
179 lines
4.7 KiB
Dart
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;
|
|
}
|
|
}
|
|
}
|