import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/app_colors.dart'; import '../../core/app_theme.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.sizeOf(context).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: AppColors.iconBg, child: Icon( Icons.forum_outlined, size: 15, color: AppColors.primary, ), ), 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: AppColors.divider)), ), child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: Container( constraints: const BoxConstraints(maxHeight: 120), decoration: BoxDecoration( color: AppColors.backgroundSoft, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.border), ), 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(color: AppColors.border), ), enabledBorder: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide(color: AppColors.border), ), focusedBorder: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(20)), borderSide: BorderSide(color: AppColors.primary), ), ), 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, ), ), ), ], ), ); } }