feat: 侧边栏重设计 - 彩色分区卡片+动画入场
This commit is contained in:
@@ -55,12 +55,6 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
// ─── 消息分发 ─────────────────────────────────────────────
|
||||
|
||||
Widget _buildMessageContent(BuildContext context, WidgetRef ref, ChatMessage msg, ChatState chatState) {
|
||||
final isUser = msg.isUser;
|
||||
|
||||
if (!isUser && chatState.isStreaming && msg.content.isEmpty) {
|
||||
return _buildThinkingBubble(context, chatState.thinkingText);
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case MessageType.agentWelcome:
|
||||
final storedAgent = _parseAgentFromName(msg.metadata?['agent'] as String?);
|
||||
@@ -78,6 +72,10 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
case MessageType.quickOptions:
|
||||
return _buildQuickOptionsCard(context, msg);
|
||||
default:
|
||||
// 只有当前正在流式回复的文本消息才显示"正在分析"
|
||||
if (!msg.isUser && chatState.isStreaming && msg.content.isEmpty) {
|
||||
return _buildThinkingBubble(context, chatState.thinkingText);
|
||||
}
|
||||
return _buildTextBubble(context, msg);
|
||||
}
|
||||
}
|
||||
@@ -90,6 +88,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
final info = _agentInfo(agent);
|
||||
final actions = agent.actions;
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final colors = _agentColors(agent);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
@@ -97,23 +96,23 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
constraints: BoxConstraints(maxWidth: screenWidth * 0.92),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFFFFFFF),
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
boxShadow: [
|
||||
BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(20), blurRadius: 16, offset: const Offset(0, 4)),
|
||||
BoxShadow(color: colors.gradient[0].withAlpha(30), blurRadius: 16, offset: const Offset(0, 4)),
|
||||
],
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// ── 紫色渐变头部 ──
|
||||
// ── 渐变色头部 ──
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 16, 20),
|
||||
decoration: const BoxDecoration(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [Color(0xFFA8B5FA), Color(0xFF8B9CF7), Color(0xFF5C70D6)],
|
||||
colors: colors.gradient,
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
),
|
||||
@@ -143,23 +142,11 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
color: Colors.white.withAlpha(25),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Text(info.$3, style: const TextStyle(fontSize: 12, color: Color(0xFFD8DCFD))),
|
||||
child: Text(info.$3, style: const TextStyle(fontSize: 12, color: Color(0xFFE8E6FF))),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
child: Container(
|
||||
width: 28,
|
||||
height: 28,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(20),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.close, size: 16, color: Color(0xFFD8DCFD)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -172,7 +159,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
runSpacing: 10,
|
||||
children: agent == ActiveAgent.consultation
|
||||
? _buildDoctorCards(screenWidth, ref)
|
||||
: actions.map((a) => _agentActionBtn(a, screenWidth, context, ref)).toList(),
|
||||
: actions.map((a) => _agentActionBtn(a, screenWidth, context, ref, colors)).toList(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -183,7 +170,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _agentActionBtn(_AgentAction a, double screenWidth, BuildContext context, WidgetRef ref) {
|
||||
Widget _agentActionBtn(_AgentAction a, double screenWidth, BuildContext context, WidgetRef ref, _AgentColors colors) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
if (a.action == 'createPlan') {
|
||||
@@ -199,14 +186,15 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
pushRoute(ref, a.route!);
|
||||
}
|
||||
}
|
||||
}, borderRadius: BorderRadius.circular(14),
|
||||
},
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
child: Container(
|
||||
width: ((screenWidth - 72) / (a.isWide ? 2 : 3)) - 10,
|
||||
padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFF7F5FF),
|
||||
color: colors.bg,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
border: Border.all(color: const Color(0xFFD8DCFD), width: 1),
|
||||
border: Border.all(color: colors.border, width: 1),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -215,10 +203,10 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
width: 38,
|
||||
height: 38,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFFEDEAFF),
|
||||
color: colors.iconBg,
|
||||
borderRadius: BorderRadius.circular(11),
|
||||
),
|
||||
child: Icon(a.icon, size: 20, color: const Color(0xFF8B9CF7)),
|
||||
child: Icon(a.icon, size: 20, color: colors.accent),
|
||||
),
|
||||
const SizedBox(height: 7),
|
||||
Text(a.label, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF333333))),
|
||||
@@ -644,16 +632,7 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
]),
|
||||
);
|
||||
}),
|
||||
] else ...[
|
||||
Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(12)),
|
||||
child: const Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
Icon(Icons.hourglass_empty, size: 18, color: Color(0xFF999999)),
|
||||
SizedBox(width: 8),
|
||||
Text('正在分析食物中...', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
|
||||
]),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
|
||||
// ── AI 建议 ──
|
||||
@@ -964,34 +943,48 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
boxShadow: isUser ? [] : [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (hasImage)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: imageUrl != null
|
||||
? Image.network(imageUrl, fit: BoxFit.cover, width: double.infinity, errorBuilder: (_, __, ___) => _buildLocalFallback(localPath))
|
||||
: localPath != null
|
||||
? Image.file(File(localPath), fit: BoxFit.cover, width: double.infinity)
|
||||
: null,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 文字内容
|
||||
if (isUser)
|
||||
Text(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1.4))
|
||||
else
|
||||
MarkdownBody(
|
||||
data: msg.content,
|
||||
selectable: true,
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A), height: 1.5),
|
||||
h1: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)),
|
||||
h2: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)),
|
||||
code: const TextStyle(fontSize: 14, backgroundColor: Color(0xFFF0F2FF)),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isUser)
|
||||
Text(msg.content, style: const TextStyle(fontSize: 16, color: Colors.white, height: 1.4))
|
||||
else
|
||||
MarkdownBody(
|
||||
data: msg.content,
|
||||
selectable: true,
|
||||
styleSheet: MarkdownStyleSheet(
|
||||
p: const TextStyle(fontSize: 16, color: Color(0xFF1A1A1A), height: 1.5),
|
||||
h1: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)),
|
||||
h2: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)),
|
||||
code: const TextStyle(fontSize: 14, backgroundColor: Color(0xFFF0F2FF)),
|
||||
|
||||
// 图片缩略图(在文字下方)
|
||||
if (hasImage)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () => _showFullImage(context, localPath ?? imageUrl),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 160, maxHeight: 120),
|
||||
child: localPath != null
|
||||
? Image.file(File(localPath), fit: BoxFit.cover)
|
||||
: imageUrl != null
|
||||
? Image.network(imageUrl, fit: BoxFit.cover, errorBuilder: (_, e, s) => Container(
|
||||
width: 80, height: 60,
|
||||
decoration: BoxDecoration(color: const Color(0xFFF0F0F0), borderRadius: BorderRadius.circular(8)),
|
||||
child: const Icon(Icons.image, size: 24, color: Color(0xFFBBBBBB)),
|
||||
))
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isUser && msg.content.isNotEmpty)
|
||||
|
||||
if (!isUser && msg.content.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Row(children: [
|
||||
@@ -1008,18 +1001,6 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLocalFallback(String? localPath) {
|
||||
if (localPath != null) {
|
||||
final file = File(localPath);
|
||||
return Image.file(file, fit: BoxFit.cover, width: double.infinity);
|
||||
}
|
||||
return Container(
|
||||
height: 100,
|
||||
color: const Color(0xFFEEEEEE),
|
||||
child: const Center(child: Icon(Icons.broken_image, size: 40, color: Color(0xFFBDBDBD))),
|
||||
);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// 公共组件:通用按钮
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@@ -1063,6 +1044,26 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
// 工具方法
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
|
||||
static void _showFullImage(BuildContext context, String? path) {
|
||||
if (path == null) return;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => Dialog(
|
||||
backgroundColor: Colors.black,
|
||||
insetPadding: const EdgeInsets.all(24),
|
||||
child: Stack(alignment: Alignment.center, children: [
|
||||
ClipRRect(borderRadius: BorderRadius.circular(12), child: InteractiveViewer(child: path.startsWith('http')
|
||||
? Image.network(path, fit: BoxFit.contain)
|
||||
: Image.file(File(path), fit: BoxFit.contain))),
|
||||
Positioned(top: 8, right: 8, child: GestureDetector(
|
||||
onTap: () => Navigator.pop(ctx),
|
||||
child: Container(padding: const EdgeInsets.all(6), decoration: BoxDecoration(color: Colors.white54, shape: BoxShape.circle), child: const Icon(Icons.close, size: 18)),
|
||||
)),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatTime(DateTime dt) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
@@ -1206,6 +1207,60 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
static _AgentColors _agentColors(ActiveAgent agent) {
|
||||
return switch (agent) {
|
||||
ActiveAgent.consultation => _AgentColors(
|
||||
gradient: [const Color(0xFFC5D5F8), const Color(0xFFA0B8F0), const Color(0xFF7B98E0)],
|
||||
bg: const Color(0xFFF0F4FF),
|
||||
border: const Color(0xFFD8E0FA),
|
||||
iconBg: const Color(0xFFE4ECFC),
|
||||
accent: const Color(0xFF7B98E0),
|
||||
),
|
||||
ActiveAgent.health => _AgentColors(
|
||||
gradient: [const Color(0xFFB8E6CF), const Color(0xFF8ED4AE), const Color(0xFF5FB88D)],
|
||||
bg: const Color(0xFFF0FAF4),
|
||||
border: const Color(0xFFD0ECD8),
|
||||
iconBg: const Color(0xFFE4F8EC),
|
||||
accent: const Color(0xFF5FB88D),
|
||||
),
|
||||
ActiveAgent.diet => _AgentColors(
|
||||
gradient: [const Color(0xFFFFD8B8), const Color(0xFFFFC896), const Color(0xFFF0A060)],
|
||||
bg: const Color(0xFFFFF6F0),
|
||||
border: const Color(0xFFFFE8D4),
|
||||
iconBg: const Color(0xFFFFEEDC),
|
||||
accent: const Color(0xFFF0A060),
|
||||
),
|
||||
ActiveAgent.medication => _AgentColors(
|
||||
gradient: [const Color(0xFFFFD4E0), const Color(0xFFFFB8CC), const Color(0xFFE898A8)],
|
||||
bg: const Color(0xFFFFF0F4),
|
||||
border: const Color(0xFFFFE0E8),
|
||||
iconBg: const Color(0xFFFFE8EE),
|
||||
accent: const Color(0xFFE898A8),
|
||||
),
|
||||
ActiveAgent.report => _AgentColors(
|
||||
gradient: [const Color(0xFFD8D0F0), const Color(0xFFC4B8EC), const Color(0xFFA898D8)],
|
||||
bg: const Color(0xFFF8F4FF),
|
||||
border: const Color(0xFFECE4F8),
|
||||
iconBg: const Color(0xFFF0E8FC),
|
||||
accent: const Color(0xFFA898D8),
|
||||
),
|
||||
ActiveAgent.exercise => _AgentColors(
|
||||
gradient: [const Color(0xFFB8E0E0), const Color(0xFF90D0D0), const Color(0xFF68B4B4)],
|
||||
bg: const Color(0xFFF0FAFA),
|
||||
border: const Color(0xFFD4ECEC),
|
||||
iconBg: const Color(0xFFE4F4F4),
|
||||
accent: const Color(0xFF68B4B4),
|
||||
),
|
||||
_ => _AgentColors(
|
||||
gradient: [const Color(0xFFC5D0F8), const Color(0xFFA0B0F0), const Color(0xFF7B90E0)],
|
||||
bg: const Color(0xFFF5F5FF),
|
||||
border: const Color(0xFFE0E0F8),
|
||||
iconBg: const Color(0xFFEDEDFC),
|
||||
accent: const Color(0xFF7B90E0),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
static (_AgentIcon, String, String) _agentInfo(ActiveAgent agent) {
|
||||
return switch (agent) {
|
||||
ActiveAgent.health => (Icons.favorite_border, '记数据', '录入血压、血糖、心率等日常指标'),
|
||||
@@ -1232,47 +1287,131 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
|
||||
Widget _buildTaskCardInChat(BuildContext context, WidgetRef ref) {
|
||||
final health = ref.watch(latestHealthProvider);
|
||||
final reminders = ref.watch(medicationReminderProvider);
|
||||
return health.when(
|
||||
data: (data) => _taskCardBubble(data),
|
||||
loading: () => _taskCardBubble({}),
|
||||
error: (_, __) => _taskCardBubble({}),
|
||||
data: (data) => _taskCardBubble(context, ref, data, reminders),
|
||||
loading: () => _taskCardBubble(context, ref, {}, const AsyncValue.loading()),
|
||||
error: (_, e) => _taskCardBubble(context, ref, {}, const AsyncValue.loading()),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _taskCardBubble(Map<String, dynamic> data) {
|
||||
final bp = data['BloodPressure'];
|
||||
final bpText = bp is Map ? '${bp['systolic'] ?? '--'}/${bp['diastolic'] ?? '--'}' : '--';
|
||||
final hr = data['HeartRate'];
|
||||
final hrText = hr is Map && hr['value'] != null ? '${hr['value']}' : '--';
|
||||
final gl = data['Glucose'];
|
||||
final glText = gl is Map && gl['value'] != null ? '${gl['value']}' : '--';
|
||||
final sp = data['SpO2'];
|
||||
final spText = sp is Map && sp['value'] != null ? '${sp['value']}' : '--';
|
||||
Widget _taskCardBubble(BuildContext context, WidgetRef ref, Map<String, dynamic> healthData, AsyncValue<List<Map<String, dynamic>>> reminders) {
|
||||
final now = DateTime.now();
|
||||
final tasks = <Widget>[];
|
||||
|
||||
// 1. 健康数据摘要行
|
||||
final bp = healthData['BloodPressure'];
|
||||
final bpText = bp is Map ? '${bp['systolic'] ?? '--'}/${bp['diastolic'] ?? '--'}' : null;
|
||||
final hr = healthData['HeartRate'];
|
||||
final hrText = hr is int ? '$hr' : null;
|
||||
final bs = healthData['BloodSugar'];
|
||||
final bsText = bs is num ? '$bs' : null;
|
||||
final bo = healthData['BloodOxygen'];
|
||||
final boText = bo is num ? '$bo' : null;
|
||||
final wt = healthData['Weight'];
|
||||
final wtText = wt is num ? '$wt' : null;
|
||||
|
||||
final allNull = bpText == null && hrText == null && bsText == null && boText == null && wtText == null;
|
||||
|
||||
if (!allNull) {
|
||||
tasks.add(_taskRow(context, Icons.check_circle, '今日已记录', trailing: [
|
||||
if (bpText != null) '血压 $bpText',
|
||||
if (hrText != null) '心率 $hrText',
|
||||
if (bsText != null) '血糖 $bsText',
|
||||
if (boText != null) '血氧 $boText',
|
||||
if (wtText != null) '体重 $wtText',
|
||||
].join(' · '), status: 'done', onTap: () => pushRoute(ref, 'trend')));
|
||||
}
|
||||
|
||||
// 2. 用药提醒
|
||||
reminders.whenOrNull(data: (meds) {
|
||||
for (final m in meds) {
|
||||
final name = m['name'] ?? '';
|
||||
final dosage = m['dosage'] ?? '';
|
||||
final times = (m['timeOfDay'] as List?)?.map((t) => t.toString().substring(0, 5)).join(', ') ?? '';
|
||||
final medOverdue = now.hour >= 8;
|
||||
tasks.add(_taskRow(
|
||||
context, Icons.medication_rounded, '$name $dosage ($times)',
|
||||
status: medOverdue ? 'overdue' : 'pending',
|
||||
onTap: () {},
|
||||
));
|
||||
}
|
||||
});
|
||||
if (tasks.length <= (allNull ? 0 : 1)) {
|
||||
tasks.add(_taskRow(context, Icons.medication_rounded, '暂无用药提醒', status: 'pending'));
|
||||
}
|
||||
|
||||
// 3. 运动
|
||||
final exOverdue = now.hour >= 18;
|
||||
tasks.add(_taskRow(
|
||||
context, Icons.directions_run, '今日待运动:散步 30 分钟',
|
||||
status: exOverdue ? 'overdue' : 'pending',
|
||||
onTap: () => pushRoute(ref, 'exercisePlan'),
|
||||
));
|
||||
|
||||
// 4. 测量
|
||||
tasks.add(_taskRow(
|
||||
context, Icons.today, '今日测量:血压',
|
||||
status: 'pending',
|
||||
onTap: () => pushRoute(ref, 'trend', params: {'type': 'blood_pressure'}),
|
||||
));
|
||||
|
||||
// 5. 异常指标
|
||||
if (bp is Map) {
|
||||
final s = bp['systolic'];
|
||||
if (s is int && s >= 140) {
|
||||
tasks.add(_taskRow(
|
||||
context, Icons.warning_amber_rounded, '血压 $s/${bp['diastolic'] ?? '--'} 偏高',
|
||||
status: 'warning',
|
||||
onTap: () => pushRoute(ref, 'trend', params: {'type': 'blood_pressure'}),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: [BoxShadow(color: const Color(0xFF8B9CF7).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))],
|
||||
),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
Row(children: [Icon(Icons.today, size: 18, color: const Color(0xFF8B9CF7)), const SizedBox(width: 8), const Text('今日任务', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A)))]),
|
||||
const SizedBox(height: 10),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
|
||||
_miniMetric('血压', bpText, Icons.favorite),
|
||||
_miniMetric('心率', hrText, Icons.monitor_heart),
|
||||
_miniMetric('血糖', glText, Icons.bloodtype),
|
||||
_miniMetric('血氧', spText, Icons.air),
|
||||
Row(children: [
|
||||
Icon(Icons.today, size: 18, color: const Color(0xFF8B9CF7)),
|
||||
const SizedBox(width: 8),
|
||||
const Text('今日任务', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
|
||||
]),
|
||||
const SizedBox(height: 10),
|
||||
...tasks,
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _miniMetric(String label, String value, IconData icon) {
|
||||
return Column(children: [
|
||||
Icon(icon, size: 20, color: const Color(0xFF8B9CF7)),
|
||||
const SizedBox(height: 4),
|
||||
Text(value, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))),
|
||||
Text(label, style: const TextStyle(fontSize: 10, color: Color(0xFF999999))),
|
||||
]);
|
||||
Widget _taskRow(BuildContext context, IconData icon, String label, {String status = 'pending', String? trailing, VoidCallback? onTap}) {
|
||||
final colors = {'done': const Color(0xFF43A047), 'warning': const Color(0xFFFF9800), 'pending': const Color(0xFF9E9E9E), 'overdue': const Color(0xFFE53935)};
|
||||
final icons = {'done': Icons.check_circle, 'warning': Icons.warning, 'pending': Icons.circle_outlined, 'overdue': Icons.error};
|
||||
final isOverdue = status == 'overdue';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: isOverdue ? const Color(0xFFFFEBEE) : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||
Container(width: 30, height: 30, decoration: BoxDecoration(color: const Color(0xFFF0F2FF), borderRadius: BorderRadius.circular(8)), child: Icon(icon, size: 15, color: const Color(0xFF8B9CF7))),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: Text(trailing ?? label, style: const TextStyle(fontSize: 13, color: Color(0xFF333333)))),
|
||||
Icon(icons[status] ?? Icons.circle_outlined, size: 18, color: colors[status] ?? Colors.grey),
|
||||
]),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1282,6 +1421,21 @@ class ChatMessagesView extends ConsumerWidget {
|
||||
|
||||
typedef _AgentIcon = IconData;
|
||||
|
||||
class _AgentColors {
|
||||
final List<Color> gradient;
|
||||
final Color bg;
|
||||
final Color border;
|
||||
final Color iconBg;
|
||||
final Color accent;
|
||||
const _AgentColors({
|
||||
required this.gradient,
|
||||
required this.bg,
|
||||
required this.border,
|
||||
required this.iconBg,
|
||||
required this.accent,
|
||||
});
|
||||
}
|
||||
|
||||
class _AgentAction {
|
||||
final String label;
|
||||
final IconData icon;
|
||||
|
||||
Reference in New Issue
Block a user