fix: 首页UI修复 - 底部溢出/胶囊/折叠/抽屉/screen适配

- 底部溢出:移除手动 viewInsets,让 Scaffold 默认处理键盘
- 智能体胶囊:新增常驻选择条,6个胶囊始终可见
- 任务卡片:双向切换,折叠后显示"点击展开"条
- 侧边栏:去掉固定高度,自适应内容
- K70适配:头像/字号/padding 全面紧凑化
This commit is contained in:
MingNian
2026-06-03 13:51:51 +08:00
parent 7b898f8660
commit 36ad334643
4 changed files with 189 additions and 97 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 KiB

View File

@@ -18,15 +18,11 @@ class _HomePageState extends ConsumerState<HomePage> {
final _textCtrl = TextEditingController(); final _textCtrl = TextEditingController();
final _scrollCtrl = ScrollController(); final _scrollCtrl = ScrollController();
bool _taskCardsExpanded = true; bool _taskCardsExpanded = true;
bool _showExpandButton = false;
@override void initState() { super.initState(); _scrollCtrl.addListener(_onScroll); } @override void initState() { super.initState(); _scrollCtrl.addListener(_onScroll); }
@override void dispose() { _textCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } @override void dispose() { _textCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); }
void _onScroll() { void _onScroll() {}
if (_scrollCtrl.offset > 50 && !_showExpandButton) setState(() => _showExpandButton = true);
else if (_scrollCtrl.offset <= 50 && _showExpandButton) setState(() => _showExpandButton = false);
}
void _sendMessage() { void _sendMessage() {
final text = _textCtrl.text.trim(); final text = _textCtrl.text.trim();
@@ -39,43 +35,53 @@ class _HomePageState extends ConsumerState<HomePage> {
final chatState = ref.watch(chatProvider); final chatState = ref.watch(chatProvider);
final auth = ref.watch(authProvider); final auth = ref.watch(authProvider);
final user = auth.user; final user = auth.user;
final bottomInset = MediaQuery.of(context).viewInsets.bottom;
final selectedAgent = ref.watch(selectedAgentProvider); final selectedAgent = ref.watch(selectedAgentProvider);
return Scaffold( return Scaffold(
drawer: const HealthDrawer(), drawer: const HealthDrawer(),
backgroundColor: const Color(0xFFF8F7FF), backgroundColor: const Color(0xFFF8F7FF),
body: SafeArea( body: SafeArea(
child: Stack(children: [ bottom: false,
Column(children: [ child: Column(children: [
// ── 顶部栏 ──
_buildHeader(user), _buildHeader(user),
if (_taskCardsExpanded) _buildTaskCards(),
// ── 今日任务(可折叠) ──
_buildTaskCardsArea(),
// ── 聊天区域(弹性填充剩余空间) ──
Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)), Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)),
// ── 智能体选择器(常驻显示) ──
_buildAgentBar(selectedAgent),
// ── 选中智能体的操作面板 ──
if (selectedAgent != null) _buildAgentPanel(context, selectedAgent), if (selectedAgent != null) _buildAgentPanel(context, selectedAgent),
// ── 输入框 ──
_buildInputBar(context), _buildInputBar(context),
SizedBox(height: bottomInset > 0 ? bottomInset : 0),
]),
_buildExpandButton(),
]), ]),
), ),
); );
} }
// ═════════════════════ 顶部栏 ═════════════════════
Widget _buildHeader(dynamic user) { Widget _buildHeader(dynamic user) {
return Container( return Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
child: Row(children: [ child: Row(children: [
Builder(builder: (ctx) => GestureDetector( Builder(builder: (ctx) => GestureDetector(
onTap: () => Scaffold.of(ctx).openDrawer(), 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), 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: 12), const SizedBox(width: 10),
Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ 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]))]), 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), const SizedBox(height: 2),
Text('${_getGreeting()}${user?.name ?? '张三'}', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))), Text('${_getGreeting()}${user?.name ?? '张三'}', style: const TextStyle(fontSize: 17, fontWeight: FontWeight.bold, color: Color(0xFF1A1A1A))),
])), ])),
Icon(Icons.notifications_none, size: 24, color: Colors.grey[600]), Icon(Icons.notifications_none, size: 22, color: Colors.grey[600]),
]), ]),
); );
} }
@@ -88,35 +94,56 @@ class _HomePageState extends ConsumerState<HomePage> {
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() { Widget _buildTaskCardsArea() {
final latestHealth = ref.watch(latestHealthProvider); final latestHealth = ref.watch(latestHealthProvider);
if (_taskCardsExpanded) {
return latestHealth.when( return latestHealth.when(
data: (data) => Container( data: (data) => _taskCardContent(data),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), loading: () => _taskCardContent({}),
padding: const EdgeInsets.all(16), error: (_, __) => _taskCardContent({}),
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), // 折叠状态:只显示一行可点击的标题栏
return GestureDetector(
onTap: () => setState(() => _taskCardsExpanded = true),
behavior: HitTestBehavior.opaque,
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
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: Row(children: [
Icon(Icons.assignment_turned_in_outlined, size: 18, color: const Color(0xFF635BFF)),
const SizedBox(width: 8),
const Text('今日任务', style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
const Spacer(),
Text('点击展开', style: TextStyle(fontSize: 12, color: const Color(0xFF635BFF))),
Icon(Icons.keyboard_arrow_right, size: 18, color: const Color(0xFF635BFF)),
]), ]),
), ),
loading: () => const SizedBox.shrink(), );
error: (_, __) => Container( }
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(16), Widget _taskCardContent(Map<String, dynamic> healthData) {
decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(10), blurRadius: 8, offset: const Offset(0, 2))]), 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: [ 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))))]), Row(children: [
const SizedBox(height: 12), ..._getTodayTasks({}), 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: [
), Text('收起', style: TextStyle(fontSize: 12, color: const Color(0xFF999999))),
Icon(Icons.keyboard_arrow_up, size: 18, color: const Color(0xFF999999)),
])),
]),
const SizedBox(height: 10),
..._getTodayTasks(healthData),
]),
); );
} }
@@ -140,14 +167,14 @@ class _HomePageState extends ConsumerState<HomePage> {
final colors = {'done': const Color(0xFF43A047), 'warning': const Color(0xFFFF9800), 'pending': const Color(0xFF9E9E9E)}; 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 icons = {'done': Icons.check_circle, 'warning': Icons.warning, 'pending': Icons.circle_outlined};
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 10),
child: GestureDetector( child: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: onTap, onTap: onTap,
child: Row(children: [ 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))), 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: 14, color: Color(0xFF333333)))), const SizedBox(width: 10), Expanded(child: Text(label, style: const TextStyle(fontSize: 13, color: Color(0xFF333333)))),
Icon(icons[status], size: 20, color: colors[status] ?? Colors.grey), Icon(icons[status], size: 18, color: colors[status] ?? Colors.grey),
]), ]),
), ),
); );
@@ -155,73 +182,139 @@ class _HomePageState extends ConsumerState<HomePage> {
void _handleMedicationCheck() async { void _handleMedicationCheck() async {
await ref.read(medicationServiceProvider).confirm(''); await ref.read(medicationServiceProvider).confirm('');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('已记录服药 ✅'), backgroundColor: Color(0xFF635BFF))); 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: 44,
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);
notifier.select(isActive ? null : agent);
// 切换智能体时清空聊天
if (!isActive) ref.read(chatProvider.notifier).setAgent(agent);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: isActive ? const Color(0xFF635BFF) : Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(color: isActive ? const Color(0xFF635BFF) : const Color(0xFFE0E0E0)),
),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Icon(icon, size: 14, color: isActive ? Colors.white : const Color(0xFF666666)),
const SizedBox(width: 4),
Text(label, style: TextStyle(fontSize: 12, fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, color: isActive ? Colors.white : const Color(0xFF666666))),
]),
),
);
},
),
);
}
// ═════════════════════ 智能体操作面板(选中后显示) ═════════════════════
Widget _buildAgentPanel(BuildContext context, ActiveAgent agent) { Widget _buildAgentPanel(BuildContext context, ActiveAgent agent) {
final titles = {ActiveAgent.consultation: 'AI 问诊', ActiveAgent.health: '记数据', ActiveAgent.diet: '拍饮食', ActiveAgent.medication: '药管家', ActiveAgent.report: '看报告', ActiveAgent.exercise: '运动计划'}; 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 分钟"'}; 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: [ return Container(
Text(titles[agent] ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
const SizedBox(height: 4), Text(tips[agent] ?? '', style: TextStyle(fontSize: 12, color: Colors.grey[500])), decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: const Color(0xFFEEEEEE)))),
const SizedBox(height: 12), Wrap(spacing: 8, runSpacing: 8, children: _getAgentButtons(agent)), child: Column(mainAxisSize: MainAxisSize.min, children: [
Row(children: [
Text(titles[agent] ?? '', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))),
const SizedBox(width: 6),
Expanded(child: Text(tips[agent] ?? '', style: TextStyle(fontSize: 11, color: Colors.grey[500]))),
GestureDetector(onTap: () => ref.read(selectedAgentProvider.notifier).select(null), child: Icon(Icons.close, size: 18, color: Colors.grey[400])),
]),
const SizedBox(height: 8),
SingleChildScrollView(scrollDirection: Axis.horizontal, child: Row(children: _getAgentButtons(agent))),
])); ]));
} }
List<Widget> _getAgentButtons(ActiveAgent agent) { List<Widget> _getAgentButtons(ActiveAgent agent) {
switch (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.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.diet: return [_agentBtn('拍照识别', Icons.camera_alt), _agentBtn('上传照片', Icons.photo_library)];
case ActiveAgent.medication: return [_agentBtn('用药管理', Icons.medication), _agentBtn('用药提醒', Icons.alarm)]; case ActiveAgent.medication: return [_agentBtn('用药管理', Icons.medication), _agentBtn('用药提醒', Icons.alarm)];
case ActiveAgent.consultation: return [_agentBtn('找医生', Icons.person_search)]; case ActiveAgent.consultation: return [_agentBtn('找医生', Icons.person_search)];
case ActiveAgent.exercise: return [_agentBtn('本周计划', Icons.calendar_view_week), _agentBtn('创建新计划', Icons.add_circle_outline)]; case ActiveAgent.exercise: return [_agentBtn('本周计划', Icons.calendar_view_week), _agentBtn('计划', Icons.add_circle_outline)];
default: return []; default: return [];
} }
} }
Widget _agentBtn(String label, IconData icon) { Widget _agentBtn(String label, IconData icon) {
return ElevatedButton.icon( return Padding(
padding: const EdgeInsets.only(right: 8),
child: ElevatedButton.icon(
onPressed: () => _onAgentAction(label), onPressed: () => _onAgentAction(label),
icon: Icon(icon, size: 16), icon: Icon(icon, size: 14),
label: Text(label, style: const TextStyle(fontSize: 13)), 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(18)), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14)), 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) { void _onAgentAction(String label) {
switch (label) { switch (label) {
case '拍照': case '上传照片': pushRoute(ref, 'dietCapture'); case '拍照识别': case '上传照片': pushRoute(ref, 'dietCapture');
case '手动录入血压': _textCtrl.text = '血压 '; case '录入血压': _textCtrl.text = '血压 ';
case '手动录入血糖': _textCtrl.text = '血糖 '; case '录入血糖': _textCtrl.text = '血糖 ';
case '手动录入心率': _textCtrl.text = '心率 '; case '录入心率': _textCtrl.text = '心率 ';
case '手动录入血氧': _textCtrl.text = '血氧 '; case '录入血氧': _textCtrl.text = '血氧 ';
case '手动录入体重': _textCtrl.text = '体重 '; case '录入体重': _textCtrl.text = '体重 ';
case '用药管理': pushRoute(ref, 'medications'); case '用药管理': pushRoute(ref, 'medications');
case '找医生': pushRoute(ref, 'doctors'); case '找医生': pushRoute(ref, 'doctors');
case '本周计划': case '创建新计划': pushRoute(ref, 'exercisePlan'); case '本周计划': case '计划': pushRoute(ref, 'exercisePlan');
} }
} }
Future<void> _pickImage(ImageSource source) async { Future<void> _pickImage(ImageSource source) async {
final picker = ImagePicker(); final picker = ImagePicker();
final picked = await picker.pickImage(source: source, imageQuality: 85); 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(() {}); } if (picked != null) { final token = await ref.read(apiClientProvider).accessToken; if (token == null) return; _textCtrl.text = '[图片已上传]'; if (mounted) setState(() {}); }
} }
void _showAttachmentPicker(BuildContext context) { void _showAttachmentPicker(BuildContext context) {
showModalBottomSheet(context: context, builder: (ctx) => SafeArea(child: Wrap(children: [ 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.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.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(() {}); }}), 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(() {}); }}),
]))); ])));
} }
Widget _buildInputBar(BuildContext context) { 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: [ return Container(
IconButton(icon: const Icon(Icons.attach_file, size: 24, color: Color(0xFF666666)), onPressed: () => _showAttachmentPicker(context)), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
Expanded(child: TextField(controller: _textCtrl, decoration: InputDecoration(hintText: '输入你想说的...', contentPadding: const EdgeInsets.symmetric(horizontal: 12), border: InputBorder.none), onSubmitted: (_) => _sendMessage())), decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: const Color(0xFFEEEEEE)))),
IconButton(icon: const Icon(Icons.send, size: 24, color: Color(0xFF635BFF)), onPressed: _sendMessage), child: Row(children: [
])); IconButton(icon: const Icon(Icons.attach_file, size: 22, color: Color(0xFF666666)), onPressed: () => _showAttachmentPicker(context)),
Expanded(child: TextField(controller: _textCtrl, decoration: InputDecoration(hintText: '输入你想说的...', contentPadding: const EdgeInsets.symmetric(horizontal: 10), border: InputBorder.none, isDense: true), onSubmitted: (_) => _sendMessage())),
IconButton(icon: const Icon(Icons.send, size: 22, color: Color(0xFF635BFF)), onPressed: _sendMessage),
]),
);
} }
} }

View File

@@ -75,24 +75,22 @@ class HealthDrawer extends ConsumerWidget {
TextButton(onPressed: () => ref.invalidate(conversationListProvider), child: const Text('刷新', style: TextStyle(fontSize: 12, color: Color(0xFF635BFF)))), TextButton(onPressed: () => ref.invalidate(conversationListProvider), child: const Text('刷新', style: TextStyle(fontSize: 12, color: Color(0xFF635BFF)))),
]), ]),
), ),
SizedBox( conversations.when(
height: 200,
child: conversations.when(
data: (items) { data: (items) {
if (items.isEmpty) { if (items.isEmpty) {
return const Center(child: Text('暂无历史对话', style: TextStyle(color: Color(0xFF999999), fontSize: 14))); return const Padding(padding: EdgeInsets.symmetric(vertical: 20), child: Center(child: Text('暂无历史对话', style: TextStyle(color: Color(0xFF999999), fontSize: 13))));
} }
return ListView.builder( return Padding(
shrinkWrap: true,
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
itemCount: items.length, child: Column(
itemBuilder: (ctx, i) => _ConversationItem(item: items[i], ref: ref), mainAxisSize: MainAxisSize.min,
children: items.map((item) => _ConversationItem(item: item, ref: ref)).toList(),
),
); );
}, },
loading: () => const Center(child: CircularProgressIndicator(strokeWidth: 2)), loading: () => const Center(child: CircularProgressIndicator(strokeWidth: 2)),
error: (_, __) => const Center(child: Text('加载失败', style: TextStyle(color: Color(0xFF999999), fontSize: 14))), error: (_, __) => const Center(child: Text('加载失败', style: TextStyle(color: Color(0xFF999999), fontSize: 14))),
), ),
),
const Divider(), const Divider(),
_DrawerItem(icon: Icons.logout, label: '退出登录', onTap: () async { _DrawerItem(icon: Icons.logout, label: '退出登录', onTap: () async {
@@ -149,30 +147,31 @@ class _ConversationItem extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFF8F7FF), color: const Color(0xFFF8F7FF),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(10),
), ),
child: ListTile( child: ListTile(
leading: Container( leading: Container(
width: 40, width: 36,
height: 40, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFEDEBFF), color: const Color(0xFFEDEBFF),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(8),
), ),
child: Icon(_getAgentIcon(item.agent), size: 18, color: const Color(0xFF635BFF)), child: Icon(_getAgentIcon(item.agent), size: 16, color: const Color(0xFF635BFF)),
), ),
title: Text(item.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), title: Text(item.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
subtitle: Text(item.lastMessage, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 12, color: Colors.grey[500])), subtitle: Text(item.lastMessage, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 11, color: Colors.grey[500])),
trailing: Column( trailing: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [ children: [
Text(_formatTime(item.updatedAt), style: const TextStyle(fontSize: 10, color: Color(0xFFCCCCCC))), Text(_formatTime(item.updatedAt), style: const TextStyle(fontSize: 9, color: Color(0xFFCCCCCC))),
const SizedBox(height: 4), const SizedBox(height: 2),
PopupMenuButton<int>( PopupMenuButton<int>(
icon: const Icon(Icons.more_vert, size: 16, color: Color(0xFFCCCCCC)), icon: const Icon(Icons.more_vert, size: 14, color: Color(0xFFCCCCCC)),
itemBuilder: (_) => [ itemBuilder: (_) => [
const PopupMenuItem(value: 1, child: Text('继续聊')), const PopupMenuItem(value: 1, child: Text('继续聊')),
const PopupMenuItem(value: 2, child: Text('删除')), const PopupMenuItem(value: 2, child: Text('删除')),