import 'package:flutter/material.dart' hide ConnectionState; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'dart:math' as math; import 'dart:async'; import 'package:get/get.dart'; import 'package:nomo/app/widgets/ix_image.dart'; import '../../../constants/assets.dart'; import '../../../../config/theme/theme_extensions/theme_extension.dart'; import '../../../../config/translations/strings_enum.dart'; import '../../../constants/enums.dart'; class ConnectionButton extends StatefulWidget { final ConnectionState state; final VoidCallback? onTap; const ConnectionButton({super.key, required this.state, this.onTap}); @override State createState() => _ConnectionButtonState(); } class _ConnectionButtonState extends State with TickerProviderStateMixin { bool _isAtTop = false; // 控制按钮位置,false=底部,true=顶部 List? _previousGradientColor; // 保存前一个渐变色用于动画过渡 late AnimationController _rotationController; // 旋转动画控制器 Timer? _connectingTimer; // 连接中状态的计时器 int _connectingTextIndex = 0; // 当前显示的连接文本索引(0-4) ConnectionState? _previousState; // 保存前一个连接状态 @override void initState() { super.initState(); // 初始化旋转动画控制器 _rotationController = AnimationController( duration: const Duration(seconds: 2), vsync: this, ); } @override void dispose() { _rotationController.dispose(); _connectingTimer?.cancel(); super.dispose(); } void _onTap() { if (widget.onTap != null) { widget.onTap!(); } } // 启动连接中状态的文本轮播计时器 void _startConnectingTimer() { _connectingTimer?.cancel(); _connectingTextIndex = 0; _connectingTimer = Timer.periodic(const Duration(seconds: 1), (timer) { if (mounted) { setState(() { // 每秒切换到下一个文本,循环显示0-4 _connectingTextIndex = (_connectingTextIndex + 1) % 5; }); } }); } // 停止连接中状态的文本轮播计时器 void _stopConnectingTimer() { _connectingTimer?.cancel(); _connectingTimer = null; _connectingTextIndex = 0; } // 根据索引获取对应的连接文本 String _getConnectingText() { switch (_connectingTextIndex) { case 0: return Strings.securingData.tr; case 1: return Strings.encryptingTraffic.tr; case 2: return Strings.protectingPrivacy.tr; case 3: return Strings.safeConnection.tr; case 4: return Strings.yourDataIsSafe.tr; default: return Strings.connecting.tr; } } // 比较两个颜色列表是否相等 bool _colorsEqual(List a, List b) { if (a.length != b.length) return false; for (int i = 0; i < a.length; i++) { if (a[i].value != b[i].value) return false; } return true; } Widget _buildImageWithAnimation(String imgPath) { // 如果是 connectingVirtual/connecting 或 disconnecting 状态,添加旋转动画 if (widget.state == ConnectionState.connectingVirtual || widget.state == ConnectionState.connecting || widget.state == ConnectionState.disconnecting) { // 启动旋转动画 if (!_rotationController.isAnimating) { _rotationController.repeat(); } return AnimatedBuilder( animation: _rotationController, builder: (context, child) { return Transform.rotate( angle: _rotationController.value * 2 * math.pi, child: IXImage( source: imgPath, sourceType: ImageSourceType.asset, width: 30.w, height: 30.w, ), ); }, ); } else { // 其他状态停止旋转动画 if (_rotationController.isAnimating) { _rotationController.stop(); } return IXImage( source: imgPath, sourceType: ImageSourceType.asset, width: 30.w, height: 30.w, ); } } @override Widget build(BuildContext context) { return GestureDetector(onTap: _onTap, child: _buildMainButton()); } Widget _buildMainButton() { // 根据状态和主题获取颜色 List gradientColor; String imgPath; String statusImgPath; String text; Color textColor; switch (widget.state) { case ConnectionState.disconnected: gradientColor = [ Get.reactiveTheme.highlightColor, Get.reactiveTheme.hintColor, ]; imgPath = Assets.switchStatusDisconnected; statusImgPath = Assets.disconnected; text = Strings.disconnected.tr; textColor = Get.reactiveTheme.hintColor; // 只在状态改变时执行一次 if (_previousState != ConnectionState.disconnected && _isAtTop) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _isAtTop = false; }); } }); } _stopConnectingTimer(); // 停止计时器 break; case ConnectionState.connectingVirtual: case ConnectionState.connecting: gradientColor = [ Get.reactiveTheme.highlightColor, Get.reactiveTheme.hintColor, ]; imgPath = Assets.switchStatusConnecting; statusImgPath = Assets.connecting; text = _getConnectingText(); // 使用轮播文本 textColor = Get.reactiveTheme.hintColor; // 只在状态改变时执行一次切换位置 if (_previousState != ConnectionState.connectingVirtual && _previousState != ConnectionState.connecting) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _isAtTop = !_isAtTop; }); } }); } // 启动连接文本轮播计时器 if (_connectingTimer == null || !_connectingTimer!.isActive) { _startConnectingTimer(); } break; case ConnectionState.disconnecting: gradientColor = [ Get.reactiveTheme.highlightColor, Get.reactiveTheme.hintColor, ]; imgPath = Assets.switchStatusConnecting; statusImgPath = Assets.connecting; text = Strings.disconnecting.tr; textColor = Get.reactiveTheme.hintColor; _stopConnectingTimer(); // 停止计时器 break; case ConnectionState.connected: gradientColor = [ Get.reactiveTheme.shadowColor, Get.reactiveTheme.primaryColor, ]; imgPath = Assets.switchStatusConnected; statusImgPath = Assets.connected; text = Strings.connected.tr; textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!; // 只在状态改变时执行一次 if (_previousState != ConnectionState.connected && !_isAtTop) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { setState(() { _isAtTop = true; }); } }); } _stopConnectingTimer(); // 停止计时器 break; case ConnectionState.error: gradientColor = [ Get.reactiveTheme.highlightColor, Get.reactiveTheme.hintColor, ]; imgPath = Assets.switchStatusDisconnected; statusImgPath = Assets.error; text = Strings.error.tr; textColor = Get.reactiveTheme.hintColor; _stopConnectingTimer(); // 停止计时器 break; } // 保存前一个渐变色用于动画 final previousColor = _previousGradientColor ?? gradientColor; // 只在颜色真正改变时更新 if (_previousGradientColor == null || _previousGradientColor!.length != gradientColor.length || !_colorsEqual(_previousGradientColor!, gradientColor)) { // 延迟更新,避免在 build 中调用 setState WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _previousGradientColor = gradientColor; } }); } // 更新前一个状态 if (_previousState != widget.state) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _previousState = widget.state; } }); } return Column( children: [ TweenAnimationBuilder>( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, tween: ColorListTween(begin: previousColor, end: gradientColor), builder: (context, colors, child) { return Container( width: 72.w, height: 132.w, padding: EdgeInsets.all(6.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.r), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: colors, ), ), child: child, ); }, child: AnimatedAlign( duration: const Duration(milliseconds: 300), curve: Curves.fastOutSlowIn, alignment: _isAtTop ? Alignment.topCenter : Alignment.bottomCenter, child: Container( width: 60.w, height: 60.w, alignment: Alignment.center, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14.r), ), child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: animation, child: ScaleTransition( scale: Tween( begin: 0.85, end: 1.0, ).animate(animation), child: child, ), ); }, child: _buildImageWithAnimation(imgPath), ), ), ), ), 20.verticalSpaceFromWidth, AnimatedSwitcher( duration: const Duration(milliseconds: 400), switchInCurve: Curves.easeIn, switchOutCurve: Curves.easeOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition(opacity: animation, child: child); }, child: Row( key: ValueKey(text), // 使用文本作为 key,确保文字改变时触发动画 mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ IXImage( source: statusImgPath, sourceType: ImageSourceType.asset, width: 14.w, height: 14.w, ), 4.horizontalSpace, Text( text, style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, height: 1.4, color: textColor, ), ), ], ), ), ], ); } } // 自定义颜色列表插值器,用于渐变色动画 class ColorListTween extends Tween> { ColorListTween({super.begin, super.end}); @override List lerp(double t) { if (begin == null || end == null) { return end ?? begin ?? []; } final length = math.max(begin!.length, end!.length); return List.generate(length, (index) { final beginColor = index < begin!.length ? begin![index] : begin!.last; final endColor = index < end!.length ? end![index] : end!.last; return Color.lerp(beginColor, endColor, t) ?? endColor; }); } }