Browse Source

feat(pairing): 优化配对视图动画效果和控制器逻辑

重构配对视图动画系统,使用更高效的AnimationController实现连续脉搏和旋转效果
新增安全连接和设备关联的特殊动画效果
优化控制器检查逻辑,使用Future.wait并行执行蓝牙和权限检查
调整导航延迟从1秒缩短至500毫秒
更新颜色透明度方法从withOpacity改为withValues
weicu
lxm 1 year ago
parent
commit
dae5dde2de
  1. 1
      .gitignore
  2. 29
      lib/modules/pairing/controllers/pairing_controller.dart
  3. 342
      lib/modules/pairing/views/pairing_view.dart
  4. 7
      lib/modules/pairing/views/pairing_view_new.dart

1
.gitignore

@ -221,3 +221,4 @@ fastlane/test_output
fastlane/readme.md fastlane/readme.md
.trae/rules/project_rules.md .trae/rules/project_rules.md
.augment/rules/imported/project_rules.md .augment/rules/imported/project_rules.md
.kiro/steering/flutter_project_standards.md

29
lib/modules/pairing/controllers/pairing_controller.dart

@ -384,29 +384,33 @@ class PairingController extends GetxController {
/// 检查当前状态并决定下一步 /// 检查当前状态并决定下一步
void _checkCurrentStatus() async { void _checkCurrentStatus() async {
try { try {
// 1. 检查蓝牙状态 // 使用 Future.wait 并行执行检查,提升启动速度
if (_bluetoothManager.bluetoothState == 'off') { 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); _setError(ErrorType.bluetoothOff, 'pleaseEnableBluetooth'.tr);
return; return;
} }
// 2. 检查权限 if (!arePermissionsOk) {
if (!await _checkPermissions()) {
_setError(ErrorType.noPermission, 'pleaseGrantNecessaryPermissions'.tr); _setError(ErrorType.noPermission, 'pleaseGrantNecessaryPermissions'.tr);
return; return;
} }
// 3. 先刷新设备连接状态,确保获取最新的连接信息 // 后续逻辑保持不变,确保所有设备都经过验证
await _bluetoothManager.refreshDeviceConnection();
// 4. 检查经典蓝牙连接
final classicDevice = _bluetoothManager.currentDevice; final classicDevice = _bluetoothManager.currentDevice;
if (classicDevice == null) { if (classicDevice == null) {
_transition(PairingState.waitingClassic); _transition(PairingState.waitingClassic);
return; return;
} }
// 5. 如果有设备,进行验证
_currentClassicDevice = classicDevice; _currentClassicDevice = classicDevice;
_transition(PairingState.verifying); _transition(PairingState.verifying);
await _verifyDevice(classicDevice); await _verifyDevice(classicDevice);
@ -416,6 +420,11 @@ class PairingController extends GetxController {
} }
} }
/// 新增一个辅助方法来检查蓝牙状态
Future<bool> _checkBluetoothState() async {
return _bluetoothManager.bluetoothState != 'off';
}
/// 检查权限 /// 检查权限
Future<bool> _checkPermissions() async { Future<bool> _checkPermissions() async {
// Android权限检查 // Android权限检查
@ -700,7 +709,7 @@ class PairingController extends GetxController {
/// 导航到主页 /// 导航到主页
void _navigateToHome() { void _navigateToHome() {
Future.delayed(const Duration(seconds: 1), () async { Future.delayed(const Duration(milliseconds: 500), () async {
// 启动AgentService服务 // 启动AgentService服务
try { try {
Logger.i(_tag, '启动AgentService服务'); Logger.i(_tag, '启动AgentService服务');

342
lib/modules/pairing/views/pairing_view.dart

@ -3,9 +3,68 @@ import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/pairing_controller.dart'; import '../controllers/pairing_controller.dart';
class PairingView extends GetView<PairingController> { class PairingView extends StatefulWidget {
const PairingView({Key? key}) : super(key: key); const PairingView({Key? key}) : super(key: key);
@override
State<PairingView> createState() => _PairingViewState();
}
class _PairingViewState extends State<PairingView>
with TickerProviderStateMixin {
late AnimationController _pulseController;
late AnimationController _rotationController;
late Animation<double> _pulseAnimation;
late Animation<double> _rotationAnimation;
PairingController get controller => Get.find<PairingController>();
@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<double>(
begin: 0.8,
end: 1.2,
).animate(CurvedAnimation(
parent: _pulseController,
curve: Curves.easeInOut,
));
// 旋转动画
_rotationAnimation = Tween<double>(
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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark; final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
@ -30,7 +89,7 @@ class PairingView extends GetView<PairingController> {
padding: EdgeInsets.symmetric(horizontal: 24.w), padding: EdgeInsets.symmetric(horizontal: 24.w),
// 添加SingleChildScrollView以防止溢出 // 添加SingleChildScrollView以防止溢出
child: SingleChildScrollView( child: SingleChildScrollView(
physics: BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: minHeight:
@ -98,8 +157,8 @@ class PairingView extends GetView<PairingController> {
final state = controller.currentState; final state = controller.currentState;
final isLoading = controller.isLoading.value; final isLoading = controller.isLoading.value;
// 根据状态决定动画颜色 // 根据状态决定动画颜色
Color primaryColor = Color(0xFF0066CC); Color primaryColor = const Color(0xFF0066CC);
Color secondaryColor = Color(0xFF5E5CE6); Color secondaryColor = const Color(0xFF5E5CE6);
// 错误状态使用红色 // 错误状态使用红色
if (state == PairingState.error) { if (state == PairingState.error) {
@ -114,7 +173,7 @@ class PairingView extends GetView<PairingController> {
// 待机状态的特殊处理 // 待机状态的特殊处理
if (state == PairingState.idle || state == PairingState.waitingClassic) { if (state == PairingState.idle || state == PairingState.waitingClassic) {
return Container( return SizedBox(
width: 240.w, width: 240.w,
height: 240.w, height: 240.w,
child: Stack( child: Stack(
@ -129,8 +188,8 @@ class PairingView extends GetView<PairingController> {
begin: Alignment.topLeft, begin: Alignment.topLeft,
end: Alignment.bottomRight, end: Alignment.bottomRight,
colors: [ colors: [
primaryColor.withOpacity(0.1), primaryColor.withValues(alpha: 0.1),
secondaryColor.withOpacity(0.1), secondaryColor.withValues(alpha: 0.1),
], ],
), ),
shape: BoxShape.circle, shape: BoxShape.circle,
@ -159,51 +218,25 @@ class PairingView extends GetView<PairingController> {
); );
} }
return Container( return SizedBox(
width: 240.w, width: 240.w,
height: 240.w, height: 240.w,
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
// 脉搏背景 - 只在加载时显示动画 // 连续脉搏动画 - 只在加载时显示
if (isLoading) if (isLoading) _buildPulseAnimation(primaryColor, secondaryColor),
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.8, end: 1.1), // 特殊的安全连接动画 - 只在建立安全连接时显示
duration: const Duration(seconds: 2), if (state == PairingState.waitingBle)
curve: Curves.easeInOut, _buildSecureConnectionAnimation(primaryColor, secondaryColor),
builder: (context, value, child) {
return Container( // 设备关联动画 - 只在关联设备时显示
width: 200.w * value, if (state == PairingState.associating)
height: 200.w * value, _buildAssociatingAnimation(primaryColor, secondaryColor),
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) if (isLoading) _buildRotatingBorder(primaryColor),
SizedBox(
width: 100.w,
height: 100.w,
child: CircularProgressIndicator(
strokeWidth: 4.w,
valueColor: AlwaysStoppedAnimation<Color>(primaryColor),
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
// 设备图标 // 设备图标
Container( Container(
@ -229,6 +262,128 @@ class PairingView extends GetView<PairingController> {
}); });
} }
// 构建连续脉搏动画
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<Color>(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) { IconData _getIconForState(PairingState state) {
switch (state) { switch (state) {
@ -352,7 +507,7 @@ class PairingView extends GetView<PairingController> {
// 已连接设备信息 // 已连接设备信息
Widget _buildConnectedDevice(device, bool isDarkMode) { Widget _buildConnectedDevice(device, bool isDarkMode) {
return Container( return SizedBox(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -443,7 +598,7 @@ class PairingView extends GetView<PairingController> {
final showSkipButton = controller.showSkipButton.value; final showSkipButton = controller.showSkipButton.value;
final showAutoCodeInput = controller.showAutoCodeInput.value; final showAutoCodeInput = controller.showAutoCodeInput.value;
final showSkipBindingButton = controller.showSkipBindingButton.value; final showSkipBindingButton = controller.showSkipBindingButton.value;
return Container( return SizedBox(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
children: [ children: [
@ -452,7 +607,7 @@ class PairingView extends GetView<PairingController> {
ElevatedButton( ElevatedButton(
onPressed: () => controller.onConnectTap(), onPressed: () => controller.onConnectTap(),
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF0066CC), backgroundColor: const Color(0xFF0066CC),
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16.h), padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -572,7 +727,7 @@ class PairingView extends GetView<PairingController> {
TextButton( TextButton(
onPressed: () => controller.onSkipTap(), onPressed: () => controller.onSkipTap(),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: Color(0xFF0066CC), foregroundColor: const Color(0xFF0066CC),
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 12.h),
minimumSize: Size(double.infinity, 48.h), minimumSize: Size(double.infinity, 48.h),
), ),
@ -592,7 +747,7 @@ class PairingView extends GetView<PairingController> {
TextButton( TextButton(
onPressed: () => controller.onSkipBindingTap(), onPressed: () => controller.onSkipBindingTap(),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: Color(0xFF0066CC), foregroundColor: const Color(0xFF0066CC),
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 12.h),
minimumSize: Size(double.infinity, 48.h), minimumSize: Size(double.infinity, 48.h),
), ),
@ -611,7 +766,7 @@ class PairingView extends GetView<PairingController> {
TextButton( TextButton(
onPressed: () => controller.onRetryTap(), onPressed: () => controller.onRetryTap(),
style: TextButton.styleFrom( style: TextButton.styleFrom(
foregroundColor: Color(0xFF0066CC), foregroundColor: const Color(0xFF0066CC),
padding: EdgeInsets.symmetric(vertical: 12.h), padding: EdgeInsets.symmetric(vertical: 12.h),
minimumSize: Size(double.infinity, 48.h), minimumSize: Size(double.infinity, 48.h),
), ),
@ -629,4 +784,89 @@ class PairingView extends GetView<PairingController> {
); );
}); });
} }
// 构建设备关联动画 - 连接线条效果
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,
),
),
),
],
),
),
),
],
);
},
);
}
} }

7
lib/modules/pairing/views/pairing_view_new.dart

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/pairing_controller.dart'; import '../controllers/pairing_controller.dart';
import '../../../core/themes/app_theme.dart';
/// 配对视图 - 简化版本 /// 配对视图 - 简化版本
class PairingView extends GetView<PairingController> { class PairingView extends GetView<PairingController> {
@ -94,9 +93,9 @@ class PairingView extends GetView<PairingController> {
Container( Container(
padding: EdgeInsets.all(16.w), padding: EdgeInsets.all(16.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red.withOpacity(0.1), color: Colors.red.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(12.r), 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( child: Row(
children: [ children: [
@ -184,7 +183,7 @@ class PairingView extends GetView<PairingController> {
width: 100.w, width: 100.w,
height: 100.w, height: 100.w,
decoration: BoxDecoration( decoration: BoxDecoration(
color: iconColor.withOpacity(0.1), color: iconColor.withValues(alpha: 0.1),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: Icon( child: Icon(

Loading…
Cancel
Save