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 createState() => _HomePageState(); } class _HomePageState extends ConsumerState { final _textCtrl = TextEditingController(); final _scrollCtrl = ScrollController(); bool _taskCardsExpanded = true; bool _showExpandButton = false; @override void initState() { super.initState(); _scrollCtrl.addListener(_onScroll); } @override void dispose() { _textCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } void _onScroll() { if (_scrollCtrl.offset > 50 && !_showExpandButton) setState(() => _showExpandButton = true); else if (_scrollCtrl.offset <= 50 && _showExpandButton) setState(() => _showExpandButton = false); } void _sendMessage() { final text = _textCtrl.text.trim(); if (text.isEmpty) return; _textCtrl.clear(); 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 bottomInset = MediaQuery.of(context).viewInsets.bottom; final selectedAgent = ref.watch(selectedAgentProvider); return Scaffold( drawer: const HealthDrawer(), backgroundColor: const Color(0xFFF8F7FF), body: SafeArea( child: Stack(children: [ Column(children: [ _buildHeader(user), if (_taskCardsExpanded) _buildTaskCards(), Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)), if (selectedAgent != null) _buildAgentPanel(context, selectedAgent), _buildInputBar(context), SizedBox(height: bottomInset > 0 ? bottomInset : 0), ]), _buildExpandButton(), ]), ), ); } Widget _buildHeader(dynamic user) { return Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: Row(children: [ Builder(builder: (ctx) => GestureDetector( onTap: () => Scaffold.of(ctx).openDrawer(), child: CircleAvatar(radius: 22, backgroundColor: const Color(0xFFEDEBFF), backgroundImage: user?.avatarUrl != null ? NetworkImage(user!.avatarUrl!) : null, child: user?.avatarUrl == null ? const Icon(Icons.person, size: 26, color: Color(0xFF635BFF)) : null), )), const SizedBox(width: 12), Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(mainAxisSize: MainAxisSize.min, children: [Icon(Icons.smart_toy_outlined, size: 18, color: const Color(0xFF635BFF)), const SizedBox(width: 4), Text('AI 健康管家', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.grey[600]))]), const SizedBox(height: 2), Text('${_getGreeting()},${user?.name ?? '张三'}!', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), ])), Icon(Icons.notifications_none, size: 24, 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 _buildExpandButton() { if (!_showExpandButton || _taskCardsExpanded) return const SizedBox.shrink(); return Positioned(top: 60, right: 16, child: AnimatedOpacity(opacity: _showExpandButton ? 1.0 : 0.0, duration: const Duration(milliseconds: 300), child: FloatingActionButton(onPressed: () => setState(() => _taskCardsExpanded = true), mini: true, backgroundColor: const Color(0xFF635BFF), child: const Icon(Icons.keyboard_arrow_down, size: 20)))); } Widget _buildTaskCards() { final latestHealth = ref.watch(latestHealthProvider); return latestHealth.when( data: (data) => Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [Text('今日任务', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const Spacer(), GestureDetector(onTap: () => setState(() => _taskCardsExpanded = false), child: Text('全部展开', style: TextStyle(fontSize: 13, color: const Color(0xFF635BFF))))]), const SizedBox(height: 12), ..._getTodayTasks(data), ]), ), loading: () => const SizedBox.shrink(), error: (_, __) => Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [const Text('今日任务', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const Spacer(), GestureDetector(onTap: () => setState(() => _taskCardsExpanded = false), child: Text('全部展开', style: TextStyle(fontSize: 13, color: const Color(0xFF635BFF))))]), const SizedBox(height: 12), ..._getTodayTasks({}), ], ), ), ); } List _getTodayTasks(Map healthData) { return [ _taskRow(icon: Icons.medication_rounded, label: '计划 8:00 吃 阿司匹林 100mg', status: 'done', onTap: _handleMedicationCheck), _taskRow(icon: Icons.directions_run, label: '今日待运动:散步 30 分钟', status: 'pending', onTap: null), _taskRow(icon: Icons.today, label: '今日测量:血压', status: 'pending', onTap: () => _textCtrl.text = '血压 '), ..._buildAbnormalRows(healthData), ]; } List _buildAbnormalRows(Map healthData) { final rows = []; 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 _taskRow({required IconData icon, required String label, required String status, VoidCallback? onTap}) { 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}; return Padding( padding: const EdgeInsets.only(bottom: 12), child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, child: Row(children: [ Container(width: 32, height: 32, decoration: BoxDecoration(color: const Color(0xFFF5F3FF), borderRadius: BorderRadius.circular(8)), child: Icon(icon, size: 16, color: const Color(0xFF635BFF))), const SizedBox(width: 10), Expanded(child: Text(label, style: const TextStyle(fontSize: 14, color: Color(0xFF333333)))), Icon(icons[status], size: 20, color: colors[status] ?? Colors.grey), ]), ), ); } void _handleMedicationCheck() async { await ref.read(medicationServiceProvider).confirm(''); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已记录服药 ✅'), backgroundColor: Color(0xFF635BFF))); } Widget _buildAgentPanel(BuildContext context, 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 AnimatedContainer(duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, padding: const EdgeInsets.all(16), decoration: BoxDecoration(color: Colors.white, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(15), blurRadius: 12, offset: const Offset(0, -4))]), child: Column(mainAxisSize: MainAxisSize.min, children: [ Text(titles[agent] ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), const SizedBox(height: 4), Text(tips[agent] ?? '', style: TextStyle(fontSize: 12, color: Colors.grey[500])), const SizedBox(height: 12), Wrap(spacing: 8, runSpacing: 8, children: _getAgentButtons(agent)), ])); } List _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 ElevatedButton.icon( onPressed: () => _onAgentAction(label), icon: Icon(icon, size: 16), label: Text(label, style: const TextStyle(fontSize: 13)), style: ElevatedButton.styleFrom(backgroundColor: const Color(0xFFF5F3FF), foregroundColor: const Color(0xFF635BFF), elevation: 0, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14)), ); } 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 _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 = '[图片已上传]'; 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}'; setState(() {}); }}), ]))); } Widget _buildInputBar(BuildContext context) { return Container(padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: const Color(0xFFEEEEEE)))), child: Row(children: [ IconButton(icon: const Icon(Icons.attach_file, size: 24, color: Color(0xFF666666)), onPressed: () => _showAttachmentPicker(context)), Expanded(child: TextField(controller: _textCtrl, decoration: InputDecoration(hintText: '输入你想说的...', contentPadding: const EdgeInsets.symmetric(horizontal: 12), border: InputBorder.none), onSubmitted: (_) => _sendMessage())), IconButton(icon: const Icon(Icons.send, size: 24, color: Color(0xFF635BFF)), onPressed: _sendMessage), ])); } }