|
@@ -10,6 +10,183 @@ import '../../../../config/theme/theme_extensions/theme_extension.dart';
|
|
|
import '../../../../config/translations/strings_enum.dart';
|
|
import '../../../../config/translations/strings_enum.dart';
|
|
|
import '../../../constants/enums.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.connecting:
|
|
|
|
|
+ _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 {
|
|
class ConnectionRoundButton extends StatefulWidget {
|
|
|
final ConnectionState state;
|
|
final ConnectionState state;
|
|
|
final VoidCallback? onTap;
|
|
final VoidCallback? onTap;
|
|
@@ -118,7 +295,7 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
void _startConnectingTimer() {
|
|
void _startConnectingTimer() {
|
|
|
_connectingTimer?.cancel();
|
|
_connectingTimer?.cancel();
|
|
|
_connectingTextIndex = -1;
|
|
_connectingTextIndex = -1;
|
|
|
- _connectingTimer = Timer.periodic(const Duration(seconds: 4), (timer) {
|
|
|
|
|
|
|
+ _connectingTimer = Timer.periodic(const Duration(seconds: 5), (timer) {
|
|
|
if (mounted) {
|
|
if (mounted) {
|
|
|
setState(() {
|
|
setState(() {
|
|
|
// 每秒切换到下一个文本,循环显示0-4
|
|
// 每秒切换到下一个文本,循环显示0-4
|
|
@@ -153,31 +330,74 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 构建旋转的圆环
|
|
|
|
|
- Widget _buildRoundRing(String svgPath, bool shouldRotate) {
|
|
|
|
|
- final ringWidget = IXImage(
|
|
|
|
|
- key: ValueKey('ring_$svgPath'),
|
|
|
|
|
- source: svgPath,
|
|
|
|
|
- sourceType: ImageSourceType.asset,
|
|
|
|
|
- width: 170.w,
|
|
|
|
|
- height: 170.w,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // 获取圆环主色调
|
|
|
|
|
+ Color _getRingPrimaryColor(ConnectionState state) {
|
|
|
|
|
+ switch (state) {
|
|
|
|
|
+ case ConnectionState.disconnected:
|
|
|
|
|
+ case ConnectionState.error:
|
|
|
|
|
+ return Get.reactiveTheme.hintColor.withValues(alpha: 0.5);
|
|
|
|
|
+ case ConnectionState.connecting:
|
|
|
|
|
+ return const Color(0xFF4FC3F7); // 浅蓝色
|
|
|
|
|
+ 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.connecting:
|
|
|
|
|
+ return const Color(0xFF7E57C2); // 紫色
|
|
|
|
|
+ case ConnectionState.connected:
|
|
|
|
|
+ return const Color(0xFF26C6DA); // 青色
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建圆环 - 使用自定义绘制
|
|
|
|
|
+ Widget _buildRoundRing(ConnectionState state, bool shouldRotate) {
|
|
|
|
|
+ final primaryColor = _getRingPrimaryColor(state);
|
|
|
|
|
+ final secondaryColor = _getRingSecondaryColor(state);
|
|
|
|
|
|
|
|
if (shouldRotate) {
|
|
if (shouldRotate) {
|
|
|
return AnimatedBuilder(
|
|
return AnimatedBuilder(
|
|
|
- key: const ValueKey('rotating_ring'),
|
|
|
|
|
|
|
+ key: ValueKey('rotating_ring_$state'),
|
|
|
animation: _rotationController,
|
|
animation: _rotationController,
|
|
|
builder: (context, child) {
|
|
builder: (context, child) {
|
|
|
- return Transform.rotate(
|
|
|
|
|
- angle: _rotationController.value * 2 * math.pi,
|
|
|
|
|
- child: 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,
|
|
|
|
|
+ ),
|
|
|
);
|
|
);
|
|
|
},
|
|
},
|
|
|
- child: ringWidget,
|
|
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return ringWidget;
|
|
|
|
|
|
|
+ return TweenAnimationBuilder<double>(
|
|
|
|
|
+ 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,
|
|
|
|
|
+ ),
|
|
|
|
|
+ );
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 构建电源图标
|
|
// 构建电源图标
|
|
@@ -198,7 +418,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
|
|
|
|
|
Widget _buildMainButton() {
|
|
Widget _buildMainButton() {
|
|
|
// 根据状态获取对应的资源和样式
|
|
// 根据状态获取对应的资源和样式
|
|
|
- String ringPath;
|
|
|
|
|
String statusImgPath;
|
|
String statusImgPath;
|
|
|
String text;
|
|
String text;
|
|
|
Color textColor;
|
|
Color textColor;
|
|
@@ -207,7 +426,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
|
|
|
|
|
switch (widget.state) {
|
|
switch (widget.state) {
|
|
|
case ConnectionState.disconnected:
|
|
case ConnectionState.disconnected:
|
|
|
- ringPath = Assets.disconnectedRound;
|
|
|
|
|
statusImgPath = Assets.disconnected;
|
|
statusImgPath = Assets.disconnected;
|
|
|
text = Strings.disconnected.tr;
|
|
text = Strings.disconnected.tr;
|
|
|
textColor = Get.reactiveTheme.hintColor;
|
|
textColor = Get.reactiveTheme.hintColor;
|
|
@@ -215,7 +433,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
shouldRotate = false;
|
|
shouldRotate = false;
|
|
|
break;
|
|
break;
|
|
|
case ConnectionState.connecting:
|
|
case ConnectionState.connecting:
|
|
|
- ringPath = Assets.connectingRound;
|
|
|
|
|
statusImgPath = Assets.connecting;
|
|
statusImgPath = Assets.connecting;
|
|
|
text = _getConnectingText(); // 使用轮播文本
|
|
text = _getConnectingText(); // 使用轮播文本
|
|
|
textColor = Get.reactiveTheme.hintColor;
|
|
textColor = Get.reactiveTheme.hintColor;
|
|
@@ -223,7 +440,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
shouldRotate = true;
|
|
shouldRotate = true;
|
|
|
break;
|
|
break;
|
|
|
case ConnectionState.connected:
|
|
case ConnectionState.connected:
|
|
|
- ringPath = Assets.connectedRound;
|
|
|
|
|
statusImgPath = Assets.connected;
|
|
statusImgPath = Assets.connected;
|
|
|
text = Strings.connected.tr;
|
|
text = Strings.connected.tr;
|
|
|
textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
|
|
textColor = Get.reactiveTheme.textTheme.bodyLarge!.color!;
|
|
@@ -231,7 +447,6 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
shouldRotate = false;
|
|
shouldRotate = false;
|
|
|
break;
|
|
break;
|
|
|
case ConnectionState.error:
|
|
case ConnectionState.error:
|
|
|
- ringPath = Assets.disconnectedRound;
|
|
|
|
|
statusImgPath = Assets.error;
|
|
statusImgPath = Assets.error;
|
|
|
text = Strings.error.tr;
|
|
text = Strings.error.tr;
|
|
|
textColor = Get.reactiveTheme.hintColor;
|
|
textColor = Get.reactiveTheme.hintColor;
|
|
@@ -282,7 +497,7 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
],
|
|
],
|
|
|
);
|
|
);
|
|
|
},
|
|
},
|
|
|
- child: _buildRoundRing(ringPath, shouldRotate),
|
|
|
|
|
|
|
+ child: _buildRoundRing(widget.state, shouldRotate),
|
|
|
),
|
|
),
|
|
|
// 电源图标 - 纯淡入淡出
|
|
// 电源图标 - 纯淡入淡出
|
|
|
AnimatedSwitcher(
|
|
AnimatedSwitcher(
|
|
@@ -339,29 +554,32 @@ class _ConnectionRoundButtonState extends State<ConnectionRoundButton>
|
|
|
),
|
|
),
|
|
|
);
|
|
);
|
|
|
},
|
|
},
|
|
|
- child: Row(
|
|
|
|
|
|
|
+ child: SizedBox(
|
|
|
key: ValueKey('status_$text'), // 使用文本作为 key,确保文字改变时触发动画
|
|
key: ValueKey('status_$text'), // 使用文本作为 key,确保文字改变时触发动画
|
|
|
- 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,
|
|
|
|
|
|
|
+ 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,
|
|
|
|
|
+ ),
|
|
|
|
|
+ ),
|
|
|
|
|
+ ],
|
|
|
|
|
+ ),
|
|
|
),
|
|
),
|
|
|
),
|
|
),
|
|
|
],
|
|
],
|