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
.trae/rules/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 {
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<bool> _checkBluetoothState() async {
return _bluetoothManager.bluetoothState != 'off';
}
/// 检查权限
Future<bool> _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服务');

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 '../controllers/pairing_controller.dart';
class PairingView extends GetView<PairingController> {
class PairingView extends StatefulWidget {
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
Widget build(BuildContext context) {
final bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
@ -30,7 +89,7 @@ class PairingView extends GetView<PairingController> {
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<PairingController> {
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<PairingController> {
// 待机状态的特殊处理
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<PairingController> {
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<PairingController> {
);
}
return Container(
return SizedBox(
width: 240.w,
height: 240.w,
child: Stack(
alignment: Alignment.center,
children: [
// 脉搏背景 - 只在加载时显示动画
if (isLoading)
TweenAnimationBuilder<double>(
tween: Tween<double>(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<Color>(primaryColor),
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
if (isLoading) _buildRotatingBorder(primaryColor),
// 设备图标
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) {
switch (state) {
@ -352,7 +507,7 @@ class PairingView extends GetView<PairingController> {
// 已连接设备信息
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<PairingController> {
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<PairingController> {
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<PairingController> {
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<PairingController> {
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<PairingController> {
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<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:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/pairing_controller.dart';
import '../../../core/themes/app_theme.dart';
/// 配对视图 - 简化版本
class PairingView extends GetView<PairingController> {
@ -94,9 +93,9 @@ class PairingView extends GetView<PairingController> {
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<PairingController> {
width: 100.w,
height: 100.w,
decoration: BoxDecoration(
color: iconColor.withOpacity(0.1),
color: iconColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(

Loading…
Cancel
Save