import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:dating_touchme_app/controller/discover/room_controller.dart'; import 'package:dating_touchme_app/controller/overlay_controller.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/pages/discover/live_room_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; /// 可拖拽的 Overlay 小组件 class DraggableOverlayWidget extends StatefulWidget { final VoidCallback? onClose; final Widget? child; final double size; final Color backgroundColor; const DraggableOverlayWidget({ super.key, this.onClose, this.child, this.size = 60, this.backgroundColor = const Color.fromRGBO(0, 0, 0, 0.5), }); @override State createState() => _DraggableOverlayWidgetState(); } class _DraggableOverlayWidgetState extends State { Offset _position = Offset.zero; bool _isDragging = false; final RTCManager _rtcManager = RTCManager.instance; final RoomController _roomController = Get.find(); final OverlayController _overlayController = Get.find(); @override void initState() { super.initState(); // 初始位置设置为右上角 WidgetsBinding.instance.addPostFrameCallback((_) { final size = MediaQuery.of(context).size; setState(() { _position = Offset( size.width - 100.w, // 右上角,无间隙(使用100.w因为用户改了size) 100, ); }); }); } /// 吸附到边缘 void _snapToEdge(double screenWidth) { final centerX = screenWidth / 2; final targetX = _position.dx < centerX ? 0.0 // 吸附到左边,无间隙 : screenWidth - 100.w; // 吸附到右边,无间隙(使用100.w因为用户改了size) // 使用 setState 更新位置,AnimatedPositioned 会自动处理动画 setState(() { _position = Offset(targetX, _position.dy); _isDragging = false; }); } /// 构建主播视频视图 Widget _buildAnchorVideo() { final engine = _rtcManager.engine; if (_roomController.rtcChannelDetail.value?.anchorInfo == null || engine == null) { return Container( decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(8.w)), color: Colors.grey.withOpacity(0.5), ), child: Center( child: Text( '等待主播', style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 12.w, ), ), ), ); } return ClipRRect( borderRadius: BorderRadius.all(Radius.circular(8.w)), child: _roomController.currentRole == CurrentRole.broadcaster ? AgoraVideoView( controller: VideoViewController( rtcEngine: engine, canvas: const VideoCanvas(uid: 0), ), ) : AgoraVideoView( controller: VideoViewController.remote( rtcEngine: engine, canvas: VideoCanvas( uid: _roomController.rtcChannelDetail.value?.anchorInfo?.uid, ), connection: RtcConnection( channelId: _rtcManager.currentChannelId ?? '', ), ), ), ); } @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; // 限制在屏幕范围内(使用100.w因为用户改了size) _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(() { 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: [ // 视频内容 widget.child ?? _buildAnchorVideo(), // 右上角关闭按钮 Positioned( top: 2.w, right: 2.w, child: GestureDetector( onTap: () async { await _roomController.leaveChannel(); widget.onClose?.call(); }, child: Container( width: 20.w, height: 20.w, decoration: BoxDecoration( color: Colors.black.withOpacity(0.5), shape: BoxShape.circle, ), child: Icon(Icons.close, color: Colors.white, size: 14.w), ), ), ), ], ), ), ).onTap(() async { // 先隐藏小窗口(直接调用 OverlayController) _overlayController.hide(); // 等待响应式更新完成,确保小窗口完全隐藏后再跳转 // 使用循环检查,最多等待300ms,确保小窗口已隐藏 int waitCount = 0; const maxWait = 6; // 最多等待6次,每次50ms,总共300ms while (waitCount < maxWait && _overlayController.showOverlay.value) { await Future.delayed(const Duration(milliseconds: 50)); waitCount++; } // 确保小窗口已隐藏后再跳转到直播间 if (!_overlayController.showOverlay.value) { Get.to(() => const LiveRoomPage(id: 0)); } else { // 如果还是显示状态,强制隐藏后再跳转 _overlayController.hide(); await Future.delayed(const Duration(milliseconds: 100)); Get.to(() => const LiveRoomPage(id: 0)); } }); }), ); } }