aslan-flutter/lib/chatvibe_ui/widgets/room/give_room_luck_page.dart
2026-07-01 18:25:58 +08:00

550 lines
16 KiB
Dart

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<String> acceptUserIds = [];
Function() call;
GiveRoomLuckPage(this.checkedGift, this.acceptUserIds, this.call);
@override
_GiveRoomLuckPageState createState() => _GiveRoomLuckPageState();
}
class _GiveRoomLuckPageState extends State<GiveRoomLuckPage>
with TickerProviderStateMixin {
static const int _maxOverlayAnimations = 10;
var debouncer = Debouncer();
final Set<AnimationController> _runningControllers = <AnimationController>{};
final Set<OverlayEntry> _activeOverlays = <OverlayEntry>{};
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<double> _fasheAnimation;
Timer? timer;
int startFasheTime = 0;
@override
void initState() {
super.initState();
Provider.of<GiftProvider>(
navigatorKey.currentContext!,
listen: false,
).reset();
Provider.of<GiftProvider>(
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<State<StatefulWidget>>? targetKey =
Provider.of<RtcProvider>(
context,
listen: false,
).getSeatGlobalKeyByIndex(uid);
if (targetKey?.currentContext != null) {
hasUser = true;
}
}
fasheComlete();
///麦上都没人
if (!hasUser) {
return;
}
_startA2();
}
_disposeTransientController(controller);
}
});
// 创建位置动画:从起始位置到目标位置
final Animation<Offset> positionAnimationX1 = Tween<Offset>(
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<double> scaleAnimationX1 = Tween<double>(
begin: 0.5,
end: 1.1, // 可以根据需要调整
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(0, 0.2, curve: Curves.easeInOut),
),
);
// 创建缩放动画
final Animation<double> scaleAnimationX2 = Tween<double>(
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<RtcProvider>(context, listen: false);
final List<String> targetUserIds = _pickTargetUserIds(widget.acceptUserIds);
for (var userId in targetUserIds) {
final GlobalKey<State<StatefulWidget>>? 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<Offset> positionAnimationX1 = Tween<Offset>(
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<double> scaleAnimationX1 = Tween<double>(
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<String> _pickTargetUserIds(List<String> userIds) {
final List<String> uniqueUserIds = <String>[];
final Set<String> duplicateFilter = <String>{};
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<double>([
TweenSequenceItem(
tween: Tween<double>(begin: 1.0, end: 1.1), // 缩小到0.5
weight: 1, // 权重,这里两个阶段各占一半
),
TweenSequenceItem(
tween: Tween<double>(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<double>(
// begin: 1,
// end: 1.2, // 可以根据需要调整
// ).animate(
// CurvedAnimation(
// parent: _giftController,
// curve: const Interval(0, 0.4, curve: Curves.easeInOut),
// ),
// );
// _scalegiftAnimation2 = Tween<double>(
// 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,
});
}