From dae5dde2def2c4b1e7345fcf6a1500369bacb53b Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sun, 20 Jul 2025 20:45:56 +0800 Subject: [PATCH] =?UTF-8?q?feat(pairing):=20=E4=BC=98=E5=8C=96=E9=85=8D?= =?UTF-8?q?=E5=AF=B9=E8=A7=86=E5=9B=BE=E5=8A=A8=E7=94=BB=E6=95=88=E6=9E=9C?= =?UTF-8?q?=E5=92=8C=E6=8E=A7=E5=88=B6=E5=99=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构配对视图动画系统,使用更高效的AnimationController实现连续脉搏和旋转效果 新增安全连接和设备关联的特殊动画效果 优化控制器检查逻辑,使用Future.wait并行执行蓝牙和权限检查 调整导航延迟从1秒缩短至500毫秒 更新颜色透明度方法从withOpacity改为withValues --- .gitignore | 1 + .../controllers/pairing_controller.dart | 29 +- lib/modules/pairing/views/pairing_view.dart | 342 +++++++++++++++--- .../pairing/views/pairing_view_new.dart | 7 +- 4 files changed, 314 insertions(+), 65 deletions(-) diff --git a/.gitignore b/.gitignore index 51e5178e8..80c4856c3 100644 --- a/.gitignore +++ b/.gitignore @@ -221,3 +221,4 @@ fastlane/test_output fastlane/readme.md .trae/rules/project_rules.md .augment/rules/imported/project_rules.md +.kiro/steering/flutter_project_standards.md diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 280f8cf58..21c6d33c8 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -384,29 +384,33 @@ class PairingController extends GetxController { /// 检查当前状态并决定下一步 void _checkCurrentStatus() async { try { - // 1. 检查蓝牙状态 - if (_bluetoothManager.bluetoothState == 'off') { + // 使用 Future.wait 并行执行检查,提升启动速度 + final results = await Future.wait([ + _checkBluetoothState(), // 检查蓝牙状态 + _checkPermissions(), // 检查权限 + _bluetoothManager.refreshDeviceConnection(), // 刷新设备连接 + ]); + + final isBluetoothOk = results[0] as bool; + final arePermissionsOk = results[1] as bool; + + if (!isBluetoothOk) { _setError(ErrorType.bluetoothOff, 'pleaseEnableBluetooth'.tr); return; } - // 2. 检查权限 - if (!await _checkPermissions()) { + if (!arePermissionsOk) { _setError(ErrorType.noPermission, 'pleaseGrantNecessaryPermissions'.tr); return; } - // 3. 先刷新设备连接状态,确保获取最新的连接信息 - await _bluetoothManager.refreshDeviceConnection(); - - // 4. 检查经典蓝牙连接 + // 后续逻辑保持不变,确保所有设备都经过验证 final classicDevice = _bluetoothManager.currentDevice; if (classicDevice == null) { _transition(PairingState.waitingClassic); return; } - // 5. 如果有设备,进行验证 _currentClassicDevice = classicDevice; _transition(PairingState.verifying); await _verifyDevice(classicDevice); @@ -416,6 +420,11 @@ class PairingController extends GetxController { } } + /// 新增一个辅助方法来检查蓝牙状态 + Future _checkBluetoothState() async { + return _bluetoothManager.bluetoothState != 'off'; + } + /// 检查权限 Future _checkPermissions() async { // Android权限检查 @@ -700,7 +709,7 @@ class PairingController extends GetxController { /// 导航到主页 void _navigateToHome() { - Future.delayed(const Duration(seconds: 1), () async { + Future.delayed(const Duration(milliseconds: 500), () async { // 启动AgentService服务 try { Logger.i(_tag, '启动AgentService服务'); diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index 1f2bf9b31..a059ab6a8 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -3,9 +3,68 @@ import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/pairing_controller.dart'; -class PairingView extends GetView { +class PairingView extends StatefulWidget { const PairingView({Key? key}) : super(key: key); + @override + State createState() => _PairingViewState(); +} + +class _PairingViewState extends State + with TickerProviderStateMixin { + late AnimationController _pulseController; + late AnimationController _rotationController; + late Animation _pulseAnimation; + late Animation _rotationAnimation; + + PairingController get controller => Get.find(); + + @override + void initState() { + super.initState(); + + // 脉搏动画控制器 + _pulseController = AnimationController( + duration: const Duration(milliseconds: 1500), + vsync: this, + ); + + // 旋转动画控制器 + _rotationController = AnimationController( + duration: const Duration(milliseconds: 2000), + vsync: this, + ); + + // 脉搏动画 + _pulseAnimation = Tween( + begin: 0.8, + end: 1.2, + ).animate(CurvedAnimation( + parent: _pulseController, + curve: Curves.easeInOut, + )); + + // 旋转动画 + _rotationAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation( + parent: _rotationController, + curve: Curves.linear, + )); + + // 开始动画 + _pulseController.repeat(reverse: true); + _rotationController.repeat(); + } + + @override + void dispose() { + _pulseController.dispose(); + _rotationController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; @@ -30,7 +89,7 @@ class PairingView extends GetView { padding: EdgeInsets.symmetric(horizontal: 24.w), // 添加SingleChildScrollView以防止溢出 child: SingleChildScrollView( - physics: BouncingScrollPhysics(), + physics: const BouncingScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( minHeight: @@ -98,8 +157,8 @@ class PairingView extends GetView { final state = controller.currentState; final isLoading = controller.isLoading.value; // 根据状态决定动画颜色 - Color primaryColor = Color(0xFF0066CC); - Color secondaryColor = Color(0xFF5E5CE6); + Color primaryColor = const Color(0xFF0066CC); + Color secondaryColor = const Color(0xFF5E5CE6); // 错误状态使用红色 if (state == PairingState.error) { @@ -114,7 +173,7 @@ class PairingView extends GetView { // 待机状态的特殊处理 if (state == PairingState.idle || state == PairingState.waitingClassic) { - return Container( + return SizedBox( width: 240.w, height: 240.w, child: Stack( @@ -129,8 +188,8 @@ class PairingView extends GetView { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - primaryColor.withOpacity(0.1), - secondaryColor.withOpacity(0.1), + primaryColor.withValues(alpha: 0.1), + secondaryColor.withValues(alpha: 0.1), ], ), shape: BoxShape.circle, @@ -159,51 +218,25 @@ class PairingView extends GetView { ); } - return Container( + return SizedBox( width: 240.w, height: 240.w, child: Stack( alignment: Alignment.center, children: [ - // 脉搏背景 - 只在加载时显示动画 - if (isLoading) - TweenAnimationBuilder( - tween: Tween(begin: 0.8, end: 1.1), - duration: const Duration(seconds: 2), - curve: Curves.easeInOut, - builder: (context, value, child) { - return Container( - width: 200.w * value, - height: 200.w * value, - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - primaryColor.withOpacity(0.3), - secondaryColor.withOpacity(0.3), - ], - ), - shape: BoxShape.circle, - ), - ); - }, - onEnd: () { - // 重新开始动画 - }, - ), + // 连续脉搏动画 - 只在加载时显示 + if (isLoading) _buildPulseAnimation(primaryColor, secondaryColor), + + // 特殊的安全连接动画 - 只在建立安全连接时显示 + if (state == PairingState.waitingBle) + _buildSecureConnectionAnimation(primaryColor, secondaryColor), + + // 设备关联动画 - 只在关联设备时显示 + if (state == PairingState.associating) + _buildAssociatingAnimation(primaryColor, secondaryColor), // 旋转边框 - 只在加载时显示 - if (isLoading) - SizedBox( - width: 100.w, - height: 100.w, - child: CircularProgressIndicator( - strokeWidth: 4.w, - valueColor: AlwaysStoppedAnimation(primaryColor), - backgroundColor: Colors.grey.withOpacity(0.2), - ), - ), + if (isLoading) _buildRotatingBorder(primaryColor), // 设备图标 Container( @@ -229,6 +262,128 @@ class PairingView extends GetView { }); } + // 构建连续脉搏动画 + Widget _buildPulseAnimation(Color primaryColor, Color secondaryColor) { + return AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + final value = _pulseAnimation.value; + final opacity = 0.4 - (_pulseController.value * 0.3); // 0.4 到 0.1 的透明度变化 + + return Container( + width: 200.w * value, + height: 200.w * value, + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + primaryColor.withValues(alpha: opacity), + secondaryColor.withValues(alpha: opacity), + ], + ), + shape: BoxShape.circle, + ), + ); + }, + ); + } + + // 构建旋转边框动画 + Widget _buildRotatingBorder(Color primaryColor) { + return AnimatedBuilder( + animation: _rotationAnimation, + builder: (context, child) { + return Transform.rotate( + angle: _rotationAnimation.value * 2 * 3.14159, // 完整旋转 + child: SizedBox( + width: 140.w, + height: 140.w, + child: CircularProgressIndicator( + strokeWidth: 3.w, + valueColor: AlwaysStoppedAnimation(primaryColor), + backgroundColor: Colors.grey.withValues(alpha: 0.2), + value: 0.7, // 显示70%的圆弧 + ), + ), + ); + }, + ); + } + + // 构建安全连接特殊动画 - 多层波纹效果 + Widget _buildSecureConnectionAnimation(Color primaryColor, Color secondaryColor) { + return Stack( + alignment: Alignment.center, + children: [ + // 第一层波纹 + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + final value = _pulseAnimation.value; + final opacity = 0.3 - (_pulseController.value * 0.2); + + return Container( + width: 180.w * value, + height: 180.w * value, + decoration: BoxDecoration( + border: Border.all( + color: primaryColor.withValues(alpha: opacity), + width: 2.w, + ), + shape: BoxShape.circle, + ), + ); + }, + ), + + // 第二层波纹 - 延迟动画 + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + final delayedValue = (_pulseController.value + 0.3) % 1.0; + final value = 0.8 + (delayedValue * 0.4); + final opacity = 0.25 - (delayedValue * 0.15); + + return Container( + width: 160.w * value, + height: 160.w * value, + decoration: BoxDecoration( + border: Border.all( + color: secondaryColor.withValues(alpha: opacity), + width: 1.5.w, + ), + shape: BoxShape.circle, + ), + ); + }, + ), + + // 第三层波纹 - 更大延迟 + AnimatedBuilder( + animation: _pulseAnimation, + builder: (context, child) { + final delayedValue = (_pulseController.value + 0.6) % 1.0; + final value = 0.8 + (delayedValue * 0.4); + final opacity = 0.2 - (delayedValue * 0.1); + + return Container( + width: 140.w * value, + height: 140.w * value, + decoration: BoxDecoration( + border: Border.all( + color: primaryColor.withValues(alpha: opacity), + width: 1.w, + ), + shape: BoxShape.circle, + ), + ); + }, + ), + ], + ); + } + // 根据状态获取图标 IconData _getIconForState(PairingState state) { switch (state) { @@ -352,7 +507,7 @@ class PairingView extends GetView { // 已连接设备信息 Widget _buildConnectedDevice(device, bool isDarkMode) { - return Container( + return SizedBox( width: double.infinity, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -443,7 +598,7 @@ class PairingView extends GetView { final showSkipButton = controller.showSkipButton.value; final showAutoCodeInput = controller.showAutoCodeInput.value; final showSkipBindingButton = controller.showSkipBindingButton.value; - return Container( + return SizedBox( width: double.infinity, child: Column( children: [ @@ -452,7 +607,7 @@ class PairingView extends GetView { ElevatedButton( onPressed: () => controller.onConnectTap(), style: ElevatedButton.styleFrom( - backgroundColor: Color(0xFF0066CC), + backgroundColor: const Color(0xFF0066CC), foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 16.h), shape: RoundedRectangleBorder( @@ -572,7 +727,7 @@ class PairingView extends GetView { TextButton( onPressed: () => controller.onSkipTap(), style: TextButton.styleFrom( - foregroundColor: Color(0xFF0066CC), + foregroundColor: const Color(0xFF0066CC), padding: EdgeInsets.symmetric(vertical: 12.h), minimumSize: Size(double.infinity, 48.h), ), @@ -592,7 +747,7 @@ class PairingView extends GetView { TextButton( onPressed: () => controller.onSkipBindingTap(), style: TextButton.styleFrom( - foregroundColor: Color(0xFF0066CC), + foregroundColor: const Color(0xFF0066CC), padding: EdgeInsets.symmetric(vertical: 12.h), minimumSize: Size(double.infinity, 48.h), ), @@ -611,7 +766,7 @@ class PairingView extends GetView { TextButton( onPressed: () => controller.onRetryTap(), style: TextButton.styleFrom( - foregroundColor: Color(0xFF0066CC), + foregroundColor: const Color(0xFF0066CC), padding: EdgeInsets.symmetric(vertical: 12.h), minimumSize: Size(double.infinity, 48.h), ), @@ -629,4 +784,89 @@ class PairingView extends GetView { ); }); } + + // 构建设备关联动画 - 连接线条效果 + Widget _buildAssociatingAnimation(Color primaryColor, Color secondaryColor) { + return AnimatedBuilder( + animation: _rotationAnimation, + builder: (context, child) { + return Stack( + alignment: Alignment.center, + children: [ + // 旋转的连接线条 + Transform.rotate( + angle: _rotationAnimation.value * 2 * 3.14159, + child: Container( + width: 160.w, + height: 160.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: primaryColor.withValues(alpha: 0.6), + width: 2.w, + ), + ), + child: Stack( + children: [ + // 连接点1 + Positioned( + top: 10.w, + left: 80.w - 4.w, + child: Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: primaryColor, + shape: BoxShape.circle, + ), + ), + ), + // 连接点2 + Positioned( + bottom: 10.w, + left: 80.w - 4.w, + child: Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: secondaryColor, + shape: BoxShape.circle, + ), + ), + ), + // 连接点3 + Positioned( + top: 80.w - 4.w, + left: 10.w, + child: Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: primaryColor, + shape: BoxShape.circle, + ), + ), + ), + // 连接点4 + Positioned( + top: 80.w - 4.w, + right: 10.w, + child: Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: secondaryColor, + shape: BoxShape.circle, + ), + ), + ), + ], + ), + ), + ), + ], + ); + }, + ); + } } diff --git a/lib/modules/pairing/views/pairing_view_new.dart b/lib/modules/pairing/views/pairing_view_new.dart index acb400810..b4a2f94d2 100644 --- a/lib/modules/pairing/views/pairing_view_new.dart +++ b/lib/modules/pairing/views/pairing_view_new.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/pairing_controller.dart'; -import '../../../core/themes/app_theme.dart'; /// 配对视图 - 简化版本 class PairingView extends GetView { @@ -94,9 +93,9 @@ class PairingView extends GetView { Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), + color: Colors.red.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12.r), - border: Border.all(color: Colors.red.withOpacity(0.3)), + border: Border.all(color: Colors.red.withValues(alpha: 0.3)), ), child: Row( children: [ @@ -184,7 +183,7 @@ class PairingView extends GetView { width: 100.w, height: 100.w, decoration: BoxDecoration( - color: iconColor.withOpacity(0.1), + color: iconColor.withValues(alpha: 0.1), shape: BoxShape.circle, ), child: Icon(