chatapp3-flutter/lib/services/gift/gift_animation_manager.dart
2026-04-21 13:44:03 +08:00

280 lines
7.8 KiB
Dart

import 'dart:async';
import 'dart:collection';
import 'package:flutter/cupertino.dart';
import 'package:yumi/ui_kit/widgets/room/anim/l_gift_animal_view.dart';
class GiftAnimationManager extends ChangeNotifier {
static const int _maxPendingAnimations = 24;
static const Duration _activeComboIdleDismissDelay = Duration(
milliseconds: 3200,
);
Queue<LGiftModel> pendingAnimationsQueue = Queue<LGiftModel>();
List<LGiftScrollingScreenAnimsBean> animationControllerList = [];
//每个控件正在播放的动画
Map<int, LGiftModel?> giftMap = {0: null, 1: null, 2: null, 3: null};
bool get _controllersReady =>
animationControllerList.length >= giftMap.length;
GiftAnimationSlotSnapshot? slotSnapshotAt(int index) {
final gift = giftMap[index];
if (gift == null || animationControllerList.length <= index) {
return null;
}
final bean = animationControllerList[index];
return GiftAnimationSlotSnapshot(
index: index,
bean: bean,
sendUserName: gift.sendUserName,
sendToUserName: gift.sendToUserName,
sendUserPic: gift.sendUserPic,
giftPic: gift.giftPic,
giftName: gift.giftName,
giftCount: gift.giftCount,
rewardAmountText: gift.rewardAmountText,
rewardAmount: gift.rewardAmount,
showLuckyRewardFrame: gift.showLuckyRewardFrame,
labelId: gift.labelId,
giftCountStepUnit: gift.giftCountStepUnit,
);
}
void enqueueGiftAnimation(LGiftModel giftModel) {
if (_mergeIntoActiveAnimation(giftModel)) {
return;
}
if (_mergeIntoPendingAnimation(giftModel)) {
return;
}
_trimPendingAnimations();
pendingAnimationsQueue.add(giftModel);
proceedToNextAnimation();
}
void _trimPendingAnimations() {
while (pendingAnimationsQueue.length >= _maxPendingAnimations) {
pendingAnimationsQueue.removeFirst();
}
}
bool _mergeIntoActiveAnimation(LGiftModel incoming) {
for (final entry in giftMap.entries) {
final current = entry.value;
if (current == null || current.labelId != incoming.labelId) {
continue;
}
_mergeGiftModel(target: current, incoming: incoming);
notifyListeners();
_refreshSlotAnimation(entry.key, restartEntry: false);
return true;
}
return false;
}
bool _mergeIntoPendingAnimation(LGiftModel incoming) {
for (final pending in pendingAnimationsQueue) {
if (pending.labelId != incoming.labelId) {
continue;
}
_mergeGiftModel(target: pending, incoming: incoming);
return true;
}
return false;
}
void _mergeGiftModel({
required LGiftModel target,
required LGiftModel incoming,
}) {
target.giftCount = (target.giftCount) + (incoming.giftCount);
if (target.sendUserName.isEmpty) {
target.sendUserName = incoming.sendUserName;
}
if (target.sendToUserName.isEmpty) {
target.sendToUserName = incoming.sendToUserName;
}
if (target.sendUserPic.isEmpty) {
target.sendUserPic = incoming.sendUserPic;
}
if (target.giftPic.isEmpty) {
target.giftPic = incoming.giftPic;
}
if (target.giftName.isEmpty) {
target.giftName = incoming.giftName;
}
target.rewardAmount = target.rewardAmount + incoming.rewardAmount;
if (incoming.rewardAmountText.isNotEmpty) {
target.rewardAmountText = incoming.rewardAmountText;
}
target.showLuckyRewardFrame =
target.showLuckyRewardFrame || incoming.showLuckyRewardFrame;
if (incoming.giftCountStepUnit > 0) {
target.giftCountStepUnit = incoming.giftCountStepUnit;
}
}
///开始播放
proceedToNextAnimation() {
if (pendingAnimationsQueue.isEmpty || !_controllersReady) {
return;
}
var playGift = pendingAnimationsQueue.first;
for (var key in giftMap.keys) {
var value = giftMap[key];
if (value == null) {
giftMap[key] = playGift;
pendingAnimationsQueue.removeFirst();
notifyListeners();
_refreshSlotAnimation(key, restartEntry: true);
break;
} else {
if (value.labelId == playGift.labelId) {
_mergeGiftModel(target: value, incoming: playGift);
pendingAnimationsQueue.removeFirst();
notifyListeners();
_refreshSlotAnimation(key, restartEntry: false);
break;
}
}
}
}
void attachAnimationControllers(List<LGiftScrollingScreenAnimsBean> anins) {
animationControllerList = anins;
proceedToNextAnimation();
}
void cleanupAnimationResources() {
pendingAnimationsQueue.clear();
giftMap[0] = null;
giftMap[1] = null;
giftMap[2] = null;
giftMap[3] = null;
for (var element in animationControllerList) {
element.dismissTimer?.cancel();
element.controller.dispose();
element.countPulseController.dispose();
}
animationControllerList.clear();
}
void markAnimationAsFinished(int index) {
_cancelSlotDismissTimer(index);
if (giftMap[index] == null) {
return;
}
giftMap[index] = null;
notifyListeners();
proceedToNextAnimation();
}
void _refreshSlotAnimation(int index, {required bool restartEntry}) {
if (animationControllerList.length <= index) {
return;
}
final bean = animationControllerList[index];
_restartSlotDismissTimer(index);
bean.countPulseController.forward(from: 0);
if (restartEntry) {
bean.controller.forward(from: 0);
}
}
void _restartSlotDismissTimer(int index) {
if (animationControllerList.length <= index) {
return;
}
final bean = animationControllerList[index];
bean.dismissTimer?.cancel();
bean.dismissTimer = Timer(_activeComboIdleDismissDelay, () {
if (giftMap[index] == null) {
return;
}
markAnimationAsFinished(index);
});
}
void _cancelSlotDismissTimer(int index) {
if (animationControllerList.length <= index) {
return;
}
animationControllerList[index].dismissTimer?.cancel();
animationControllerList[index].dismissTimer = null;
}
}
@immutable
class GiftAnimationSlotSnapshot {
const GiftAnimationSlotSnapshot({
required this.index,
required this.bean,
required this.sendUserName,
required this.sendToUserName,
required this.sendUserPic,
required this.giftPic,
required this.giftName,
required this.giftCount,
required this.rewardAmountText,
required this.rewardAmount,
required this.showLuckyRewardFrame,
required this.labelId,
required this.giftCountStepUnit,
});
final int index;
final LGiftScrollingScreenAnimsBean bean;
final String sendUserName;
final String sendToUserName;
final String sendUserPic;
final String giftPic;
final String giftName;
final num giftCount;
final String rewardAmountText;
final num rewardAmount;
final bool showLuckyRewardFrame;
final String labelId;
final num giftCountStepUnit;
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
return other is GiftAnimationSlotSnapshot &&
other.index == index &&
identical(other.bean, bean) &&
other.sendUserName == sendUserName &&
other.sendToUserName == sendToUserName &&
other.sendUserPic == sendUserPic &&
other.giftPic == giftPic &&
other.giftName == giftName &&
other.giftCount == giftCount &&
other.rewardAmountText == rewardAmountText &&
other.rewardAmount == rewardAmount &&
other.showLuckyRewardFrame == showLuckyRewardFrame &&
other.labelId == labelId &&
other.giftCountStepUnit == giftCountStepUnit;
}
@override
int get hashCode => Object.hash(
index,
bean,
sendUserName,
sendToUserName,
sendUserPic,
giftPic,
giftName,
giftCount,
rewardAmountText,
rewardAmount,
showLuckyRewardFrame,
labelId,
giftCountStepUnit,
);
}