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 RingPainter extends CustomPainter { final ConnectionState state; final double rotationAngle; final double glowIntensity; final Color primaryColor; final Color secondaryColor; RingPainter({ required this.state, this.rotationAngle = 0, this.glowIntensity = 0.5, required this.primaryColor, required this.secondaryColor, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 8; final strokeWidth = 2.5; // 根据状态选择绘制方式 switch (state) { case ConnectionState.disconnected: case ConnectionState.error: _drawDashedRing(canvas, center, radius, strokeWidth); break; case ConnectionState.connectingVirtual: case ConnectionState.connecting: case ConnectionState.disconnecting: _drawGradientRing(canvas, center, radius, strokeWidth, true); break; case ConnectionState.connected: _drawGradientRing(canvas, center, radius, strokeWidth, false); _drawGlowEffect(canvas, center, radius); break; } } /// 绘制虚线圆环(断开/错误状态) void _drawDashedRing( Canvas canvas, Offset center, double radius, double strokeWidth, ) { final paint = Paint() ..color = secondaryColor.withValues(alpha: 0.4) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round ..isAntiAlias = true; const int dashCount = 60; const double dashRatio = 0.5; // 虚线占比 final double dashAngle = (2 * math.pi / dashCount) * dashRatio; final double gapAngle = (2 * math.pi / dashCount) * (1 - dashRatio); for (int i = 0; i < dashCount; i++) { final startAngle = i * (dashAngle + gapAngle) - math.pi / 2; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, dashAngle, false, paint, ); } } /// 绘制渐变圆环(连接中/已连接状态) void _drawGradientRing( Canvas canvas, Offset center, double radius, double strokeWidth, bool isAnimating, ) { final rect = Rect.fromCircle(center: center, radius: radius); if (isAnimating) { // 连接中状态:绘制平滑的流光尾巴效果 final gradient = SweepGradient( startAngle: 0, endAngle: 2 * math.pi, colors: [ primaryColor.withValues(alpha: 0.0), primaryColor.withValues(alpha: 0.05), primaryColor.withValues(alpha: 0.2), primaryColor.withValues(alpha: 0.5), primaryColor.withValues(alpha: 0.8), primaryColor, secondaryColor, secondaryColor.withValues(alpha: 0.8), secondaryColor.withValues(alpha: 0.4), secondaryColor.withValues(alpha: 0.1), primaryColor.withValues(alpha: 0.0), ], stops: const [ 0.0, 0.1, 0.2, 0.35, 0.45, 0.5, 0.55, 0.65, 0.8, 0.9, 1.0, ], transform: GradientRotation(rotationAngle - math.pi / 2), ); final paint = Paint() ..shader = gradient.createShader(rect) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round ..isAntiAlias = true; // 绘制完整圆环,渐变本身形成流光效果 canvas.drawCircle(center, radius, paint); } else { // 已连接状态:绘制平滑渐变圆环 final gradient = SweepGradient( startAngle: 0, endAngle: 2 * math.pi, colors: [ primaryColor, primaryColor.withValues(alpha: 0.9), secondaryColor.withValues(alpha: 0.9), secondaryColor, secondaryColor, secondaryColor.withValues(alpha: 0.9), primaryColor.withValues(alpha: 0.9), primaryColor, ], stops: const [0.0, 0.15, 0.35, 0.5, 0.5, 0.65, 0.85, 1.0], transform: GradientRotation(rotationAngle), ); final paint = Paint() ..shader = gradient.createShader(rect) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth ..strokeCap = StrokeCap.round ..isAntiAlias = true; canvas.drawCircle(center, radius, paint); } } /// 绘制发光效果(已连接状态) void _drawGlowEffect(Canvas canvas, Offset center, double radius) { // 外层发光 - 使用更多层次和更柔和的模糊 for (int i = 4; i > 0; i--) { final glowPaint = Paint() ..color = primaryColor.withValues(alpha: glowIntensity * 0.12 / i) ..style = PaintingStyle.stroke ..strokeWidth = 2.0 + i * 2.5 ..maskFilter = MaskFilter.blur(BlurStyle.normal, i * 1.5) ..isAntiAlias = true; canvas.drawCircle(center, radius, glowPaint); } } @override bool shouldRepaint(covariant RingPainter oldDelegate) { return oldDelegate.state != state || oldDelegate.rotationAngle != rotationAngle || oldDelegate.glowIntensity != glowIntensity || oldDelegate.primaryColor != primaryColor || oldDelegate.secondaryColor != secondaryColor; } } class ConnectionRoundButton extends StatefulWidget { final ConnectionState state; final VoidCallback? onTap; const ConnectionRoundButton({super.key, required this.state, this.onTap}); @override State createState() => _ConnectionRoundButtonState(); } class _ConnectionRoundButtonState extends State with TickerProviderStateMixin { late AnimationController _rotationController; // 旋转动画控制器 late AnimationController _fadeController; // 淡入淡出控制器 Timer? _connectingTimer; // 连接中状态的计时器 int _connectingTextIndex = 0; // 当前显示的连接文本索引(0-4) ConnectionState? _previousState; // 保存前一个连接状态 bool _isStoppingRotation = false; // 是否正在停止旋转 @override void initState() { super.initState(); // 初始化旋转动画控制器 _rotationController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); // 初始化淡入淡出控制器 _fadeController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, value: 1.0, ); // 如果初始状态是 connectingVirtual/connecting 或 disconnecting,启动动画 if (widget.state == ConnectionState.connectingVirtual || widget.state == ConnectionState.connecting || widget.state == ConnectionState.disconnecting) { _rotationController.repeat(); if (widget.state == ConnectionState.connectingVirtual || widget.state == ConnectionState.connecting) { _startConnectingTimer(); } } } @override void didUpdateWidget(ConnectionRoundButton oldWidget) { super.didUpdateWidget(oldWidget); // 处理状态变化 if (oldWidget.state != widget.state) { _handleStateChange(oldWidget.state); } } void _handleStateChange(ConnectionState oldState) { if (widget.state == ConnectionState.connectingVirtual || widget.state == ConnectionState.connecting || widget.state == ConnectionState.disconnecting) { _isStoppingRotation = false; if (!_rotationController.isAnimating) { _rotationController.repeat(); } if (widget.state == ConnectionState.connectingVirtual || widget.state == ConnectionState.connecting) { if (_connectingTimer == null || !_connectingTimer!.isActive) { _startConnectingTimer(); } } else { _stopConnectingTimer(); } } else { // 从连接中/断开中切换到其他状态时,平滑停止旋转 if (_rotationController.isAnimating && !_isStoppingRotation) { _isStoppingRotation = true; // 计算剩余角度,让动画平滑停止在顶部(0度位置) final currentValue = _rotationController.value; // 停止重复,然后平滑减速到完整的一圈 _rotationController.stop(); _rotationController .animateTo( 1.0, duration: Duration( milliseconds: ((1.0 - currentValue) * 400).toInt().clamp( 100, 400, ), ), curve: Curves.easeOutCubic, ) .then((_) { if (mounted) { _rotationController.reset(); _isStoppingRotation = false; } }); } _stopConnectingTimer(); } } @override void dispose() { _rotationController.dispose(); _fadeController.dispose(); _connectingTimer?.cancel(); super.dispose(); } void _onTap() { if (widget.onTap != null) { widget.onTap!(); } } // 启动连接中状态的文本轮播计时器 void _startConnectingTimer() { _connectingTimer?.cancel(); _connectingTextIndex = -1; _connectingTimer = Timer.periodic(const Duration(seconds: 5), (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; } } // 获取圆环主色调 Color _getRingPrimaryColor(ConnectionState state) { switch (state) { case ConnectionState.disconnected: case ConnectionState.error: return Get.reactiveTheme.hintColor.withValues(alpha: 0.5); case ConnectionState.connectingVirtual: case ConnectionState.connecting: return const Color(0xFF4FC3F7); // 浅蓝色 case ConnectionState.disconnecting: return const Color(0xFFFF7043); // 橙色 case ConnectionState.connected: return const Color(0xFF66BB6A); // 绿色 } } // 获取圆环副色调 Color _getRingSecondaryColor(ConnectionState state) { switch (state) { case ConnectionState.disconnected: case ConnectionState.error: return Get.reactiveTheme.hintColor.withValues(alpha: 0.3); case ConnectionState.connectingVirtual: case ConnectionState.connecting: return const Color(0xFF7E57C2); // 紫色 case ConnectionState.disconnecting: return const Color(0xFFFFCA28); // 黄色 case ConnectionState.connected: return const Color(0xFF26C6DA); // 青色 } } // 构建圆环 - 使用自定义绘制 Widget _buildRoundRing(ConnectionState state, bool shouldRotate) { final primaryColor = _getRingPrimaryColor(state); final secondaryColor = _getRingSecondaryColor(state); if (shouldRotate) { return AnimatedBuilder( key: ValueKey('rotating_ring_$state'), animation: _rotationController, builder: (context, child) { return CustomPaint( size: Size(170.w, 170.w), painter: RingPainter( state: state, rotationAngle: _rotationController.value * 2 * math.pi, glowIntensity: 0.5, primaryColor: primaryColor, secondaryColor: secondaryColor, ), ); }, ); } return TweenAnimationBuilder( key: ValueKey('static_ring_$state'), tween: Tween(begin: 0.0, end: 1.0), duration: const Duration(milliseconds: 800), curve: Curves.easeOutCubic, builder: (context, value, child) { return CustomPaint( size: Size(170.w, 170.w), painter: RingPainter( state: state, rotationAngle: 0, glowIntensity: state == ConnectionState.connected ? value : 0.3, primaryColor: primaryColor, secondaryColor: secondaryColor, ), ); }, ); } // 根据状态和主题获取中心图片路径 String _getCenterImagePath(ConnectionState state) { final isDark = Get.isDarkMode; switch (state) { case ConnectionState.disconnected: case ConnectionState.error: return isDark ? Assets.darkDisconnected : Assets.lightDisconnected; case ConnectionState.connectingVirtual: case ConnectionState.connecting: case ConnectionState.disconnecting: return isDark ? Assets.darkDisconnected : Assets.lightDisconnected; case ConnectionState.connected: return isDark ? Assets.darkConnected : Assets.lightConnected; } } // 构建中心图片 Widget _buildCenterImage(ConnectionState state) { final imagePath = _getCenterImagePath(state); return IXImage( key: ValueKey('center_image_${state}_${Get.isDarkMode}'), source: imagePath, sourceType: ImageSourceType.asset, width: 40.w, height: 40.w, ); } @override Widget build(BuildContext context) { return GestureDetector(onTap: _onTap, child: _buildMainButton()); } Widget _buildMainButton() { // 根据状态获取对应的资源和样式 String statusImgPath; String text; Color textColor; bool shouldRotate; switch (widget.state) { case ConnectionState.disconnected: statusImgPath = Assets.disconnected; text = Strings.disconnected.tr; textColor = Get.reactiveTheme.hintColor; shouldRotate = false; break; case ConnectionState.connectingVirtual: case ConnectionState.connecting: statusImgPath = Assets.connecting; text = _getConnectingText(); // 使用轮播文本 textColor = Get.reactiveTheme.hintColor; shouldRotate = true; break; case ConnectionState.disconnecting: statusImgPath = Assets.connecting; text = Strings.disconnecting.tr; textColor = Get.reactiveTheme.hintColor; shouldRotate = true; break; case ConnectionState.connected: statusImgPath = Assets.connected; text = Strings.connected.tr; textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!; shouldRotate = false; break; case ConnectionState.error: statusImgPath = Assets.error; text = Strings.error.tr; textColor = Get.reactiveTheme.hintColor; shouldRotate = false; break; } // 更新前一个状态 if (_previousState != widget.state) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { _previousState = widget.state; } }); } return Column( mainAxisSize: MainAxisSize.min, children: [ // 圆形按钮区域 SizedBox( width: 170.w, height: 170.w, child: Stack( alignment: Alignment.center, children: [ // 圆环背景 - 使用纯淡入淡出,无缩放,更自然 AnimatedSwitcher( duration: const Duration(milliseconds: 600), switchInCurve: Curves.easeInOut, switchOutCurve: Curves.easeInOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.easeInOut, ), child: child, ); }, layoutBuilder: (currentChild, previousChildren) { return Stack( alignment: Alignment.center, children: [ ...previousChildren, if (currentChild != null) currentChild, ], ); }, child: _buildRoundRing(widget.state, shouldRotate), ), // 中心图片 - 纯淡入淡出 AnimatedSwitcher( duration: const Duration(milliseconds: 500), switchInCurve: Curves.easeInOut, switchOutCurve: Curves.easeInOut, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.easeInOut, ), child: child, ); }, layoutBuilder: (currentChild, previousChildren) { return Stack( alignment: Alignment.center, children: [ ...previousChildren, if (currentChild != null) currentChild, ], ); }, child: _buildCenterImage(widget.state), ), ], ), ), 20.verticalSpaceFromWidth, // 状态文字 AnimatedSwitcher( duration: const Duration(milliseconds: 350), switchInCurve: Curves.easeOutCubic, switchOutCurve: Curves.easeInCubic, transitionBuilder: (Widget child, Animation animation) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.easeInOut, ), child: SlideTransition( position: Tween( begin: const Offset(0, 0.15), end: Offset.zero, ).animate( CurvedAnimation( parent: animation, curve: Curves.easeOutCubic, ), ), child: child, ), ); }, child: SizedBox( key: ValueKey('status_$text'), // 使用文本作为 key,确保文字改变时触发动画 height: 20.w, child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, mainAxisSize: MainAxisSize.min, 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, ), ), ], ), ), ), ], ); } }