import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/widget/live/svga_player_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../controller/message/call_controller.dart'; import '../../controller/message/chat_controller.dart'; import '../../controller/message/voice_player_manager.dart'; import '../../controller/mine/rose_controller.dart'; import '../../generated/assets.dart'; import '../../model/home/marriage_data.dart'; import '../../model/rtc/chat_audio_product_model.dart'; import '../../../widget/message/chat_input_bar.dart'; import '../../../widget/message/message_item.dart'; import '../../../widget/message/chat_gift_popup.dart'; import '../../../widget/message/call_type_selection_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'chat_settings_page.dart'; import 'video_call_page.dart'; import '../home/user_information_page.dart'; import '../../../widget/live/live_recharge_popup.dart'; class ChatPage extends StatefulWidget { final String userId; // 对应 MarriageData.userId final MarriageData? userData; // 可选:从外部传入的用户数据模型 const ChatPage({ required this.userId, this.userData, super.key, }); @override State createState() => _ChatPageState(); } class _ChatPageState extends State { /// 根据出生年份计算年龄 String _calculateAgeFromBirthYear(String birthYear) { if (birthYear.isEmpty) { return '0'; } try { final year = int.tryParse(birthYear); if (year == null) { return '0'; } final currentYear = DateTime.now().year; final age = currentYear - year; return age > 0 ? age.toString() : '0'; } catch (e) { return '0'; } } final ScrollController _scrollController = ScrollController(); bool _isLoadingMore = false; late ChatController _controller; // 礼物弹窗相关 final activeGift = ValueNotifier(null); final giftNum = ValueNotifier(1); // ChatInputBar 的 GlobalKey,用于关闭底部面板 final GlobalKey> _chatInputBarKey = GlobalKey>(); @override void initState() { super.initState(); // 初始化 controller,直接传入用户信息参数 // 如果已存在相同 userId 的 controller,先删除再创建新的,确保参数正确 final tag = 'chat_${widget.userId}'; if (Get.isRegistered(tag: tag)) { Get.delete(tag: tag); } _controller = Get.put(ChatController(userId: widget.userId, userData: widget.userData), tag: tag); // 监听滚动,当滚动到顶部时加载更多消息 _scrollController.addListener(() { if (_scrollController.hasClients && _scrollController.position.pixels <= _scrollController.position.minScrollExtent + 100 && !_isLoadingMore && _controller.messages.isNotEmpty && _controller.hasMoreMessages()) { _loadMoreMessages(); } }); // 监听消息列表变化,自动滚动到底部(跳过用户信息卡片) _controller.messages.listen((_) { WidgetsBinding.instance.addPostFrameCallback((_) { if (_scrollController.hasClients && _controller.messages.isNotEmpty) { // 滚动到消息列表的底部(跳过顶部的用户信息卡片) _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: Duration(milliseconds: 300), curve: Curves.easeOut, ); } }); }); // 页面可见后,延迟一小段时间标记所有消息为已读(确保页面已完全渲染) WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(Duration(milliseconds: 300), () { _controller.markAllMessagesAsRead(); }); }); } @override void dispose() { _scrollController.dispose(); activeGift.dispose(); giftNum.dispose(); super.dispose(); } // 显示礼物弹窗 void _showGiftPopup() { // 隐藏键盘 FocusScope.of(context).unfocus(); final giftProducts = _controller.giftProducts.toList(); if (giftProducts.isEmpty) { SmartDialog.showToast('礼物列表加载中,请稍候...'); return; } // 刷新玫瑰余额(参考 LiveRoomPage 的实现) _refreshRoseBalance(); _controller.setDialogDismiss(true); SmartDialog.show( builder: (context) { return ChatGiftPopup( activeGift: activeGift, giftNum: giftNum, giftList: giftProducts, changeActive: (index) { activeGift.value = index; }, onSendGift: (gift, quantity) async { await _controller.sendGift(gift: gift, quantity: quantity); }, ); }, alignment: Alignment.bottomCenter, animationType: SmartAnimationType.centerFade_otherSlide, maskColor: TDTheme.of(context).fontGyColor2, onDismiss: () { _controller.setDialogDismiss(false); } ); } // 刷新玫瑰余额(参考 LiveRoomPage 的实现) Future _refreshRoseBalance() async { try { // 确保 RoseController 已注册,如果未注册则注册它 final roseController = Get.isRegistered() ? Get.find() : Get.put(RoseController()); // 刷新玫瑰余额 await roseController.getRoseNum(); print('✅ 刷新玫瑰余额成功: ${roseController.roseNum.value}'); } catch (e) { print('❌ 刷新玫瑰余额失败: $e'); } } // 显示通话类型选择弹框 void _showCallTypeSelectionDialog(ChatController controller, {List? products}) { // 隐藏键盘 if(_controller.isDialogShowing.value){ return; } FocusScope.of(context).unfocus(); _controller.setDialogDismiss(true); SmartDialog.show( builder: (context) { return CallTypeSelectionDialog( products: products, onVoiceCall: () async { // 发起语音通话并跳转到视频通话页面 final success = await CallController.instance.initiateCall( targetUserId: widget.userId, callType: CallType.voice, chatController: controller, ); // 只有发起通话成功时才跳转到视频通话页面 if (success) { Get.to(() => VideoCallPage( targetUserId: widget.userId, userData: widget.userData ?? controller.userData, isInitiator: true, )); } }, onVideoCall: () async { // 发起视频通话并跳转到视频通话页面 final success = await CallController.instance.initiateCall( targetUserId: widget.userId, callType: CallType.video, chatController: controller, ); // 只有发起通话成功时才跳转到视频通话页面 if (success) { Get.to(() => VideoCallPage( targetUserId: widget.userId, userData: widget.userData ?? controller.userData, isInitiator: true, )); } }, ); }, alignment: Alignment.bottomCenter, animationType: SmartAnimationType.centerFade_otherSlide, maskColor: TDTheme.of(context).fontGyColor3, onDismiss: () { _controller.setDialogDismiss(false); } ); } @override Widget build(BuildContext context) { return GetBuilder( init: _controller, builder: (controller) { return PopScope( canPop: !controller.isDialogShowing.value, onPopInvokedWithResult: (bool didPop, Object? result) async { // 退出页面时停止播放并销毁播放器 if (controller.isDialogShowing.value) { SmartDialog.dismiss(); return; // 阻止页面返回 } await VoicePlayerManager.instance.stop(); if (!didPop) { Get.back(); } }, child: Stack( children: [ Scaffold( backgroundColor: Color(0xffF5F5F5), appBar: AppBar( title: Row( mainAxisSize: MainAxisSize.min, children: [ Text( controller.userNickName ?? widget.userData?.nickName ?? '', style: TextStyle(fontSize: 18.sp), ), // 使用 Obx 来响应在线状态的变化 Obx(() { final isOnline = controller.isUserOnline.value; if (isOnline) { return Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox(width: 8.w), Container( width: 46.w, height: 26.h, decoration: BoxDecoration( color: Color.fromRGBO(234, 255, 219, 0.5), borderRadius: BorderRadius.circular(178), ), child: Center( child: Text( '在线', style: TextStyle( fontSize: 12.sp, color: Color.fromRGBO(38, 199, 124, 1), ), ), ), ), ], ); } return SizedBox.shrink(); }), ], ), centerTitle: false, actions: [ Container( padding: EdgeInsets.only(right: 16.w), child: Image.asset(Assets.imagesMore, width: 16.w), ).onTap(() { // 跳转到聊天设置页面,传递用户信息 Get.to(() => ChatSettingsPage( userId: widget.userId, userData: widget.userData ?? _controller.userData, )); }), ], leading: IconButton( icon: Icon(Icons.arrow_back_ios), onPressed: () { Get.back(); }, ), ), body: GestureDetector( onTap: () { // 点击空白处收起键盘 FocusManager.instance.primaryFocus?.unfocus(); // 关闭底部面板(表情、更多选项、语音输入) ChatInputBar.closePanels(_chatInputBarKey); }, behavior: HitTestBehavior.opaque, child: Column( children: [ // 消息列表区域 Expanded( child: Container( color: Color(0xffF5F5F5), child: ListView.builder( controller: _scrollController, reverse: false, padding: EdgeInsets.only( top: 16.w, left: 16.w, right: 16.w, bottom: 16.w, ), itemCount: controller.messages.length + 1, // 添加用户信息卡片 // 🚀 性能优化:添加缓存范围,减少重建 cacheExtent: 500, // 缓存屏幕外500像素的内容 itemBuilder: (context, index) { // 第一个是用户信息卡片 if (index == 0) { return _buildUserInfoCard(controller, widget.userData); } final message = controller.messages[index - 1]; Get.log('message347: $message'); final isSentByMe = message.direction == MessageDirection.SEND; final previousMessage = index > 1 ? controller.messages[index - 2] : null; // 检查是否需要显示508错误提示(使用临时状态,不持久化) final showRoseError = isSentByMe && message.status == MessageStatus.FAIL && controller.shouldShowRoseError(message.msgId); // 检查是否需要显示敏感词错误提示(使用临时状态,不持久化) final showSensitiveWordError = isSentByMe && message.status == MessageStatus.FAIL && controller.shouldShowSensitiveWordError(message.msgId); // 🚀 性能优化:为每个消息项设置唯一的 key return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ MessageItem( key: ValueKey(message.msgId), message: message, isSentByMe: isSentByMe, previousMessage: previousMessage, chatController: controller, // 传递 controller 避免使用 Get.find ), // 显示错误提示(错误码508) if (showRoseError) _buildRoseErrorHint(context), // 显示敏感词错误提示(错误码E0001) if (showSensitiveWordError) _buildSensitiveWordErrorHint(context), ], ); }, ), ), ), // 使用抽离的聊天输入栏组件 ChatInputBar( key: _chatInputBarKey, onSendMessage: (message) async { await controller.sendMessage(message); }, onImageSelected: (imagePaths) async { // 为每个图片路径调用控制器的方法发送图片消息 for (var imagePath in imagePaths) { await controller.sendImageMessage(imagePath); } }, onVoiceRecorded: (filePath, seconds) async { // 处理语音录音完成,回传文件路径和秒数 await controller.sendVoiceMessage(filePath, seconds); }, onGiftTap: () { // 显示礼物弹窗 _showGiftPopup(); }, // 视频通话回调 onVideoCall: () async { // 先调用获取聊天音频产品列表接口 final products = await CallController.instance.listChatAudioProduct(widget.userId); // 显示通话类型选择弹框,传入产品数据 _showCallTypeSelectionDialog(controller, products: products); }, ), ], ), ), ), const SvgaPlayerWidget(), ], ), ); }, ); } // 构建用户信息卡片 Widget _buildUserInfoCard(ChatController controller, MarriageData? userData) { // 优先使用传入的 MarriageData,如果没有则使用环信用户信息 final marriageData = userData ?? _getMarriageDataFromController(controller); // 如果用户信息还未加载,显示占位符 if (marriageData == null) { return SizedBox.shrink(); } return IntrinsicHeight( child: GestureDetector( onTap: () { // 点击卡片跳转到用户主页 Get.to(() => UserInformationPage( miId: marriageData.miId, userId: marriageData.userId, )); }, child: Container( margin: EdgeInsets.only(bottom: 16.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.w), image: DecorationImage( image: AssetImage(Assets.imagesChatUserBg), fit: BoxFit.cover, ), ), child: Stack( children: [ // 顶层背景图(chat_user_bg_bottom,覆盖整个卡片) Positioned.fill( child: ClipRRect( borderRadius: BorderRadius.circular(16.w), child: Image.asset( Assets.imagesChatUserBgBottom, fit: BoxFit.cover, // 覆盖整个区域,保持宽高比 ), ), ), // 内容(自适应高度) Padding( padding: EdgeInsets.all(16.w), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 头部:名称、标签、箭头 Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 名称和标签 Wrap( spacing: 8.w, runSpacing: 4.w, crossAxisAlignment: WrapCrossAlignment.center, children: [ Text( marriageData.nickName, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.black87, ), ), // 实名认证标签(和主页一样) if (marriageData.isRealNameCertified) Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 4.h, ), decoration: BoxDecoration( color: Color(0xFFF3E9FF), borderRadius: BorderRadius.circular(12.w), border: Border.all( width: 1, color: Color.fromRGBO(117, 98, 249, 0.32), ), ), constraints: BoxConstraints( minWidth: 40.w, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( Assets.imagesVerifiedIcon, width: 14.w, height: 12.w, ), SizedBox(width: 4.w), Text( '实名', style: TextStyle( fontSize: 9.sp, color: Color.fromRGBO(160, 92, 255, 1), fontWeight: FontWeight.w500, ), ), ], ), ), // 在线状态标签(和主页一样) Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 4.h, ), decoration: BoxDecoration( color: Color.fromRGBO(234, 255, 219, 1), borderRadius: BorderRadius.circular(12.w), border: Border.all( width: 1, color: Color.fromRGBO(117, 98, 249, 0.32), ), ), child: Text( '在线', style: TextStyle( fontSize: 9.sp, color: Color.fromRGBO(38, 199, 124, 1), fontWeight: FontWeight.w500, ), ), ), ], ), SizedBox(height: 8.h), // 用户信息:性别图标、年龄、位置(根据 MarriageData 显示) Row( children: [ // 性别图标(根据实际数据调整,这里暂时默认显示女性图标) Image.asset( marriageData.genderCode == 0 ? Assets.imagesMale : Assets.imagesFemale, width: 14.w, height: 14.w, ), SizedBox(width: 4.w), Text( _calculateAgeFromBirthYear(marriageData.birthYear), style: TextStyle( fontSize: 12.sp, color: Colors.grey[700], ), ), if (marriageData.cityName.isNotEmpty) ...[ SizedBox(width: 12.w), Text( marriageData.cityName, style: TextStyle( fontSize: 12.sp, color: Colors.grey[700], ), ), ], ], ), ], ), ), // 右侧箭头 Container( width: 32.w, height: 32.w, decoration: BoxDecoration( color: Colors.grey[200], shape: BoxShape.circle, ), child: Icon( Icons.arrow_forward_ios, size: 16.w, color: Colors.grey[700], ), ), ], ), SizedBox(height: 8.h), // 减小间距 // 个人简介(单行) if (marriageData.describeInfo.isNotEmpty) Text( marriageData.describeInfo, style: TextStyle( fontSize: 13.sp, color: Colors.grey[800], height: 1.4, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), if (marriageData.photoList.isNotEmpty) ...[ SizedBox(height: 8.h), // 图片上方间距 // 图片画廊(正方形)- 固定4张的布局 Row( children: List.generate(4, (index) { // 如果索引超出照片列表长度,返回空容器 if (index >= marriageData.photoList.length) { return Expanded( child: AspectRatio( aspectRatio: 1, // 设置为正方形 child: Container( margin: EdgeInsets.only( right: index < 3 ? 8.w : 0, ), ), ), ); } final photo = marriageData.photoList[index]; final photoUrl = photo.photoUrl.trim().replaceAll('`', ''); // 移除URL中的反引号 return Expanded( child: AspectRatio( aspectRatio: 1, // 设置为正方形 child: Container( margin: EdgeInsets.only( right: index < 3 ? 8.w : 0, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.w), image: DecorationImage( image: NetworkImage(photoUrl), fit: BoxFit.cover, onError: (exception, stackTrace) { // 加载失败时显示占位图 return; }, ), ), ), ), ); }), ), SizedBox(height: 4.h), // 图片下方间距 ], ], ), ), ], ), ), ), ); } // 从 Controller 获取 MarriageData(如果没有传入) MarriageData? _getMarriageDataFromController(ChatController controller) { // 优先从 controller 获取已保存的 userData if (controller.userData != null) { return controller.userData; } // 如果 controller 也没有,返回 null,不显示卡片 return null; } // 加载更多消息 Future _loadMoreMessages() async { if (_isLoadingMore) return; setState(() { _isLoadingMore = true; }); try { await _controller.fetchMessages(loadMore: true); } finally { if (mounted) { setState(() { _isLoadingMore = false; }); } } } // 构建玫瑰不足错误提示 Widget _buildRoseErrorHint(BuildContext context) { return Container( margin: EdgeInsets.only(top: 8.h, bottom: 8.h), padding: EdgeInsets.symmetric(horizontal: 16.w), width: double.infinity, child: Center( child: RichText( textAlign: TextAlign.center, text: TextSpan( children: [ TextSpan( text: '您的玫瑰余额不足哦~快来充值补充,立刻点亮精彩对话 ', style: TextStyle( fontSize: 11.sp, color: Color.fromRGBO(199, 199, 199, 1), ), ), WidgetSpan( child: GestureDetector( onTap: () { // 隐藏键盘 FocusScope.of(context).unfocus(); _controller.setDialogDismiss(true); SmartDialog.show( alignment: Alignment.bottomCenter, maskColor: TDTheme.of(context).fontGyColor2, onDismiss: () { _controller.setDialogDismiss(false); }, builder: (_) => const LiveRechargePopup(), ); }, child: Text( '立即充值', style: TextStyle( fontSize: 11.sp, color: Color.fromRGBO(117, 98, 249, 1), ), ), ), ), ], ), ), ), ); } // 构建敏感词错误提示 Widget _buildSensitiveWordErrorHint(BuildContext context) { return Container( margin: EdgeInsets.only(top: 8.h, bottom: 8.h), padding: EdgeInsets.symmetric(horizontal: 16.w), width: double.infinity, child: Center( child: Text( '聊天内容包含敏感词', style: TextStyle( fontSize: 11.sp, color: Color.fromRGBO(199, 199, 199, 1), ), textAlign: TextAlign.center, ), ), ); } }