import 'package:cached_network_image/cached_network_image.dart'; import 'package:dating_touchme_app/controller/message/call_manager.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:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; /// 视频通话小窗组件 class VideoCallOverlayWidget extends StatefulWidget { final VoidCallback? onClose; final String targetUserId; final String? targetUserName; final String? targetAvatarUrl; const VideoCallOverlayWidget({ super.key, this.onClose, required this.targetUserId, this.targetUserName, this.targetAvatarUrl, }); @override State createState() => _VideoCallOverlayWidgetState(); } class _VideoCallOverlayWidgetState extends State { Offset _position = Offset.zero; bool _isDragging = false; final CallManager _callManager = CallManager.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; }); } /// 格式化通话时长 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 = _callManager.currentCall.value; final isConnected = callSession != null && _callManager.callDurationSeconds.value > 0; final callDuration = _callManager.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: [ // 背景:头像 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, ), ), // 半透明遮罩 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.to(() => VideoCallPage( targetUserId: widget.targetUserId, isInitiator: callSession?.isInitiator ?? true, )); widget.onClose?.call(); }); }), ); } }