import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dating_touchme_app/controller/message/call_controller.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/pages/message/video_call_page.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.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'; /// 视频通话小窗组件 class VideoCallOverlayWidget extends StatefulWidget { final VoidCallback? onClose; final String targetUserId; final String? targetUserName; final String? targetAvatarUrl; final EMMessage? message; const VideoCallOverlayWidget({ super.key, this.onClose, required this.targetUserId, this.targetUserName, this.targetAvatarUrl, this.message, }); @override State createState() => _VideoCallOverlayWidgetState(); } class _VideoCallOverlayWidgetState extends State { Offset _position = Offset.zero; bool _isDragging = false; final CallController _callController = CallController.instance; final RTCManager _rtcManager = RTCManager.instance; @override void initState() { super.initState(); // 初始位置设置为右上角 WidgetsBinding.instance.addPostFrameCallback((_) { final size = MediaQuery.of(context).size; setState(() { _position = Offset( size.width - 100.w, 100, ); }); }); } /// 吸附到边缘 void _snapToEdge(double screenWidth) { final centerX = screenWidth / 2; final targetX = _position.dx < centerX ? 0.0 : screenWidth - 100.w; setState(() { _position = Offset(targetX, _position.dy); _isDragging = false; }); } /// 构建背景(视频或头像) Widget _buildBackground() { return Obx(() { // 在 Obx 中访问响应式变量,确保建立监听关系 final callSession = _callController.currentCall.value; final isVideoCall = callSession != null && callSession.callType == CallType.video; final isConnected = callSession != null && _callController.callDurationSeconds.value > 0; final remoteUid = _callController.remoteUid.value; final remoteUsers = _rtcManager.remoteUsersNotifier.value; // 如果是视频通话且已接通,显示远端视频 if (isVideoCall && isConnected && remoteUid != null) { final engine = _rtcManager.engine; if (engine != null) { final remoteVideoViewController = VideoViewController( rtcEngine: engine, canvas: VideoCanvas(uid: remoteUid), ); return SizedBox( width: 100.w, height: 100.w, child: AgoraVideoView( controller: remoteVideoViewController, ), ); } } // 如果 remoteUid 为空,尝试从 RTCManager 的远端用户列表中获取 if (isVideoCall && isConnected && remoteUid == null && remoteUsers.isNotEmpty) { final firstRemoteUid = remoteUsers.first; final engine = _rtcManager.engine; if (engine != null) { _callController.remoteUid.value = firstRemoteUid; final remoteVideoViewController = VideoViewController( rtcEngine: engine, canvas: VideoCanvas(uid: firstRemoteUid), ); return SizedBox( width: 100.w, height: 100.w, child: AgoraVideoView( controller: remoteVideoViewController, ), ); } } // 默认显示头像 return Container( width: 100.w, height: 100.w, color: Colors.black, child: widget.targetAvatarUrl != null && widget.targetAvatarUrl!.isNotEmpty ? CachedNetworkImage( imageUrl: widget.targetAvatarUrl!, fit: BoxFit.cover, errorWidget: (context, url, error) => Image.asset( Assets.imagesUserAvatar, fit: BoxFit.cover, ), ) : Image.asset( Assets.imagesUserAvatar, fit: BoxFit.cover, ), ); }); } /// 格式化通话时长 String _formatDuration(int seconds) { String twoDigits(int n) => n.toString().padLeft(2, '0'); final minutes = twoDigits(seconds ~/ 60); final secs = twoDigits(seconds % 60); return '$minutes:$secs'; } @override Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; return AnimatedPositioned( duration: _isDragging ? const Duration(milliseconds: 50) : const Duration(milliseconds: 300), curve: _isDragging ? Curves.linear : Curves.easeOut, left: _position.dx, top: _position.dy, child: _buildContent(screenSize), ); } Widget _buildContent(Size screenSize) { return GestureDetector( onPanStart: (details) { setState(() { _isDragging = true; }); }, onPanUpdate: (details) { setState(() { _position += details.delta; _position = Offset( _position.dx.clamp(0.0, screenSize.width - 100.w), _position.dy.clamp(0.0, screenSize.height - 100.w), ); }); }, onPanEnd: (details) { _snapToEdge(screenSize.width); }, child: Obx(() { final callSession = _callController.currentCall.value; final isConnected = callSession != null && _callController.callDurationSeconds.value > 0; final callDuration = _callController.callDurationSeconds.value; return Container( width: 100.w, height: 100.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8.w), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.3), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(8.w), child: Stack( children: [ // 背景:视频通话接通时显示远端视频,否则显示头像 _buildBackground(), // 半透明遮罩 Container( color: Colors.black.withOpacity(0.4), ), // 内容:昵称和状态 Positioned( bottom: 8.w, left: 4.w, right: 4.w, child: Column( mainAxisSize: MainAxisSize.min, children: [ // 昵称 Text( widget.targetUserName ?? widget.targetUserId, style: TextStyle( color: Colors.white, fontSize: 10.sp, fontWeight: FontWeight.w600, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), SizedBox(height: 2.h), // 通话状态 Text( isConnected ? _formatDuration(callDuration) : '通话中', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 8.sp, ), ), ], ), ), ], ), ), ).onTap(() { Get.log("点击小窗${widget.message}"); // 点击小窗,返回视频通话页面 Get.to(() => VideoCallPage( targetUserId: widget.targetUserId, callMessage: widget.message, isInitiator: callSession?.isInitiator ?? CallController.instance.callRole == CallRole.caller ? true : false, )); widget.onClose?.call(); }); }), ); } }