import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import '../../core/app_colors.dart'; import '../../core/app_theme.dart'; import '../../core/navigation_provider.dart'; import '../../providers/data_providers.dart'; import '../../providers/consultation_provider.dart'; /// 问诊对话页 — 完整的 AI 分身聊天界面 class DoctorChatPage extends ConsumerStatefulWidget { final String id; const DoctorChatPage({super.key, required this.id}); @override ConsumerState createState() => _DoctorChatPageState(); } class _DoctorChatPageState extends ConsumerState { final _textCtrl = TextEditingController(); final _scrollCtrl = ScrollController(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(consultationChatProvider.notifier).init(widget.id); }); } @override void dispose() { ref.read(consultationChatProvider.notifier).stop(); _textCtrl.dispose(); _scrollCtrl.dispose(); super.dispose(); } void _send() { final text = _textCtrl.text.trim(); if (text.isEmpty) return; _textCtrl.clear(); ref.read(consultationChatProvider.notifier).sendMessage(text); } String _statusText(String status) { switch (status) { case 'AiTalking': return 'AI分身对话中'; case 'WaitingDoctor': return '已转接医生,请耐心等待'; case 'DoctorReplied': return '医生已回复'; case 'Closed': return '对话已结束'; default: return ''; } } @override Widget build(BuildContext context) { final state = ref.watch(consultationChatProvider); final canSend = state.status == 'AiTalking' && !state.isSending; return GradientScaffold( appBar: AppBar( backgroundColor: Colors.white, title: Column(children: [ Text(state.doctorName.isNotEmpty ? '${state.doctorName} · ${state.doctorDepartment}' : '问诊对话', style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w600, color: Color(0xFF1A1A1A))), if (state.doctorName.isNotEmpty) Text(_statusText(state.status), style: const TextStyle(fontSize: 15, color: AppColors.textSecondary)), ]), centerTitle: true, actions: [ Container( margin: const EdgeInsets.only(right: 12), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), child: Text('剩余${state.quotaRemaining}/${state.quotaTotal}次', style: const TextStyle(fontSize: 15, color: AppColors.textPrimary, fontWeight: FontWeight.w500)), ), ], ), body: state.isLoading ? const Center(child: CircularProgressIndicator(color: AppTheme.primaryLight)) : Column(children: [ Expanded(child: _buildMessageList(state)), _buildInputBar(canSend), ]), ); } Widget _buildMessageList(ConsultationChatState state) { if (state.messages.isEmpty) { return const Center( child: Text('暂无消息', style: TextStyle(color: Color(0xFFBBBBBB)))); } return ListView.builder( controller: _scrollCtrl, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), itemCount: state.messages.length, itemBuilder: (ctx, i) => _buildBubble(state, state.messages[i]), ); } Widget _buildBubble(ConsultationChatState state, ConsultationMsg msg) { final isUser = msg.senderType == 'User'; final isAi = msg.senderType == 'Ai'; return Align( alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.82), decoration: BoxDecoration( color: isUser ? AppTheme.primary : const Color(0xFFFEFEFF), borderRadius: BorderRadius.only( topLeft: Radius.circular(isUser ? 20 : 4), topRight: Radius.circular(isUser ? 4 : 20), bottomLeft: const Radius.circular(20), bottomRight: const Radius.circular(20), ), border: isUser ? null : Border.all(color: const Color(0xFFD8DCFD), width: 1.5), boxShadow: isUser ? [] : [BoxShadow(color: AppTheme.primary.withAlpha(12), blurRadius: 10, offset: const Offset(0, 3))], ), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!isUser && msg.senderName != null) ...[ Row(children: [ const CircleAvatar(radius: 10, backgroundColor: Color(0xFFF0F2FF), child: Icon(Icons.smart_toy, size: 15, color: AppTheme.primaryLight)), const SizedBox(width: 6), Text(msg.senderName!, style: const TextStyle(fontSize: 15, color: Color(0xFF9E9E9E))), ]), const SizedBox(height: 8), ], if (isUser) Text(msg.content, style: const TextStyle(fontSize: 19, color: Colors.white, height: 1.4)) else MarkdownBody( data: msg.content, selectable: true, styleSheet: MarkdownStyleSheet( p: const TextStyle(fontSize: 19, color: Color(0xFF1A1A1A), height: 1.5), ), ), if (isAi) ...[ const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: const Color(0xFFFFF8E1), borderRadius: BorderRadius.circular(6), ), child: Text('🏷️ 以上为AI分析,具体请咨询${state.doctorName}', style: const TextStyle(fontSize: 13, color: Color(0xFFF9A825))), ), ], ]), ), ); } Widget _buildInputBar(bool canSend) { return Container( padding: const EdgeInsets.fromLTRB(12, 8, 12, 10), decoration: BoxDecoration( color: Colors.white, border: const Border(top: BorderSide(color: Color(0xFFEEEEEE))), ), child: Row(crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: Container( constraints: const BoxConstraints(maxHeight: 120), decoration: BoxDecoration( color: AppColors.backgroundSoft, borderRadius: BorderRadius.circular(20), ), child: TextField( controller: _textCtrl, enabled: canSend, maxLines: null, style: const TextStyle(fontSize: 18), decoration: InputDecoration( hintText: canSend ? '描述您的症状...' : '对话已结束', hintStyle: const TextStyle(fontSize: 18, color: Color(0xFFBBBBBB)), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none), enabledBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none), focusedBorder: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide.none), ), onSubmitted: (_) => _send(), ), ), ), const SizedBox(width: 8), GestureDetector( onTap: canSend ? _send : null, child: Container( width: 40, height: 40, decoration: BoxDecoration( color: canSend ? AppColors.blueMeasure : const Color(0xFFCCCCCC), borderRadius: BorderRadius.circular(20), ), child: const Icon(Icons.send_rounded, size: 20, color: Colors.white), ), ), ]), ); } }