- 登录: QQ/163/126/189/搜狐/Gmail/Outlook/Yahoo/iCloud/企业邮/自定义, 自动识别+测试连接 - 收信: IMAP 多文件夹同步/未读/星标/搜索(本地+服务器) - 读信: HTML渲染/CID内嵌图/原文查看/附件下载 - 写信: 收件人/抄送/密送/附件/富文本/草稿/回复转发 - 存储: SQLite + 系统安全存储(DPAPI/Keychain/Keystore) - 测试: 单元测试 + Mock IMAP/SMTP 服务器集成测试(23项全过) - 可迁移: Windows/Android/iOS/macOS/Linux 一套代码
153 lines
5.3 KiB
Dart
153 lines
5.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
import '../../core/models/email.dart';
|
|
|
|
/// 邮件列表项
|
|
class EmailListTile extends StatelessWidget {
|
|
final Email email;
|
|
final bool selected;
|
|
final VoidCallback onTap;
|
|
final VoidCallback? onFlag;
|
|
|
|
const EmailListTile({
|
|
super.key,
|
|
required this.email,
|
|
required this.selected,
|
|
required this.onTap,
|
|
this.onFlag,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final scheme = Theme.of(context).colorScheme;
|
|
final bg = selected
|
|
? scheme.primaryContainer.withValues(alpha: 0.45)
|
|
: email.seen
|
|
? Colors.transparent
|
|
: scheme.surfaceContainerHighest.withValues(alpha: 0.35);
|
|
|
|
return Material(
|
|
color: bg,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 已读/未读圆点
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 6),
|
|
child: email.seen
|
|
? const SizedBox(width: 8)
|
|
: Container(
|
|
width: 8,
|
|
height: 8,
|
|
decoration: BoxDecoration(
|
|
color: scheme.primary,
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
email.fromLabel.isEmpty ? '(无发件人)' : email.fromLabel,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: email.seen ? FontWeight.w500 : FontWeight.w700,
|
|
color: email.seen ? scheme.onSurface : scheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
_formatDate(email.date),
|
|
style: TextStyle(fontSize: 12, color: scheme.onSurfaceVariant),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 3),
|
|
Text(
|
|
email.subject.isEmpty ? '(无主题)' : email.subject,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: 13.5,
|
|
fontWeight: email.seen ? FontWeight.w400 : FontWeight.w600,
|
|
color: email.seen ? scheme.onSurfaceVariant : scheme.onSurface,
|
|
),
|
|
),
|
|
if (email.snippet.isNotEmpty) ...[
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
email.snippet,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: scheme.onSurfaceVariant.withValues(alpha: 0.8),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
// 附件 / 星标
|
|
Column(
|
|
children: [
|
|
if (email.hasAttachments)
|
|
Icon(Icons.attach_file,
|
|
size: 16, color: scheme.onSurfaceVariant),
|
|
if (email.hasAttachments) const SizedBox(height: 2),
|
|
IconButton(
|
|
visualDensity: VisualDensity.compact,
|
|
constraints: const BoxConstraints(),
|
|
padding: EdgeInsets.zero,
|
|
iconSize: 19,
|
|
icon: Icon(
|
|
email.flagged ? Icons.star : Icons.star_border,
|
|
color: email.flagged ? Colors.amber : scheme.outlineVariant,
|
|
),
|
|
onPressed: onFlag,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _formatDate(DateTime? date) {
|
|
if (date == null) return '';
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
final d = DateTime(date.year, date.month, date.day);
|
|
final diff = today.difference(d).inDays;
|
|
if (diff == 0) {
|
|
return DateFormat('HH:mm').format(date);
|
|
}
|
|
if (diff == 1) return '昨天';
|
|
if (diff < 7) {
|
|
const week = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
|
|
return week[date.weekday - 1];
|
|
}
|
|
if (date.year == now.year) {
|
|
return DateFormat('M/d').format(date);
|
|
}
|
|
return DateFormat('yyyy/M/d').format(date);
|
|
}
|
|
}
|