299 lines
8.7 KiB
Dart
299 lines
8.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:aslan/app_localizations.dart';
|
|
import 'package:aslan/chatvibe_ui/components/at_compontent.dart';
|
|
import 'package:aslan/chatvibe_ui/components/text/at_text.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:aslan/chatvibe_managers/rtm_manager.dart';
|
|
import 'package:aslan/chatvibe_ui/widgets/room/room_msg_item.dart';
|
|
|
|
import '../../../../chatvibe_data/models/enum/at_vip_type.dart';
|
|
|
|
class RoomAnimationQueueScreen extends StatefulWidget {
|
|
const RoomAnimationQueueScreen({super.key});
|
|
|
|
@override
|
|
State<RoomAnimationQueueScreen> createState() =>
|
|
_RoomAnimationQueueScreenState();
|
|
}
|
|
|
|
class _RoomAnimationQueueScreenState extends State<RoomAnimationQueueScreen> {
|
|
final List<AnimationTask> _animationQueue = [];
|
|
bool _isQueueProcessing = false;
|
|
final Map<int, GlobalKey<_RoomEntranceAnimationState>> _animationKeys = {};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
Provider.of<RtmProvider>(context, listen: false).msgUserJoinListener =
|
|
_msgUserJoinListener;
|
|
}
|
|
|
|
_msgUserJoinListener(Msg msg) {
|
|
Future.delayed(Duration(milliseconds: 550), () {
|
|
_addToQueue(msg);
|
|
});
|
|
}
|
|
|
|
void _addToQueue(Msg msg) {
|
|
setState(() {
|
|
final taskId = DateTime.now().millisecondsSinceEpoch;
|
|
final animationKey = GlobalKey<_RoomEntranceAnimationState>();
|
|
|
|
_animationKeys[taskId] = animationKey;
|
|
|
|
final task = AnimationTask(
|
|
id: taskId,
|
|
msg: msg,
|
|
onComplete: () {
|
|
if (_animationQueue.isNotEmpty &&
|
|
_animationQueue.first.id == taskId) {
|
|
setState(() {
|
|
_animationQueue.removeAt(0);
|
|
_animationKeys.remove(taskId);
|
|
});
|
|
|
|
if (_animationQueue.isNotEmpty) {
|
|
_startNextAnimation();
|
|
} else {
|
|
setState(() => _isQueueProcessing = false);
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
_animationQueue.add(task);
|
|
});
|
|
|
|
if (!_isQueueProcessing && _animationQueue.isNotEmpty) {
|
|
setState(() => _isQueueProcessing = true);
|
|
_startNextAnimation();
|
|
}
|
|
}
|
|
|
|
void _startNextAnimation({int retryCount = 0}) {
|
|
if (_animationQueue.isEmpty) return;
|
|
|
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
|
if (_animationQueue.isNotEmpty) {
|
|
final task = _animationQueue.first;
|
|
final key = _animationKeys[task.id];
|
|
|
|
if (key?.currentState != null) {
|
|
key!.currentState!._startAnimation();
|
|
} else if (retryCount < 3) {
|
|
// 有限次重试,避免无限循环
|
|
Future.delayed(const Duration(milliseconds: 50), () {
|
|
_startNextAnimation(retryCount: retryCount + 1);
|
|
});
|
|
} else {
|
|
// 重试多次失败后,跳过当前动画
|
|
print("动画启动失败,跳过当前任务");
|
|
task.onComplete();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void _clearQueue() {
|
|
setState(() {
|
|
_animationQueue.clear();
|
|
_animationKeys.clear();
|
|
_isQueueProcessing = false;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.all(20.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Stack(
|
|
children: [
|
|
if (_animationQueue.isNotEmpty)
|
|
RoomEntranceAnimation(
|
|
key: _animationKeys[_animationQueue.first.id],
|
|
msg: _animationQueue.first.msg,
|
|
onComplete: _animationQueue.first.onComplete,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class AnimationTask {
|
|
final int id;
|
|
final Msg msg;
|
|
final VoidCallback onComplete;
|
|
|
|
AnimationTask({
|
|
required this.id,
|
|
required this.msg,
|
|
required this.onComplete,
|
|
});
|
|
}
|
|
|
|
class RoomEntranceAnimation extends StatefulWidget {
|
|
final VoidCallback onComplete;
|
|
final Msg msg;
|
|
|
|
const RoomEntranceAnimation({
|
|
super.key,
|
|
required this.onComplete,
|
|
required this.msg,
|
|
});
|
|
|
|
@override
|
|
State<RoomEntranceAnimation> createState() => _RoomEntranceAnimationState();
|
|
}
|
|
|
|
class _RoomEntranceAnimationState extends State<RoomEntranceAnimation>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
late Animation<Offset> _offsetAnimation;
|
|
bool _isAnimating = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(
|
|
duration: const Duration(seconds: 4),
|
|
vsync: this,
|
|
);
|
|
|
|
_offsetAnimation = TweenSequence<Offset>([
|
|
TweenSequenceItem(
|
|
tween: Tween<Offset>(
|
|
begin: const Offset(1.0, 0.0),
|
|
end: const Offset(0.0, 0.0),
|
|
).chain(CurveTween(curve: Curves.easeOut)),
|
|
weight: 40.0,
|
|
),
|
|
TweenSequenceItem(
|
|
tween: ConstantTween<Offset>(const Offset(0.0, 0.0)),
|
|
weight: 20.0,
|
|
),
|
|
TweenSequenceItem(
|
|
tween: Tween<Offset>(
|
|
begin: const Offset(0.0, 0.0),
|
|
end: const Offset(-1.0, 0.0),
|
|
).chain(CurveTween(curve: Curves.easeIn)),
|
|
weight: 40.0,
|
|
),
|
|
]).animate(_controller);
|
|
|
|
_controller.addStatusListener((status) {
|
|
if (status == AnimationStatus.completed) {
|
|
setState(() => _isAnimating = false);
|
|
widget.onComplete();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _startAnimation() {
|
|
if (_isAnimating) return;
|
|
|
|
setState(() => _isAnimating = true);
|
|
_controller.reset();
|
|
_controller.forward();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return SlideTransition(
|
|
position: _offsetAnimation,
|
|
child: Container(
|
|
width: ScreenUtil().screenWidth * 0.8,
|
|
height: widget.msg.user?.getVIP() == null ? 37.w : 150.w,
|
|
decoration: BoxDecoration(
|
|
image: DecorationImage(
|
|
image: AssetImage(
|
|
getEntranceBg(widget.msg.user?.getVIP()?.name ?? ""),
|
|
),
|
|
fit: BoxFit.fill,
|
|
),
|
|
),
|
|
child: Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: [
|
|
widget.msg.user?.getVIP() == null
|
|
? Padding(
|
|
padding: EdgeInsets.all(2.w),
|
|
child: netImage(
|
|
url: widget.msg.user?.userAvatar ?? "",
|
|
width: 28.w,
|
|
height: 28.w,
|
|
shape: BoxShape.circle,
|
|
),
|
|
)
|
|
: Padding(
|
|
padding: EdgeInsets.only(left: 44.w),
|
|
child: SizedBox(width: 40.w, height: 40.w),
|
|
),
|
|
widget.msg.user?.getVIP() == null
|
|
? SizedBox(width: 5.w)
|
|
: SizedBox(width: 25.w),
|
|
Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
height:
|
|
widget.msg.user?.getVIP()?.name == ATVIPType.VISCOUNT.name
|
|
? 10.w
|
|
: 2.w,
|
|
),
|
|
chatvibeNickNameText(
|
|
maxWidth: 150.w,
|
|
widget.msg.user?.userNickname ?? "",
|
|
fontSize: 14.sp,
|
|
type: widget.msg.user?.getVIP()?.name ?? "",
|
|
needScroll:
|
|
(widget.msg.user?.userNickname?.characters.length ?? 0) >
|
|
15,
|
|
),
|
|
text(
|
|
ATAppLocalizations.of(context)!.enterTheRoom,
|
|
fontSize: 12.sp,
|
|
lineHeight: 0.9,
|
|
textColor: Colors.white,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String getEntranceBg(String type) {
|
|
String bg = "atu_images/room/entrance/at_icon_room_entrance_no_vip_bg.png";
|
|
if (type == ATVIPType.VISCOUNT.name) {
|
|
bg = "atu_images/room/entrance/at_icon_room_entrance_vip1_bg.png";
|
|
} else if (type == ATVIPType.EARL.name) {
|
|
bg = "atu_images/room/entrance/at_icon_room_entrance_vip2_bg.png";
|
|
} else if (type == ATVIPType.MARQUIS.name) {
|
|
bg = "atu_images/room/entrance/at_icon_room_entrance_vip3_bg.png";
|
|
} else if (type == ATVIPType.DUKE.name) {
|
|
bg = "atu_images/room/entrance/at_icon_room_entrance_vip4_bg.png";
|
|
} else if (type == ATVIPType.KING.name) {
|
|
bg = "atu_images/room/entrance/at_icon_room_entrance_vip5_bg.png";
|
|
} else if (type == ATVIPType.EMPEROR.name) {
|
|
bg = "atu_images/room/entrance/at_icon_room_entrance_vip6_bg.png";
|
|
}
|
|
return bg;
|
|
}
|
|
}
|