- Backend: .NET 10 Minimal API + EF Core + PostgreSQL - Frontend: Flutter + Riverpod + GoRouter + Dio - AI: DeepSeek LLM + Qwen VLM (OpenAI-compatible) - Auth: SMS + JWT (access/refresh tokens) - Features: AI chat, health tracking, medication management, diet analysis, exercise plans, doctor consultations, report analysis
175 lines
6.2 KiB
Dart
175 lines
6.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../providers/chat_provider.dart';
|
|
import '../../widgets/agent_bar.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;
|
|
|
|
@override
|
|
void dispose() {
|
|
_textCtrl.dispose();
|
|
_scrollCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
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 selectedAgent = ref.watch(selectedAgentProvider);
|
|
|
|
return Scaffold(
|
|
drawer: const HealthDrawer(),
|
|
body: SafeArea(
|
|
child: Column(children: [
|
|
_buildHeader(context),
|
|
if (_taskCardsExpanded) _buildTaskCards(chatState),
|
|
Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)),
|
|
if (selectedAgent != null) _buildAgentPanel(context, selectedAgent),
|
|
const AgentBar(),
|
|
_buildInputBar(),
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
child: Row(children: [
|
|
Builder(builder: (ctx) => IconButton(
|
|
icon: const Icon(Icons.menu, size: 24),
|
|
onPressed: () => Scaffold.of(ctx).openDrawer(),
|
|
)),
|
|
const Spacer(),
|
|
Text('健康管家', style: Theme.of(context).textTheme.titleLarge),
|
|
const Spacer(),
|
|
const SizedBox(width: 48),
|
|
]),
|
|
);
|
|
}
|
|
|
|
Widget _buildTaskCards(ChatState chatState) {
|
|
return GestureDetector(
|
|
onVerticalDragUpdate: (d) { if (d.delta.dy < -10) setState(() => _taskCardsExpanded = false); },
|
|
child: Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFEDEBFF),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(children: [
|
|
Row(children: [
|
|
const Icon(Icons.wb_sunny, size: 18, color: Color(0xFF635BFF)),
|
|
const SizedBox(width: 8),
|
|
const Text('早上好!', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
|
|
const Spacer(),
|
|
GestureDetector(
|
|
onTap: () => setState(() => _taskCardsExpanded = false),
|
|
child: const Icon(Icons.keyboard_arrow_up, size: 20, color: Color(0xFF666666)),
|
|
),
|
|
]),
|
|
if (chatState.noticeText != null)
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 8),
|
|
child: Text(chatState.noticeText!, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))),
|
|
),
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAgentPanel(BuildContext context, ActiveAgent agent) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
|
|
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(20), blurRadius: 8, offset: const Offset(0, -2))],
|
|
),
|
|
child: Column(mainAxisSize: MainAxisSize.min, children: _getAgentButtons(agent)),
|
|
);
|
|
}
|
|
|
|
List<Widget> _getAgentButtons(ActiveAgent agent) {
|
|
final buttons = <Widget>[];
|
|
if (agent == ActiveAgent.health) {
|
|
buttons.add(_panelBtn('手动录入血压', Icons.favorite));
|
|
buttons.add(_panelBtn('手动录入血糖', Icons.bloodtype));
|
|
buttons.add(_panelBtn('手动录入心率', Icons.monitor_heart));
|
|
} else if (agent == ActiveAgent.diet) {
|
|
buttons.add(_panelBtn('拍照', Icons.camera_alt));
|
|
buttons.add(_panelBtn('上传照片', Icons.photo_library));
|
|
} else if (agent == ActiveAgent.medication) {
|
|
buttons.add(_panelBtn('用药管理', Icons.medication));
|
|
buttons.add(_panelBtn('用药提醒', Icons.alarm));
|
|
} else if (agent == ActiveAgent.consultation) {
|
|
buttons.add(_panelBtn('找医生', Icons.person_search));
|
|
} else if (agent == ActiveAgent.exercise) {
|
|
buttons.add(_panelBtn('查看本周计划', Icons.calendar_view_week));
|
|
buttons.add(_panelBtn('创建新计划', Icons.add_circle_outline));
|
|
}
|
|
return buttons;
|
|
}
|
|
|
|
Widget _panelBtn(String label, IconData icon) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 8),
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () {},
|
|
icon: Icon(icon, size: 20),
|
|
label: Text(label),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: const Color(0xFF635BFF),
|
|
side: const BorderSide(color: Color(0xFF635BFF)),
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildInputBar() {
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
border: Border(top: BorderSide(color: Colors.grey.shade200)),
|
|
),
|
|
child: Row(children: [
|
|
IconButton(icon: const Icon(Icons.attach_file, size: 24, color: Color(0xFF666666)), onPressed: () {}),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _textCtrl,
|
|
decoration: const InputDecoration(hintText: '输入你想说的...', contentPadding: EdgeInsets.symmetric(horizontal: 12), border: InputBorder.none),
|
|
onSubmitted: (_) => _sendMessage(),
|
|
),
|
|
),
|
|
IconButton(icon: const Icon(Icons.send, size: 24, color: Color(0xFF635BFF)), onPressed: _sendMessage),
|
|
]),
|
|
);
|
|
}
|
|
}
|