import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:get/get.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; class NewTopSlideNotification extends StatefulWidget { final String text; final double fontSize; final Color? textColor; final double slideOffset; final Duration duration; const NewTopSlideNotification({ super.key, required this.text, required this.fontSize, this.textColor, required this.slideOffset, required this.duration, }); static OverlayEntry? _currentEntry; static bool _isShowing = false; /// ✅ 不需要传 context static void show({ String text = '操作成功!', double fontSize = 16, Color? textColor, double slideOffset = 300.0, Duration duration = const Duration(seconds: 2), }) { _showInternal( text: text, fontSize: fontSize, textColor: textColor, slideOffset: slideOffset, duration: duration, ); } static void _showInternal({ required String text, required double fontSize, Color? textColor, required double slideOffset, required Duration duration, }) { // 移除现有弹窗 _removeCurrentEntry(); // ✅ 自动获取全局可用的 context final context = Get.overlayContext ?? Get.context; if (context == null) { debugPrint('NewTopSlideNotification: 无法获取有效的上下文'); return; } final overlay = Overlay.of(context); if (overlay == null) { debugPrint('NewTopSlideNotification: 未找到 Overlay'); return; } final entry = OverlayEntry( builder: (_) => NewTopSlideNotification( text: text, fontSize: fontSize, textColor: textColor, slideOffset: slideOffset, duration: duration, ), ); _currentEntry = entry; _isShowing = true; overlay.insert(entry); // 自动移除 Future.delayed(duration + const Duration(milliseconds: 500), () { _removeCurrentEntry(); }); } static void _removeCurrentEntry() { if (_currentEntry != null && _isShowing) { _currentEntry!.remove(); _currentEntry = null; _isShowing = false; } } @override State createState() => _TopSlideNotificationState(); } class _TopSlideNotificationState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation; bool _isAnimating = false; @override void initState() { super.initState(); _controller = AnimationController( duration: const Duration(milliseconds: 300), vsync: this, ); SchedulerBinding.instance.addPostFrameCallback((_) { _startAnimation(); }); } @override void didChangeDependencies() { super.didChangeDependencies(); final screenHeight = MediaQuery.of(context).size.height; final offsetValue = widget.slideOffset / screenHeight; _animation = Tween( begin: const Offset(0, -1), end: Offset(0, offsetValue), ).animate(CurvedAnimation( parent: _controller, curve: Curves.easeOut, reverseCurve: Curves.easeIn, )); } @override void dispose() { _controller.dispose(); super.dispose(); } Color get _textColor => widget.textColor ?? Get.find().currentColor.sc2; @override Widget build(BuildContext context) { return Positioned( top: 140.rpx, left: 0, right: 0, child: SlideTransition( position: _animation, child: Material( color: stringToColor("#000000").withOpacity(0.8), child: Padding( padding: EdgeInsets.symmetric(vertical: 20.rpx), child: Center( child: Text( widget.text, textAlign: TextAlign.center, style: TextStyle( fontSize: widget.fontSize, color: _textColor, ), ), ), ), ), ), ); } Future _startAnimation() async { if (_isAnimating) return; _isAnimating = true; try { await _controller.forward(); await Future.delayed(widget.duration); if (mounted) { await _controller.reverse(); } } finally { _isAnimating = false; } } }