511 lines
22 KiB
Dart
511 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||
import 'package:image_picker/image_picker.dart';
|
||
import 'package:file_picker/file_picker.dart';
|
||
import '../../core/navigation_provider.dart';
|
||
import '../../providers/auth_provider.dart';
|
||
import '../../providers/chat_provider.dart';
|
||
import '../../providers/data_providers.dart';
|
||
import '../../widgets/health_drawer.dart';
|
||
import 'widgets/chat_messages_view.dart';
|
||
|
||
class HomePage extends ConsumerStatefulWidget {
|
||
const HomePage({super.key});
|
||
@override ConsumerState<HomePage> createState() => _HomePageState();
|
||
}
|
||
|
||
class _HomePageState extends ConsumerState<HomePage> {
|
||
final _textCtrl = TextEditingController();
|
||
final _scrollCtrl = ScrollController();
|
||
bool _taskCardsExpanded = true;
|
||
double _lastScrollOffset = 0;
|
||
DateTime? _lastCollapseTime;
|
||
bool _exerciseDone = false;
|
||
final Set<ActiveAgent> _welcomedAgents = {};
|
||
|
||
static final _mockFollowUps = [
|
||
{'hospital': '协和医院', 'department': '心内科', 'date': DateTime.now().add(const Duration(days: 2)), 'type': '复查'},
|
||
{'hospital': '人民医院', 'department': '心内科', 'date': DateTime.now().add(const Duration(days: 3)), 'type': '复诊'},
|
||
];
|
||
|
||
@override void initState() { super.initState(); _scrollCtrl.addListener(_onScroll); _textCtrl.addListener(_onTextChange); }
|
||
@override void dispose() { _textCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); }
|
||
|
||
void _onTextChange() {
|
||
if (_textCtrl.text.isNotEmpty && _taskCardsExpanded) {
|
||
setState(() => _taskCardsExpanded = false);
|
||
}
|
||
}
|
||
|
||
void _onScroll() {
|
||
if (!_scrollCtrl.hasClients) return;
|
||
final offset = _scrollCtrl.offset;
|
||
if (offset < _lastScrollOffset && _taskCardsExpanded) {
|
||
final delta = _lastScrollOffset - offset;
|
||
if (delta > 50) {
|
||
final now = DateTime.now();
|
||
if (_lastCollapseTime == null || now.difference(_lastCollapseTime!) > const Duration(seconds: 2)) {
|
||
_lastCollapseTime = now;
|
||
setState(() => _taskCardsExpanded = false);
|
||
}
|
||
}
|
||
}
|
||
_lastScrollOffset = offset;
|
||
}
|
||
|
||
void _sendMessage() {
|
||
final text = _textCtrl.text.trim();
|
||
if (text.isEmpty) return;
|
||
_textCtrl.clear();
|
||
setState(() => _taskCardsExpanded = false);
|
||
ref.read(chatProvider.notifier).sendMessage(text);
|
||
}
|
||
|
||
@override Widget build(BuildContext context) {
|
||
final chatState = ref.watch(chatProvider);
|
||
final auth = ref.watch(authProvider);
|
||
final user = auth.user;
|
||
final selectedAgent = ref.watch(selectedAgentProvider);
|
||
|
||
return Scaffold(
|
||
drawer: const HealthDrawer(),
|
||
backgroundColor: const Color(0xFFF8F7FF),
|
||
body: SafeArea(
|
||
child: Column(children: [
|
||
// ── 顶部栏 ──
|
||
_buildHeader(user),
|
||
|
||
// ── 今日任务(可折叠) ──
|
||
_buildTaskCardsArea(),
|
||
|
||
// ── 聊天区域(弹性填充剩余空间) ──
|
||
Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)),
|
||
|
||
// ── 底部合并区:智能体栏 + 操作面板 + 输入框(固定高度) ──
|
||
_buildBottomBar(context, selectedAgent),
|
||
]),
|
||
),
|
||
);
|
||
}
|
||
|
||
// ═════════════════════ 顶部栏 ═════════════════════
|
||
|
||
Widget _buildHeader(dynamic user) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||
child: Row(children: [
|
||
Builder(builder: (ctx) => GestureDetector(
|
||
onTap: () => Scaffold.of(ctx).openDrawer(),
|
||
child: CircleAvatar(radius: 20, backgroundColor: const Color(0xFFEDEBFF), backgroundImage: user?.avatarUrl != null ? NetworkImage(user!.avatarUrl!) : null, child: user?.avatarUrl == null ? const Icon(Icons.person, size: 24, color: Color(0xFF635BFF)) : null),
|
||
)),
|
||
const SizedBox(width: 10),
|
||
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||
Row(mainAxisSize: MainAxisSize.min, children: [Icon(Icons.smart_toy_outlined, size: 16, color: const Color(0xFF635BFF)), const SizedBox(width: 4), Text('AI 健康管家', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[600]))]),
|
||
const SizedBox(height: 2),
|
||
Text('${_getGreeting()},${user?.name ?? '张三'}!', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))),
|
||
])),
|
||
Icon(Icons.notifications_none, size: 22, color: Colors.grey[600]),
|
||
]),
|
||
);
|
||
}
|
||
|
||
String _getGreeting() {
|
||
final hour = DateTime.now().hour;
|
||
if (hour < 9) return '早上好';
|
||
if (hour < 12) return '上午好';
|
||
if (hour < 18) return '下午好';
|
||
return '晚上好';
|
||
}
|
||
|
||
// ═════════════════════ 今日任务(可折叠/展开) ═════════════════════
|
||
|
||
Widget _buildTaskCardsArea() {
|
||
final latestHealth = ref.watch(latestHealthProvider);
|
||
|
||
if (_taskCardsExpanded) {
|
||
return latestHealth.when(
|
||
data: (data) => _taskCardContent(data),
|
||
loading: () => _taskCardContent({}),
|
||
error: (_, __) => _taskCardContent({}),
|
||
);
|
||
}
|
||
|
||
// 折叠状态:只显示一行可点击的标题栏
|
||
return GestureDetector(
|
||
onTap: () => setState(() => _taskCardsExpanded = true),
|
||
behavior: HitTestBehavior.opaque,
|
||
child: Center(
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||
const Text('今日任务', style: TextStyle(fontSize: 13, color: Color(0xFF635BFF))),
|
||
const SizedBox(width: 4),
|
||
const Text('▾', style: TextStyle(fontSize: 12, color: Color(0xFF635BFF))),
|
||
]),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _taskCardContent(Map<String, dynamic> healthData) {
|
||
return Container(
|
||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||
padding: const EdgeInsets.all(14),
|
||
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(8), blurRadius: 6, offset: const Offset(0, 1))]),
|
||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||
Row(children: [
|
||
const Text('今日任务', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
|
||
const Spacer(),
|
||
GestureDetector(onTap: () => setState(() => _taskCardsExpanded = false), child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||
const Text('收起', style: TextStyle(fontSize: 12, color: Color(0xFF999999))),
|
||
const Text('∧', style: TextStyle(fontSize: 12, color: Color(0xFF999999))),
|
||
])),
|
||
]),
|
||
const SizedBox(height: 10),
|
||
..._getTodayTasks(healthData),
|
||
]),
|
||
);
|
||
}
|
||
|
||
List<Widget> _getTodayTasks(Map<String, dynamic> healthData) {
|
||
final now = DateTime.now();
|
||
final tasks = <Widget>[];
|
||
|
||
// 1. 数据摘要卡片(有今日指标数据时显示)
|
||
final summaryParts = <String>[];
|
||
final bp = healthData['BloodPressure'];
|
||
if (bp is Map) {
|
||
final s = bp['systolic'];
|
||
final d = bp['diastolic'];
|
||
if (s != null && d != null) summaryParts.add('血压 $s/$d');
|
||
}
|
||
final hr = healthData['HeartRate'];
|
||
if (hr is int) summaryParts.add('心率 $hr');
|
||
final bs = healthData['BloodSugar'];
|
||
if (bs is num) summaryParts.add('血糖 $bs');
|
||
final bo = healthData['BloodOxygen'];
|
||
if (bo is num) summaryParts.add('血氧 $bo');
|
||
final wt = healthData['Weight'];
|
||
if (wt is num) summaryParts.add('体重 $wt');
|
||
|
||
if (summaryParts.isNotEmpty) {
|
||
tasks.add(_summaryCard(summaryParts));
|
||
}
|
||
|
||
// 2. 用药提醒(从后端拉取真实数据)
|
||
final reminders = ref.watch(medicationReminderProvider);
|
||
reminders.whenData((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(
|
||
icon: Icons.medication_rounded,
|
||
label: '$name $dosage ($times)',
|
||
status: 'pending',
|
||
isOverdue: medOverdue,
|
||
onTap: () => _handleMedicationCheck,
|
||
));
|
||
}
|
||
});
|
||
|
||
// 无提醒时显示默认用药卡片
|
||
if (tasks.length <= (summaryParts.isNotEmpty ? 1 : 0)) {
|
||
tasks.add(_taskRow(
|
||
icon: Icons.medication_rounded,
|
||
label: '暂无用药提醒',
|
||
status: 'pending',
|
||
onTap: null,
|
||
));
|
||
}
|
||
|
||
// 3. 运动卡片(超时变红)
|
||
final exOverdue = now.hour >= 18 && !_exerciseDone;
|
||
tasks.add(_taskRow(
|
||
icon: Icons.directions_run,
|
||
label: '今日待运动:散步 30 分钟',
|
||
status: _exerciseDone ? 'done' : 'pending',
|
||
isOverdue: exOverdue,
|
||
onTap: _exerciseDone ? null : () => setState(() => _exerciseDone = true),
|
||
));
|
||
|
||
// 4. 测量卡片
|
||
tasks.add(_taskRow(
|
||
icon: Icons.today,
|
||
label: '今日测量:血压',
|
||
status: 'pending',
|
||
onTap: () => _textCtrl.text = '血压 ',
|
||
));
|
||
|
||
// 5. 异常指标
|
||
tasks.addAll(_buildAbnormalRows(healthData));
|
||
|
||
// 6. 复查提醒(未来3天内有复查安排时显示)
|
||
final upcomingFollowUps = _mockFollowUps.where((f) {
|
||
final date = f['date'] as DateTime;
|
||
return date.difference(now).inDays <= 3 && date.isAfter(now);
|
||
}).toList();
|
||
|
||
if (upcomingFollowUps.isNotEmpty) {
|
||
tasks.add(_followUpCard(upcomingFollowUps.first));
|
||
}
|
||
|
||
return tasks;
|
||
}
|
||
|
||
List<Widget> _buildAbnormalRows(Map<String, dynamic> healthData) {
|
||
final rows = <Widget>[];
|
||
final bp = healthData['BloodPressure'];
|
||
if (bp is Map) { final s = bp['systolic']; if (s is int && s >= 140) rows.add(_taskRow(icon: Icons.warning_amber_rounded, label: '血压 $s/${bp['diastolic'] ?? '--'} 偏高', status: 'warning', onTap: () => pushRoute(ref, 'trend', params: {'type': 'blood_pressure'}))); }
|
||
return rows;
|
||
}
|
||
|
||
Widget _summaryCard(List<String> parts) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 10),
|
||
child: GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
onTap: () => pushRoute(ref, 'trend'),
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF1F8E9),
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
child: Row(children: [
|
||
const Icon(Icons.check_circle, size: 18, color: Color(0xFF43A047)),
|
||
const SizedBox(width: 10),
|
||
Expanded(child: Text(
|
||
'今日已记录:${parts.join('、')}',
|
||
style: const TextStyle(fontSize: 13, color: Color(0xFF333333)),
|
||
)),
|
||
]),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _followUpCard(Map<String, dynamic> followUp) {
|
||
final date = followUp['date'] as DateTime;
|
||
final now = DateTime.now();
|
||
final diff = date.difference(now).inDays;
|
||
String dateLabel;
|
||
if (diff == 0) {
|
||
dateLabel = '今天';
|
||
} else if (diff == 1) {
|
||
dateLabel = '明天';
|
||
} else if (diff == 2) {
|
||
dateLabel = '后天';
|
||
} else {
|
||
dateLabel = '$diff天后';
|
||
}
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 10),
|
||
child: GestureDetector(
|
||
behavior: HitTestBehavior.opaque,
|
||
onTap: () => pushRoute(ref, 'followups'),
|
||
child: Row(children: [
|
||
Container(
|
||
width: 30, height: 30,
|
||
decoration: BoxDecoration(
|
||
color: const Color(0xFFF5F3FF),
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
child: const Icon(Icons.event_available, size: 15, color: Color(0xFF635BFF)),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Expanded(child: Text(
|
||
'📋 $dateLabel ${followUp['hospital']} ${followUp['department']} ${followUp['type']}',
|
||
style: const TextStyle(fontSize: 13, color: Color(0xFF333333)),
|
||
)),
|
||
]),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _taskRow({required IconData icon, required String label, required String status, VoidCallback? onTap, bool isOverdue = false}) {
|
||
final colors = {'done': const Color(0xFF43A047), 'warning': const Color(0xFFFF9800), 'pending': const Color(0xFF9E9E9E)};
|
||
final icons = {'done': Icons.check_circle, 'warning': Icons.warning, 'pending': Icons.circle_outlined};
|
||
final effectiveStatus = isOverdue ? 'warning' : status;
|
||
return Padding(
|
||
padding: const EdgeInsets.only(bottom: 10),
|
||
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(children: [
|
||
Container(width: 30, height: 30, decoration: BoxDecoration(color: const Color(0xFFF5F3FF), borderRadius: BorderRadius.circular(8)), child: Icon(icon, size: 15, color: const Color(0xFF635BFF))),
|
||
const SizedBox(width: 10), Expanded(child: Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF333333)))),
|
||
Icon(icons[effectiveStatus], size: 18, color: colors[effectiveStatus] ?? Colors.grey),
|
||
]),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _handleMedicationCheck() async {
|
||
await ref.read(medicationServiceProvider).confirm('');
|
||
if (!mounted) return;
|
||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已记录服药 ✅'), backgroundColor: Color(0xFF635BFF)));
|
||
}
|
||
|
||
// ═════════════════════ 智能体选择条(常驻) ═════════════════════
|
||
|
||
static final _agentDefs = [
|
||
(ActiveAgent.consultation, '问诊', Icons.chat_bubble_outline),
|
||
(ActiveAgent.health, '记数据', Icons.favorite_border),
|
||
(ActiveAgent.diet, '拍饮食', Icons.restaurant_outlined),
|
||
(ActiveAgent.medication, '药管家', Icons.medication_outlined),
|
||
(ActiveAgent.report, '看报告', Icons.description_outlined),
|
||
(ActiveAgent.exercise, '运动', Icons.directions_run_outlined),
|
||
];
|
||
|
||
Widget _buildAgentBar(ActiveAgent? selected) {
|
||
return Container(
|
||
height: 36,
|
||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||
child: ListView.separated(
|
||
scrollDirection: Axis.horizontal,
|
||
itemCount: _agentDefs.length,
|
||
separatorBuilder: (_, __) => const SizedBox(width: 6),
|
||
itemBuilder: (_, i) {
|
||
final (agent, label, icon) = _agentDefs[i];
|
||
final isActive = selected == agent;
|
||
return GestureDetector(
|
||
onTap: () {
|
||
final notifier = ref.read(selectedAgentProvider.notifier);
|
||
final newAgent = isActive ? null : agent;
|
||
notifier.select(newAgent);
|
||
if (newAgent != null) {
|
||
ref.read(chatProvider.notifier).setAgent(newAgent);
|
||
if (!_welcomedAgents.contains(newAgent)) {
|
||
_welcomedAgents.add(newAgent);
|
||
ref.read(chatProvider.notifier).insertAgentWelcome(newAgent);
|
||
}
|
||
}
|
||
},
|
||
child: Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||
decoration: BoxDecoration(
|
||
color: isActive ? const Color(0xFF635BFF) : Colors.white,
|
||
borderRadius: BorderRadius.circular(16),
|
||
border: Border.all(color: isActive ? const Color(0xFF635BFF) : const Color(0xFFE0E0E0)),
|
||
),
|
||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||
Icon(icon, size: 13, color: isActive ? Colors.white : const Color(0xFF666666)),
|
||
const SizedBox(width: 3),
|
||
Text(label, style: TextStyle(fontSize: 11, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, color: isActive ? Colors.white : const Color(0xFF666666))),
|
||
]),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
);
|
||
}
|
||
|
||
// ═════════════════════ 底部合并区:智能体栏 + 操作面板 + 输入框 ═════════════════════
|
||
|
||
Widget _buildBottomBar(BuildContext context, ActiveAgent? selectedAgent) {
|
||
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||
// 智能体胶囊栏(常驻,高度36)
|
||
_buildAgentBar(selectedAgent),
|
||
|
||
// 选中智能体的操作面板(紧凑版)
|
||
if (selectedAgent != null) _buildCompactAgentPanel(selectedAgent),
|
||
|
||
// 输入框(紧凑)
|
||
_buildCompactInputBar(context),
|
||
]);
|
||
}
|
||
|
||
Widget _buildCompactAgentPanel(ActiveAgent agent) {
|
||
final titles = {ActiveAgent.consultation: 'AI 问诊', ActiveAgent.health: '记数据', ActiveAgent.diet: '拍饮食', ActiveAgent.medication: '药管家', ActiveAgent.report: '看报告', ActiveAgent.exercise: '运动计划'};
|
||
final tips = {ActiveAgent.consultation: '或直接对我说你的症状', ActiveAgent.health: '或直接对我说:"血压 135/85"', ActiveAgent.diet: '或直接对我说:"中午吃了牛肉面"', ActiveAgent.medication: '或直接对我说:"医生让我吃阿托伐他汀 20mg"', ActiveAgent.report: '或直接上传报告图片', ActiveAgent.exercise: '或直接对我说:"每周一三五散步 30 分钟"'};
|
||
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||
decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: const Color(0xFFEEEEEE)))),
|
||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||
Row(children: [
|
||
Text(titles[agent] ?? '', style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
|
||
const SizedBox(width: 6),
|
||
Expanded(child: Text(tips[agent] ?? '', style: TextStyle(fontSize: 10, color: Colors.grey[500]))),
|
||
GestureDetector(onTap: () => ref.read(selectedAgentProvider.notifier).select(null), child: Icon(Icons.close, size: 16, color: Colors.grey[400])),
|
||
]),
|
||
const SizedBox(height: 4),
|
||
SingleChildScrollView(scrollDirection: Axis.horizontal, child: Row(children: _getAgentButtons(agent))),
|
||
]));
|
||
}
|
||
|
||
Widget _buildCompactInputBar(BuildContext context) {
|
||
return Container(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||
decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: const Color(0xFFEEEEEE)))),
|
||
child: Row(children: [
|
||
IconButton(icon: const Icon(Icons.attach_file, size: 20, color: Color(0xFF666666)), onPressed: () => _showAttachmentPicker(context), padding: const EdgeInsets.all(4)),
|
||
Expanded(child: TextField(controller: _textCtrl, decoration: InputDecoration(hintText: '输入你想说的...', contentPadding: const EdgeInsets.symmetric(horizontal: 8), border: InputBorder.none, isDense: true, hintStyle: const TextStyle(fontSize: 13)), onSubmitted: (_) => _sendMessage())),
|
||
IconButton(icon: const Icon(Icons.send, size: 20, color: Color(0xFF635BFF)), onPressed: _sendMessage, padding: const EdgeInsets.all(4)),
|
||
]),
|
||
);
|
||
}
|
||
|
||
List<Widget> _getAgentButtons(ActiveAgent agent) {
|
||
switch (agent) {
|
||
case ActiveAgent.health: return [_agentBtn('录入血压', Icons.favorite), _agentBtn('录入血糖', Icons.bloodtype), _agentBtn('录入心率', Icons.monitor_heart), _agentBtn('录入血氧', Icons.air), _agentBtn('录入体重', Icons.monitor_weight)];
|
||
case ActiveAgent.diet: return [_agentBtn('拍照识别', Icons.camera_alt), _agentBtn('上传照片', Icons.photo_library)];
|
||
case ActiveAgent.medication: return [_agentBtn('用药管理', Icons.medication), _agentBtn('用药提醒', Icons.alarm)];
|
||
case ActiveAgent.consultation: return [_agentBtn('找医生', Icons.person_search)];
|
||
case ActiveAgent.exercise: return [_agentBtn('本周计划', Icons.calendar_view_week), _agentBtn('新建计划', Icons.add_circle_outline)];
|
||
default: return [];
|
||
}
|
||
}
|
||
|
||
Widget _agentBtn(String label, IconData icon) {
|
||
return Padding(
|
||
padding: const EdgeInsets.only(right: 8),
|
||
child: ElevatedButton.icon(
|
||
onPressed: () => _onAgentAction(label),
|
||
icon: Icon(icon, size: 14),
|
||
label: Text(label, style: const TextStyle(fontSize: 12)),
|
||
style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFFF5F3FF), foregroundColor: const Color(0xFF635BFF), elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12)),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _onAgentAction(String label) {
|
||
switch (label) {
|
||
case '拍照识别': case '上传照片': pushRoute(ref, 'dietCapture');
|
||
case '录入血压': _textCtrl.text = '血压 ';
|
||
case '录入血糖': _textCtrl.text = '血糖 ';
|
||
case '录入心率': _textCtrl.text = '心率 ';
|
||
case '录入血氧': _textCtrl.text = '血氧 ';
|
||
case '录入体重': _textCtrl.text = '体重 ';
|
||
case '用药管理': pushRoute(ref, 'medications');
|
||
case '找医生': pushRoute(ref, 'doctors');
|
||
case '本周计划': case '新建计划': pushRoute(ref, 'exercisePlan');
|
||
}
|
||
}
|
||
|
||
Future<void> _pickImage(ImageSource source) async {
|
||
final picker = ImagePicker();
|
||
final picked = await picker.pickImage(source: source, imageQuality: 85);
|
||
if (picked != null) { final token = await ref.read(apiClientProvider).accessToken; if (token == null) return; _textCtrl.text = '[图片已上传]'; if (mounted) setState(() {}); }
|
||
}
|
||
|
||
void _showAttachmentPicker(BuildContext context) {
|
||
showModalBottomSheet(context: context, builder: (ctx) => SafeArea(child: Wrap(children: [
|
||
ListTile(leading: const Icon(Icons.camera_alt), title: const Text('拍照'), onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.camera); }),
|
||
ListTile(leading: const Icon(Icons.photo_library), title: const Text('从相册选'), onTap: () { Navigator.pop(ctx); _pickImage(ImageSource.gallery); }),
|
||
ListTile(leading: const Icon(Icons.attach_file), title: const Text('传文件'), onTap: () async { Navigator.pop(ctx); final result = await FilePicker.platform.pickFiles(); if (result != null && result.files.isNotEmpty) { _textCtrl.text = '[文件已选择] ${result.files.first.name}'; if (mounted) setState(() {}); }}),
|
||
])));
|
||
}
|
||
|
||
}
|