import 'dart:async'; import 'dart:ui'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:im_flutter_sdk/im_flutter_sdk.dart'; import '../../controller/message/call_controller.dart'; import '../../controller/message/chat_controller.dart'; import '../../controller/message/conversation_controller.dart'; import '../../controller/overlay_controller.dart'; import '../../model/home/marriage_data.dart'; /// 视频通话页面 class VideoCallPage extends StatefulWidget { final String targetUserId; final String? callType; final String? channelId; final MarriageData? userData; final bool isInitiator; // 是否是发起方 final EMMessage? callMessage; // 通话消息(用于接听通话时更新消息状态) const VideoCallPage({ super.key, required this.targetUserId, this.callType, this.channelId, this.userData, this.isInitiator = true, this.callMessage, }); @override State createState() => _VideoCallPageState(); } class _VideoCallPageState extends State { final CallController _callController = CallController.instance; final RTCManager _rtcManager = RTCManager.instance; bool _isMicMuted = false; bool _isSpeakerOn = false; Duration _callDuration = Duration.zero; Timer? _durationTimer; String? _targetUserName; String? _targetAvatarUrl; // 通话是否已接通 bool _isCallConnected = false; // 本地视频视图控制器 VideoViewController? _localVideoViewController; @override void initState() { super.initState(); _loadUserInfo(); _initCallStatus(); _startDurationTimer(); _initLocalVideo(); // 设置系统UI样式 SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []); SystemChrome.setPreferredOrientations([ DeviceOrientation.portraitUp, ]); } /// 初始化本地视频视图 void _initLocalVideo() { final callSession = _callController.currentCall.value; // 如果是视频通话,创建本地视频视图控制器 if (callSession != null && callSession.callType == CallType.video) { final engine = _rtcManager.engine; if (engine != null) { _localVideoViewController = VideoViewController( rtcEngine: engine, canvas: const VideoCanvas(uid: 0), ); } } } /// 初始化通话状态 void _initCallStatus() { // 检查当前通话状态 final callSession = _callController.currentCall.value; if (callSession != null && _callController.callDurationSeconds.value > 0) { // 如果通话已存在且已经开始计时,说明已接通 _isCallConnected = true; _callDuration = Duration(seconds: _callController.callDurationSeconds.value); } else { // 否则是未接通状态 _isCallConnected = false; } } /// 加载用户信息 Future _loadUserInfo() async { // 优先使用传入的 userData if (widget.userData != null) { setState(() { _targetUserName = widget.userData!.nickName.isNotEmpty ? widget.userData!.nickName : widget.targetUserId; _targetAvatarUrl = widget.userData!.profilePhoto; }); return; } // 如果没有传入 userData,尝试从 ConversationController 获取 try { if (Get.isRegistered()) { final conversationController = Get.find(); // 先从缓存中获取 final cachedUserInfo = conversationController.getCachedUserInfo(widget.targetUserId); if (cachedUserInfo != null && (cachedUserInfo.nickName != null || cachedUserInfo.avatarUrl != null)) { setState(() { _targetUserName = cachedUserInfo.nickName ?? widget.targetUserId; _targetAvatarUrl = cachedUserInfo.avatarUrl; }); return; } // 如果缓存中没有,尝试从 IM 加载 final userInfo = await conversationController.loadContact(widget.targetUserId); if (userInfo != null && (userInfo.nickName != null || userInfo.avatarUrl != null)) { setState(() { _targetUserName = userInfo.nickName ?? widget.targetUserId; _targetAvatarUrl = userInfo.avatarUrl; }); return; } } } catch (e) { print('⚠️ [VideoCallPage] 加载用户信息失败: $e'); } // 如果都获取不到,使用默认值 setState(() { _targetUserName = widget.targetUserId; _targetAvatarUrl = null; }); } @override void dispose() { _durationTimer?.cancel(); _localVideoViewController?.dispose(); SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); SystemChrome.setPreferredOrientations(DeviceOrientation.values); super.dispose(); } /// 开始通话时长计时器 void _startDurationTimer() { // 监听 CallController 的通话状态变化 _callController.currentCall.listen((callSession) { if (mounted) { final wasConnected = _isCallConnected; // 如果通话存在且已经开始计时,说明已接通 if (callSession != null && _callController.callDurationSeconds.value > 0) { _isCallConnected = true; if (!wasConnected) { // 刚接通,同步时长 _callDuration = Duration(seconds: _callController.callDurationSeconds.value); } } else if (callSession == null) { _isCallConnected = false; } setState(() {}); } }); // 监听通话时长变化(已接通时更新) _callController.callDurationSeconds.listen((seconds) { if (mounted && _isCallConnected) { setState(() { _callDuration = Duration(seconds: seconds); }); } else if (mounted && !_isCallConnected && seconds > 0) { // 如果时长开始增加,说明刚接通 setState(() { _isCallConnected = true; _callDuration = Duration(seconds: seconds); }); } }); // 如果未接通,使用本地计时器检查状态变化 if (!_isCallConnected) { _durationTimer = Timer.periodic(Duration(seconds: 1), (timer) { if (mounted) { final callSession = _callController.currentCall.value; final duration = _callController.callDurationSeconds.value; // 检查是否已接通(通话存在且时长大于0) if (callSession != null && duration > 0) { _isCallConnected = true; _callDuration = Duration(seconds: duration); timer.cancel(); setState(() {}); } } }); } } /// 格式化通话时长 String _formatDuration(Duration duration) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final minutes = twoDigits(duration.inMinutes.remainder(60)); final seconds = twoDigits(duration.inSeconds.remainder(60)); return '$minutes:$seconds'; } /// 切换麦克风状态 void _toggleMic() { setState(() { _isMicMuted = !_isMicMuted; }); // TODO: 调用RTC Manager切换麦克风 // _rtcManager.enableAudio(!_isMicMuted); } /// 切换扬声器状态 void _toggleSpeaker() { setState(() { _isSpeakerOn = !_isSpeakerOn; }); // TODO: 调用RTC Manager切换扬声器 // _rtcManager.setEnableSpeakerphone(_isSpeakerOn); } /// 挂断通话 void _hangUp() async { try { // TODO: 离开RTC频道 // await _rtcManager.leaveChannel(); // 结束通话(传递通话时长) await _callController.endCall(callDuration: _callDuration.inSeconds); // 返回上一页 Get.back(); } catch (e) { print('挂断通话失败: $e'); Get.back(); } } @override Widget build(BuildContext context) { return PopScope( canPop: false, // 禁止手势返回 onPopInvoked: (didPop) { // 已经禁止返回,所以这里不会被调用 // 如果需要返回,应该通过挂断按钮或其他明确的操作 }, child: Scaffold( backgroundColor: Colors.black, body: Stack( children: [ // 背景视频/头像(模糊) _buildBackground(), // 最小化按钮(左上角) _buildMinimizeButton(), // 用户信息 _buildUserInfo(), // 通话时长 _buildCallDuration(), // 底部控制按钮 _buildControlButtons(), ], ), ), ); } /// 构建最小化按钮 Widget _buildMinimizeButton() { return Positioned( top: 26.w, left: 26.w, child: GestureDetector( onTap: _minimizeCall, child: Image.asset(Assets.imagesCloseArrow, width: 20.w, height: 20.w), ), ); } /// 最小化通话 void _minimizeCall() { // 显示视频通话小窗 if (Get.isRegistered()) { final overlayController = Get.find(); overlayController.showVideoCall( targetUserId: widget.targetUserId, targetUserName: _targetUserName, targetAvatarUrl: _targetAvatarUrl, ); } // 返回上一页 Get.back(); } /// 构建背景 Widget _buildBackground() { final callSession = _callController.currentCall.value; final isVideoCall = callSession != null && callSession.callType == CallType.video; // 使用 Obx 监听远端用户 UID 的变化 if (isVideoCall) { return Obx(() { // 同时监听 CallController.remoteUid 和 RTCManager.remoteUsersNotifier var remoteUid = _callController.remoteUid.value; final remoteUsers = _rtcManager.remoteUsersNotifier.value; // 触发 Obx 监听 print('📞 [VideoCallPage] Obx 重建,CallController.remoteUid: ${_callController.remoteUid.value}, remoteUsers: $remoteUsers, isVideoCall: $isVideoCall'); // 如果 remoteUid 为空,尝试从 RTCManager 的远端用户列表中获取 if (remoteUid == null && remoteUsers.isNotEmpty) { remoteUid = remoteUsers.first; // 同步到 CallController _callController.remoteUid.value = remoteUid; print('📞 [VideoCallPage] 从 RTCManager.remoteUsersNotifier 获取到 remoteUid: $remoteUid'); } // 如果远端用户已加入,显示远端视频视图(对方画面) if (remoteUid != null) { final engine = _rtcManager.engine; print('📞 [VideoCallPage] remoteUid 不为 null: $remoteUid, engine: ${engine != null}'); if (engine != null) { print('📞 [VideoCallPage] 显示远端视频视图,UID:$remoteUid'); final remoteVideoViewController = VideoViewController( rtcEngine: engine, canvas: VideoCanvas(uid: remoteUid), ); return SizedBox( width: double.infinity, height: 1.sh, child: AgoraVideoView( controller: remoteVideoViewController, ), ); } else { print('⚠️ [VideoCallPage] engine 为 null,无法显示远端视频'); } } else { print('⚠️ [VideoCallPage] remoteUid 为 null,无法显示远端视频'); } // 如果没有远端视频,显示本地视频视图(自己的画面) if (_localVideoViewController != null) { print('📞 [VideoCallPage] 显示本地视频视图'); return SizedBox( width: double.infinity, height: 1.sh, child: AgoraVideoView( controller: _localVideoViewController!, ), ); } // 如果本地视频也没有,显示模糊的头像背景 print('📞 [VideoCallPage] 显示头像背景'); return _buildAvatarBackground(); }); } // 非视频通话,显示模糊的头像背景 return _buildAvatarBackground(); } /// 构建头像背景 Widget _buildAvatarBackground() { return SizedBox( width: double.infinity, height: 1.sh, child: _targetAvatarUrl != null && _targetAvatarUrl!.isNotEmpty ? ImageFiltered( imageFilter: ImageFilter.blur(sigmaX: 15, sigmaY: 15), child: CachedNetworkImage( imageUrl: _targetAvatarUrl!, fit: BoxFit.cover, width: double.infinity, height: double.infinity, errorWidget: (context, url, error) => _buildDefaultBackground(), ), ) : _buildDefaultBackground(), ); } /// 构建默认背景 Widget _buildDefaultBackground() { return Container( color: Colors.black, ); } /// 构建用户信息 Widget _buildUserInfo() { return Positioned( top: MediaQuery.of(context).size.height * 0.15, left: 0, right: 0, child: Column( children: [ // 头像 ClipOval( child: _targetAvatarUrl != null && _targetAvatarUrl!.isNotEmpty ? CachedNetworkImage( imageUrl: _targetAvatarUrl!, width: 120.w, height: 120.w, fit: BoxFit.cover, errorWidget: (context, url, error) => Image.asset( Assets.imagesUserAvatar, width: 120.w, height: 120.w, fit: BoxFit.cover, ), ) : Image.asset( Assets.imagesUserAvatar, width: 120.w, height: 120.w, fit: BoxFit.cover, ), ), SizedBox(height: 16.h), // 用户名 Text( _targetUserName ?? widget.targetUserId, style: TextStyle( color: Colors.white, fontSize: 24.sp, fontWeight: FontWeight.w600, ), ), ], ), ); } /// 构建通话时长/状态文本 Widget _buildCallDuration() { // 如果是被呼叫方且未接通,显示邀请文字 if (!widget.isInitiator && !_isCallConnected) { final isVideoCall = widget.callType == 'video'; final inviteText = isVideoCall ? '邀请你视频通话' : '邀请你语音通话'; return Positioned( bottom: MediaQuery.of(context).size.height * 0.25, left: 0, right: 0, child: Center( child: Text( inviteText, style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), ), ); } // 呼叫方或已接通,显示时长或"正在呼叫中" return Positioned( bottom: MediaQuery.of(context).size.height * 0.25, left: 0, right: 0, child: Center( child: Text( _isCallConnected ? _formatDuration(_callDuration) : '正在呼叫中', style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), ), ); } /// 构建控制按钮 Widget _buildControlButtons() { // 如果是被呼叫方且未接通,显示"拒绝"和"接听"按钮 if (!widget.isInitiator && !_isCallConnected) { return Positioned( bottom: 40.h, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 拒绝按钮 _buildControlButton( icon: Icons.call_end, label: '拒绝', isActive: true, onTap: _rejectCall, isReject: true, ), // 接听按钮 _buildControlButton( icon: Icons.phone, label: '接听', isActive: true, onTap: _acceptCall, isAccept: true, ), ], ), ); } // 呼叫方或已接通,显示常规控制按钮 return Positioned( bottom: 40.h, left: 0, right: 0, child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // 免提按钮 _buildControlButton( icon: Icons.volume_up, label: '免提', isActive: _isSpeakerOn, onTap: _toggleSpeaker, ), // 麦克风按钮 _buildControlButton( icon: Icons.mic, label: '麦克风', isActive: !_isMicMuted, onTap: _toggleMic, ), // 挂断按钮 _buildControlButton( icon: Icons.call_end, label: '挂断', isActive: true, onTap: _hangUp, isHangUp: true, ), ], ), ); } /// 构建控制按钮 Widget _buildControlButton({ required IconData icon, required String label, required bool isActive, required VoidCallback onTap, bool isHangUp = false, bool isReject = false, bool isAccept = false, }) { Color buttonColor; if (isHangUp || isReject) { buttonColor = Color(0xFFFF3B30); // 红色 } else if (isAccept) { buttonColor = Color(0xFF34C759); // 绿色 } else { buttonColor = isActive ? Colors.white.withOpacity(0.3) : Colors.white.withOpacity(0.2); } return GestureDetector( onTap: onTap, child: Column( children: [ Container( width: 56.w, height: 56.w, decoration: BoxDecoration( color: buttonColor, shape: BoxShape.circle, ), child: Icon( icon, color: Colors.white, size: 28.w, ), ), SizedBox(height: 8.h), Text( label, style: TextStyle( color: Colors.white, fontSize: 12.sp, ), ), ], ), ); } /// 接听通话 Future _acceptCall() async { try { if (widget.callMessage == null) { SmartDialog.showToast('未找到通话邀请消息'); return; } // 尝试获取 ChatController ChatController? chatController; try { final tag = 'chat_${widget.targetUserId}'; if (Get.isRegistered(tag: tag)) { chatController = Get.find(tag: tag); } } catch (e) { print('⚠️ [VideoCallPage] 获取ChatController失败: $e'); } final accepted = await _callController.acceptCall( message: widget.callMessage!, chatController: chatController, ); if (accepted) { // 通话已接通,UI会自动更新 print('✅ [VideoCallPage] 通话已接通'); } else { SmartDialog.showToast('接听失败'); } } catch (e) { print('❌ [VideoCallPage] 接听通话失败: $e'); SmartDialog.showToast('接听失败: $e'); } } /// 拒绝通话 Future _rejectCall() async { try { // 尝试获取 ChatController ChatController? chatController; try { final tag = 'chat_${widget.targetUserId}'; if (Get.isRegistered(tag: tag)) { chatController = Get.find(tag: tag); } } catch (e) { print('⚠️ [VideoCallPage] 获取ChatController失败: $e'); } if (widget.callMessage == null) { // 即使没有消息,也执行拒绝操作(关闭页面) await _callController.endCall(callDuration: 0); Get.back(); return; } final rejected = await _callController.rejectCall( message: widget.callMessage!, chatController: chatController, ); if (rejected) { // 拒绝成功,返回上一页 Get.back(); } } catch (e) { print('❌ [VideoCallPage] 拒绝通话失败: $e'); // 即使失败也返回上一页 Get.back(); } } }