|
|
|
@ -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, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
}, |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|