import 'package:dating_touchme_app/extension/ex_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 '../../controller/message/chat_controller.dart'; import '../../controller/message/voice_player_manager.dart'; import '../../generated/assets.dart'; import '../../../widget/message/chat_input_bar.dart'; import '../../../widget/message/message_item.dart'; import 'chat_settings_page.dart'; class ChatPage extends StatefulWidget { final String userId; const ChatPage({required this.userId, super.key}); @override State createState() => _ChatPageState(); } class _ChatPageState extends State { final ScrollController _scrollController = ScrollController(); bool _isLoadingMore = false; late ChatController _controller; @override void initState() { super.initState(); // 初始化 controller _controller = Get.put(ChatController(userId: widget.userId)); // 监听滚动,当滚动到顶部时加载更多消息 _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, ); } }); }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return GetBuilder( init: _controller, builder: (controller) { return WillPopScope( onWillPop: () async { // 退出页面时停止播放并销毁播放器 await VoicePlayerManager.instance.stop(); return true; }, child: Scaffold( backgroundColor: Color(0xffF5F5F5), appBar: AppBar( title: Text(controller.userInfo.value?.nickName ?? ''), centerTitle: true, actions: [ Container( padding: EdgeInsets.only(right: 16.w), child: Image.asset(Assets.imagesMore, width: 16.w), ).onTap(() { // 跳转到聊天设置页面 Get.to(() => ChatSettingsPage(userId: widget.userId)); }), ], leading: IconButton( icon: Icon(Icons.arrow_back_ios), onPressed: () { Get.back(); }, ), ), body: Column( children: [ // 消息列表区域 Expanded( child: Container( color: Color(0xffF5F5F5), child: GestureDetector( onTap: () { // 点击消息区域收起键盘 FocusManager.instance.primaryFocus?.unfocus(); }, behavior: HitTestBehavior.opaque, 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); } final message = controller.messages[index - 1]; final isSentByMe = message.direction == MessageDirection.SEND; final previousMessage = index > 1 ? controller.messages[index - 2] : null; // 🚀 性能优化:为每个消息项设置唯一的 key return MessageItem( key: ValueKey(message.msgId), message: message, isSentByMe: isSentByMe, previousMessage: previousMessage, ); }, ), ), ), ), // 使用抽离的聊天输入栏组件 ChatInputBar( 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); }, onVideoRecorded: (filePath, duration) async { print('🎬 [ChatPage] 收到视频录制/选择回调'); print('文件路径: $filePath'); print('时长: $duration 秒'); // 检查是否正在发送 if (controller.isSendingVideo.value) { print('⚠️ [ChatPage] 视频正在发送中,忽略新的发送请求'); return; } // 处理视频录制/选择完成,回传文件路径和时长 await controller.sendVideoMessage(filePath, duration); }, ), ], ), ), ); }, ); } // 构建用户信息卡片 Widget _buildUserInfoCard(ChatController controller) { final userInfo = controller.userInfo.value; // 如果用户信息还未加载,显示占位符 if (userInfo == null) { return SizedBox.shrink(); } return Container( margin: EdgeInsets.only(bottom: 16.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.w), ), child: LayoutBuilder( builder: (context, constraints) { // 计算按710:351比例的高度 // 宽度需要考虑 ListView 的 padding(左右各16.w) final screenWidth = MediaQuery.of(context).size.width; final availableWidth = screenWidth - 32.w; // 减去左右 padding final width = availableWidth; final height = width * (351 / 710); return Container( height: height, decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.w), image: DecorationImage( image: AssetImage(Assets.imagesChatUserBg), fit: BoxFit.cover, ), ), child: Stack( children: [ // 顶层背景图(chat_user_bg_bottom,按710x351比例覆盖整个卡片) 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( userInfo.nickName ?? '用户', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.black87, ), ), // 实名认证标签(和主页一样) Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 2.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: 2.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), // 用户信息:性别图标、年龄、位置、身高 Row( children: [ // 性别图标(默认显示女性图标,可以根据实际数据调整) Image.asset( Assets.imagesFemale, width: 14.w, height: 14.w, ), SizedBox(width: 4.w), Text( '19', style: TextStyle( fontSize: 12.sp, color: Colors.grey[700], ), ), SizedBox(width: 12.w), Text( '北京', style: TextStyle( fontSize: 12.sp, color: Colors.grey[700], ), ), SizedBox(width: 12.w), Text( '160cm', 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), // 减小间距 // 个人简介(单行) Text( '喜欢在广州的早茶里抢最后一个虾饺,也能在深夜的猎德大桥...', style: TextStyle( fontSize: 13.sp, color: Colors.grey[800], height: 1.4, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), SizedBox(height: 8.h), // 减小间距 // 图片画廊 Row( children: List.generate(4, (index) { return Expanded( child: Container( margin: EdgeInsets.only( right: index < 3 ? 8.w : 0, ), height: 50.w, // 进一步减小高度避免被裁剪 decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.w), image: DecorationImage( image: AssetImage(Assets.imagesAvatarsExample), fit: BoxFit.cover, ), ), ), ); }), ), ], ), ), ], ), ); }, ), ); } // 加载更多消息 Future _loadMoreMessages() async { if (_isLoadingMore) return; setState(() { _isLoadingMore = true; }); try { await _controller.fetchMessages(loadMore: true); } finally { if (mounted) { setState(() { _isLoadingMore = false; }); } } } }