import 'dart:async'; import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:flutter_debouncer/flutter_debouncer.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:provider/provider.dart'; import '../../../chatvibe_core/at_lk_event_bus.dart'; import '../../../chatvibe_core/constants/at_global_config.dart'; import '../../../chatvibe_domain/models/res/gift_res.dart'; import '../../../chatvibe_managers/gift_system_manager.dart'; import '../../../chatvibe_managers/rtc_manager.dart'; import '../../../main.dart'; import '../../components/at_compontent.dart'; class GiveRoomLuckPage extends StatefulWidget { ChatVibeGiftRes checkedGift; List acceptUserIds = []; Function() call; GiveRoomLuckPage(this.checkedGift, this.acceptUserIds, this.call); @override _GiveRoomLuckPageState createState() => _GiveRoomLuckPageState(); } class _GiveRoomLuckPageState extends State with TickerProviderStateMixin { static const int _maxOverlayAnimations = 10; var debouncer = Debouncer(); final Set _runningControllers = {}; final Set _activeOverlays = {}; final Queue<_LuckFanOutTask> _pendingFanOutTasks = Queue<_LuckFanOutTask>(); // bool isFasheOk = true; final GlobalKey _sourceKey1 = GlobalKey(); final GlobalKey _targetKey1 = GlobalKey(); bool isFashe = false; late AnimationController _fasheController; late Animation _fasheAnimation; Timer? timer; int startFasheTime = 0; @override void initState() { super.initState(); Provider.of( navigatorKey.currentContext!, listen: false, ).reset(); Provider.of( navigatorKey.currentContext!, listen: false, ).updateHideLGiftAnimal(true); initFasheAnim(); // initScanAnim(); startTimer(); // GiftVapSvgaManager().pauseAnim(); } @override void dispose() { eventBus.fire(ATGiveRoomLuckPageDisposeEvent()); _disposeTransientEffects(); _fasheController.dispose(); timer?.cancel(); // GiftVapSvgaManager().resumeAnim(); super.dispose(); } @override Widget build(BuildContext context) { return SafeArea( top: false, child: Stack( alignment: Alignment.bottomCenter, children: [ GestureDetector( child: Container(color: Colors.transparent), onTap: () { SmartDialog.dismiss(tag: "showGiftControl"); }, ), Positioned( right: ATGlobalConfig.lang == "ar" ? null : 0, left: ATGlobalConfig.lang == "ar" ? 0 : null, bottom: 0, child: Transform.rotate( angle: ATGlobalConfig.lang == "ar" ? 0.5 : -0.5, child: Transform.translate( offset: ATGlobalConfig.lang == "ar" ? Offset(-22, 35) : Offset(22, 35 ), child: AnimatedBuilder( animation: _fasheAnimation, builder: (context, child) { return Transform.scale( scale: _fasheAnimation.value, child: SizedBox( width: 200.w, height: 200.w, child: Stack( children: [ Image.asset( "atu_images/room/at_icon_luck_gift_daiji.webp", ), isFashe ? Image.asset( "atu_images/room/at_icon_luck_fashe.webp", ) : Container(), ], ), ), ); }, ), ), ), ), Positioned( bottom: 350.w, child: SizedBox(width: 100.w, height: 100.w, key: _targetKey1), ), Positioned( bottom: 160.w, right: ATGlobalConfig.lang == "ar" ? null : 105.w, left: ATGlobalConfig.lang == "ar" ? 105.w : null, child: SizedBox(key: _sourceKey1, width: 35.w, height: 35.w,), ), Positioned( right: ATGlobalConfig.lang == "ar" ? null : 0, left: ATGlobalConfig.lang == "ar" ? 0 : null, child: GestureDetector( child: Container( width: 100.w, height: 100.w, color: Colors.transparent, ), onTap: () { startFashe(); debouncer.debounce( duration: Duration(milliseconds: 600), onDebounce: () { isFashe = false; setState(() {}); }, ); }, ), ), ], ), ); } ///发射动画 void startFashe() { startTimer(); _fasheController.reset(); _fasheController.forward(); int now = DateTime.now().millisecondsSinceEpoch; if (now - startFasheTime < 150) { return; } startFasheTime = now; isFashe = true; _startA1(); } void fasheComlete() { widget.call(); } void startTimer() { timer?.cancel(); timer = Timer(Duration(seconds: 5), () { SmartDialog.dismiss(tag: "showGiveRoomLuckPage"); }); } void _startA1() { // 确保上下文有效 final Offset? sourceCenter = _centerFromKey(_sourceKey1); final Offset? targetCenter = _centerFromKey(_targetKey1); if (sourceCenter == null || targetCenter == null) { return; } // 计算图片中心点偏移(使图片中心对准控件中心) final double imageOffsetX = 55.w / 2; final double imageOffsetY = 55.w / 2; final AnimationController controller = AnimationController( vsync: this, duration: Duration(milliseconds: 1100), ); _runningControllers.add(controller); OverlayEntry? overlayEntry; controller.addStatusListener((status) { if (status == AnimationStatus.completed) { if (overlayEntry != null) { _removeOverlay(overlayEntry); overlayEntry = null; bool hasUser = false; for (var uid in widget.acceptUserIds) { GlobalKey>? targetKey = Provider.of( context, listen: false, ).getSeatGlobalKeyByIndex(uid); if (targetKey?.currentContext != null) { hasUser = true; } } fasheComlete(); ///麦上都没人 if (!hasUser) { return; } _startA2(); } _disposeTransientController(controller); } }); // 创建位置动画:从起始位置到目标位置 final Animation positionAnimationX1 = Tween( begin: Offset( sourceCenter.dx - imageOffsetX, sourceCenter.dy - imageOffsetY, ), end: Offset( targetCenter.dx - imageOffsetX, targetCenter.dy - imageOffsetY, ), ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.2, 0.4, curve: Curves.easeInOut), ), ); // 创建缩放动画 final Animation scaleAnimationX1 = Tween( begin: 0.5, end: 1.1, // 可以根据需要调整 ).animate( CurvedAnimation( parent: controller, curve: const Interval(0, 0.2, curve: Curves.easeInOut), ), ); // 创建缩放动画 final Animation scaleAnimationX2 = Tween( begin: 1, end: 1.5, // 可以根据需要调整 ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.4, 0.7, curve: Curves.easeInOut), ), ); // 创建浮动控件 overlayEntry = OverlayEntry( builder: (context) => AnimatedBuilder( animation: controller, child: RepaintBoundary( child: netImage( url: widget.checkedGift.giftPhoto ?? "", width: 55.w, height: 55.w, ), ), builder: (context, child) { return Positioned( left: positionAnimationX1.value.dx, top: positionAnimationX1.value.dy, child: Transform.scale( scale: scaleAnimationX1.value, child: Transform.scale( scale: scaleAnimationX2.value, child: child, ), ), ); }, ), ); // 插入Overlay并启动动画 if (!_insertOverlay(overlayEntry!)) { _disposeTransientController(controller); return; } controller.forward(from: 0); } void _startA2() { // 确保上下文有效 final Offset? sourceCenter = _centerFromKey(_targetKey1); if (sourceCenter == null) return; final RtcProvider rtc = Provider.of(context, listen: false); final List targetUserIds = _pickTargetUserIds(widget.acceptUserIds); for (var userId in targetUserIds) { final GlobalKey>? targetKey = rtc .getSeatGlobalKeyByIndex(userId); final Offset? targetCenter = targetKey == null ? null : _centerFromKey(targetKey); if (targetCenter == null) { continue; } _pendingFanOutTasks.add( _LuckFanOutTask( sourceCenter: sourceCenter, targetCenter: targetCenter, giftUrl: widget.checkedGift.giftPhoto ?? "", ), ); } _drainFanOutQueue(); } void _drainFanOutQueue() { while (mounted && _activeOverlays.length < _maxOverlayAnimations && _pendingFanOutTasks.isNotEmpty) { _startFanOutTask(_pendingFanOutTasks.removeFirst()); } } void _startFanOutTask(_LuckFanOutTask task) { final double imageOffsetX = 55.w / 2; final double imageOffsetY = 55.w / 2; final AnimationController controller = AnimationController( vsync: this, duration: Duration(milliseconds: 1100), ); _runningControllers.add(controller); OverlayEntry? overlayEntry; controller.addStatusListener((status) { if (status == AnimationStatus.completed) { if (overlayEntry != null) { _removeOverlay(overlayEntry); overlayEntry = null; } _disposeTransientController(controller); _drainFanOutQueue(); } }); // 创建位置动画:从起始位置到目标位置 final Animation positionAnimationX1 = Tween( begin: Offset( task.sourceCenter.dx - imageOffsetX, task.sourceCenter.dy - imageOffsetY, ), end: Offset( task.targetCenter.dx - imageOffsetX, task.targetCenter.dy - imageOffsetY, ), ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.3, 0.7, curve: Curves.easeInOut), ), ); // 创建缩放动画 final Animation scaleAnimationX1 = Tween( begin: 1.1, end: 0.7, // 可以根据需要调整 ).animate( CurvedAnimation( parent: controller, curve: const Interval(0.7, 0.8, curve: Curves.easeInOut), ), ); // 创建浮动控件 overlayEntry = OverlayEntry( builder: (context) => AnimatedBuilder( animation: controller, child: RepaintBoundary( child: netImage(url: task.giftUrl, width: 55.w, height: 55.w), ), builder: (context, child) { return Positioned( left: positionAnimationX1.value.dx, top: positionAnimationX1.value.dy, child: Transform.scale( scale: scaleAnimationX1.value, child: child, ), ); }, ), ); // 插入Overlay并启动动画 if (!_insertOverlay(overlayEntry!)) { _disposeTransientController(controller); _drainFanOutQueue(); return; } controller.forward(from: 0); } void _disposeTransientEffects() { for (final OverlayEntry entry in _activeOverlays.toList()) { entry.remove(); } _activeOverlays.clear(); for (final AnimationController controller in _runningControllers.toList()) { controller.dispose(); } _runningControllers.clear(); _pendingFanOutTasks.clear(); } void _removeOverlay(OverlayEntry? entry) { if (entry == null) { return; } if (_activeOverlays.remove(entry)) { entry.remove(); } } void _disposeTransientController(AnimationController controller) { if (_runningControllers.remove(controller)) { controller.dispose(); } } bool _insertOverlay(OverlayEntry entry) { if (!mounted || _activeOverlays.length >= _maxOverlayAnimations) { return false; } final OverlayState? overlayState = Overlay.of(context, rootOverlay: true); if (overlayState == null) { return false; } overlayState.insert(entry); _activeOverlays.add(entry); return true; } Offset? _centerFromKey(GlobalKey key) { final BuildContext? keyContext = key.currentContext; if (keyContext == null) { return null; } final RenderObject? renderObject = keyContext.findRenderObject(); if (renderObject is! RenderBox) { return null; } final Offset position = renderObject.localToGlobal(Offset.zero); final Size size = renderObject.size; return Offset(position.dx + size.width / 2, position.dy + size.height / 2); } List _pickTargetUserIds(List userIds) { final List uniqueUserIds = []; final Set duplicateFilter = {}; for (final String userId in userIds) { if (userId.isEmpty || !duplicateFilter.add(userId)) { continue; } uniqueUserIds.add(userId); } return uniqueUserIds; } void initFasheAnim() { _fasheController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); // 使用TweenSequence来定义先缩小后放大的动画 _fasheAnimation = TweenSequence([ TweenSequenceItem( tween: Tween(begin: 1.0, end: 1.1), // 缩小到0.5 weight: 1, // 权重,这里两个阶段各占一半 ), TweenSequenceItem( tween: Tween(begin: 1.1, end: 1.0), // 放大到1.0 weight: 1, ), ]).animate(_fasheController); } // void initScanAnim() { // _giftController = AnimationController( // duration: const Duration(milliseconds: 600), // vsync: this, // )..addStatusListener((status) { // if (status == AnimationStatus.completed) { // isShowGiftIcon1 = false; // setState(() {}); // } // }); // // // 创建缩放动画 // _scalegiftAnimation1 = Tween( // begin: 1, // end: 1.2, // 可以根据需要调整 // ).animate( // CurvedAnimation( // parent: _giftController, // curve: const Interval(0, 0.4, curve: Curves.easeInOut), // ), // ); // _scalegiftAnimation2 = Tween( // begin: 1.2, // end: 1, // 可以根据需要调整 // ).animate( // CurvedAnimation( // parent: _giftController, // curve: const Interval(0.4, 0.8, curve: Curves.easeInOut), // ), // ); // } } class _LuckFanOutTask { final Offset sourceCenter; final Offset targetCenter; final String giftUrl; const _LuckFanOutTask({ required this.sourceCenter, required this.targetCenter, required this.giftUrl, }); }