import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/pairing_controller.dart'; 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; return Scaffold( backgroundColor: isDarkMode ? Colors.black : Colors.white, body: SafeArea( child: Column( children: [ // 固定顶部区域 - 不受下方内容影响 _buildFixedTopSection(isDarkMode), // 可滚动的动态内容区域 Expanded( child: _buildDynamicContentSection(isDarkMode), ), ], ), ), ); } // 固定顶部区域 Widget _buildFixedTopSection(bool isDarkMode) { return Padding( padding: EdgeInsets.only(top: 60.h, left: 24.w, right: 24.w), child: Column( mainAxisSize: MainAxisSize.min, children: [ // 标题部分 _buildHeader(isDarkMode), SizedBox(height: 20.h), _buildDeviceAnimation(isDarkMode), ], ), ); } // 动态内容区域 Widget _buildDynamicContentSection(bool isDarkMode) { return SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.w), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: 30.h), // 状态信息 _buildStatusInfo(isDarkMode), SizedBox(height: 40.h), // 底部按钮 _buildActionButtons(isDarkMode), SizedBox(height: 20.h), ], ), ), ); } // 头部标题 Widget _buildHeader(bool isDarkMode) { return Column( children: [ Text( 'Voitrans', style: TextStyle( fontSize: 30.sp, fontWeight: FontWeight.bold, color: isDarkMode ? Colors.white : Colors.black, ), ), SizedBox(height: 8.h), Text( 'homeSlogan'.tr, // 连接世界,超越声音 style: TextStyle( fontSize: 16.sp, color: isDarkMode ? Colors.white60 : Colors.black54, ), ), ], ); } // 设备动画 - 根据状态显示不同的动画效果 Widget _buildDeviceAnimation(bool isDarkMode) { return Obx(() { final state = controller.currentState; final isLoading = controller.isLoading.value; // 根据状态决定动画颜色 Color primaryColor = const Color(0xFF0066CC); Color secondaryColor = const Color(0xFF5E5CE6); // 错误状态使用红色 if (state == PairingState.error) { primaryColor = Colors.red; secondaryColor = Colors.redAccent; } // 成功状态使用绿色 else if (state == PairingState.completed) { primaryColor = Colors.green; secondaryColor = Colors.greenAccent; } // 待机状态的特殊处理 if (state == PairingState.idle || state == PairingState.waitingClassic) { return SizedBox( width: 240.w, height: 240.w, child: Stack( alignment: Alignment.center, children: [ // 静态背景圆圈 Container( width: 200.w, height: 200.w, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ primaryColor.withValues(alpha: 0.1), secondaryColor.withValues(alpha: 0.1), ], ), shape: BoxShape.circle, ), ), // 设备图标 Container( width: 120.w, height: 120.w, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [primaryColor, secondaryColor], ), shape: BoxShape.circle, ), child: Icon( Icons.headphones, color: Colors.white, size: 60.w, ), ), ], ), ); } return SizedBox( width: 240.w, height: 240.w, child: Stack( alignment: Alignment.center, children: [ // 连续脉搏动画 - 只在加载时显示 if (isLoading) _buildPulseAnimation(primaryColor, secondaryColor), // 特殊的安全连接动画 - 只在建立安全连接时显示 if (state == PairingState.waitingBle) _buildSecureConnectionAnimation(primaryColor, secondaryColor), // 设备关联动画 - 只在关联设备时显示 if (state == PairingState.associating) _buildAssociatingAnimation(primaryColor, secondaryColor), // 旋转边框 - 只在加载时显示 if (isLoading) _buildRotatingBorder(primaryColor), // 设备图标 Container( width: 120.w, height: 120.w, decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [primaryColor, secondaryColor], ), shape: BoxShape.circle, ), child: Icon( _getIconForState(state), color: Colors.white, size: 60.w, ), ), ], ), ); }); } // 构建连续脉搏动画 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) { case PairingState.completed: return Icons.check_circle; case PairingState.associating: return Icons.link; case PairingState.error: final errorType = controller.errorType; switch (errorType) { case ErrorType.bluetoothOff: case ErrorType.noPermission: return Icons.bluetooth_disabled; case ErrorType.noClassicDevice: case ErrorType.deviceNotMatch: return Icons.headphones_outlined; case ErrorType.associationFailed: return Icons.link_off; default: return Icons.error_outline; } default: return Icons.headphones; } } // 状态信息 Widget _buildStatusInfo(bool isDarkMode) { return Obx(() { final status = controller.statusText.value; final state = controller.currentState; final device = controller.currentDevice; return Column( children: [ // 状态文本 - 更大更醒目 Text( status, style: TextStyle( fontSize: 20.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black, ), textAlign: TextAlign.center, ), SizedBox(height: 10.h), // 子状态提示文本 - 根据状态显示不同提示 Text( _getSubtitleForState(state), style: TextStyle( fontSize: 14.sp, color: isDarkMode ? Colors.white60 : Colors.grey[600], ), textAlign: TextAlign.center, ), // 当前连接设备(有设备时才显示) if (device != null && (state == PairingState.verifying || state == PairingState.binding || state == PairingState.waitingBle || state == PairingState.completed)) ...[ SizedBox(height: 24.h), _buildConnectedDevice(device, isDarkMode), ], ], ); }); } // 根据状态获取副标题 String _getSubtitleForState(PairingState state) { switch (state) { case PairingState.idle: return 'readyToPair'.tr; // 准备开始配对 case PairingState.checking: return 'preparingPairingEnvironment'.tr; // 正在准备配对环境... case PairingState.waitingClassic: return 'connectDeepSoundViaSystemBluetooth'.tr; // 请先通过系统蓝牙连接Voitrans设备 case PairingState.verifying: return 'verifyingDevice'.tr; // 正在验证设备... case PairingState.binding: return 'deviceBindingConfirm'.tr; // 设备绑定确认... case PairingState.associating: return 'establishingDeviceAssociation'.tr; // 正在建立设备关联... case PairingState.waitingBle: return 'waitingCompletePairing'.tr; // 请稍候,正在完成配对... case PairingState.completed: return 'pairingSuccessRedirecting'.tr; // 配对成功!即将跳转到主页... case PairingState.error: final errorType = controller.errorType; switch (errorType) { case ErrorType.bluetoothOff: return 'connectDeepSoundViaSystemBluetooth'.tr; // 蓝牙已关闭,请打开蓝牙 case ErrorType.noPermission: return 'lackPermissions'.tr; // 缺少蓝牙权限和位置权限 case ErrorType.noClassicDevice: return 'connectDeepSoundViaSystemBluetooth' .tr; // 请先通过系统蓝牙连接Voitrans设备 case ErrorType.deviceNotMatch: return 'ensureConnectedDeepSoundDevice'.tr.trArgs( [controller.currentDevice?.name ?? '']); // 请确保连接的是Voitrans设备 case ErrorType.deviceBound: return 'deviceAlreadyBoundToOtherAccount'.tr; // 该设备已被其他账号绑定 case ErrorType.associationFailed: return 'deviceAssociationFailed'.tr; // 设备关联失败,请重试 case ErrorType.bleTimeout: return 'scanTimeoutEnsureDeviceNearby'.tr; // 扫描超时,请确保设备在附近 case ErrorType.networkError: return 'networkError'.tr; // 网络错误 case ErrorType.authnFailed: return 'authorizationFailedCheckCode'.tr; // 授权失败,请检查授权码是否正确 default: return 'pairingFailedRetry'.tr; // 配对失败,请重试 } default: return ''; } } // 已连接设备信息 Widget _buildConnectedDevice(device, bool isDarkMode) { return SizedBox( width: double.infinity, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'currentlyConnected'.tr, // 当前已连接: style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white70 : Colors.black87, ), ), SizedBox(height: 8.h), _buildDeviceItem(device, isDarkMode), ], ), ); } // 设备项 Widget _buildDeviceItem(device, bool isDarkMode) { return Container( margin: EdgeInsets.only(bottom: 8.h), padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.grey[100], borderRadius: BorderRadius.circular(8.r), ), child: Row( children: [ Icon( Icons.headphones, color: isDarkMode ? Colors.white70 : Colors.black54, size: 24.w, ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( device.name ?? 'unknown'.tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, color: isDarkMode ? Colors.white : Colors.black, ), ), SizedBox(height: 4.h), Text( device.address ?? '', style: TextStyle( fontSize: 12.sp, color: isDarkMode ? Colors.white54 : Colors.black45, ), ), ], ), ), Icon( Icons.bluetooth_connected, color: Colors.green, size: 20.w, ), ], ), ); } // 获取连接按钮图标 IconData _getConnectButtonIcon() { final buttonText = controller.connectButtonText.value; if (buttonText.contains('bluetooth'.tr)) { return Icons.bluetooth; } else if (buttonText.contains('settings'.tr)) { return Icons.settings; } return Icons.bluetooth_searching; } // 操作按钮 Widget _buildActionButtons(bool isDarkMode) { return Obx(() { final showConnectButton = controller.showConnectButton.value; final showRetryButton = controller.showRetryButton.value; final showBindButton = controller.showBindButton.value; final showRetryChectButton = controller.showRetryChectButton.value; final showSkipButton = controller.showSkipButton.value; final showAutoCodeInput = controller.showAutoCodeInput.value; final showSkipBindingButton = controller.showSkipBindingButton.value; return SizedBox( width: double.infinity, child: Column( children: [ // 显示连接按钮 if (showConnectButton) ElevatedButton( onPressed: () => controller.onConnectTap(), style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF0066CC), foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 16.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), elevation: 0, minimumSize: Size(double.infinity, 56.h), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(_getConnectButtonIcon(), size: 20.w), SizedBox(width: 8.w), Text( controller.connectButtonText.value, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, ), ), ], ), ), if (showBindButton) ElevatedButton( onPressed: () => controller.confirmBindDevice(), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 16.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), elevation: 0, minimumSize: Size(double.infinity, 56.h), ), child: Text( "bind".tr, // 绑定 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, ), ), ), SizedBox(height: 12.h), // 显示重试按钮 if (showRetryButton) ElevatedButton( onPressed: () => controller.onRetryTap(), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, padding: EdgeInsets.symmetric(vertical: 16.h), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16.r), ), elevation: 0, minimumSize: Size(double.infinity, 56.h), ), child: Text( controller.retryButtonText.value, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, ), ), ), if (showAutoCodeInput) Column( children: [ // 输入框 SizedBox( width: double.infinity, height: 48.h, child: TextField( controller: controller .codeInputController, // 你可以在 Controller 中声明这个 decoration: InputDecoration( hintText: 'enterCode'.tr, // 请输入代码 border: OutlineInputBorder( borderRadius: BorderRadius.circular(12.r), ), contentPadding: EdgeInsets.symmetric(horizontal: 16.w), ), ), ), SizedBox(height: 12.h), SizedBox( width: double.infinity, height: 48.h, child: ElevatedButton( onPressed: controller.onAutoCodeInputTap, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(Get.context!).primaryColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.r), ), elevation: 0, ), child: Text( 'authorizeBind'.tr, // 授权绑定 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ), ], ), // 自动输入代码按钮 // 跳过按钮 if (showSkipButton) ...[ SizedBox(height: 12.h), TextButton( onPressed: () => controller.onSkipTap(), style: TextButton.styleFrom( foregroundColor: const Color(0xFF0066CC), padding: EdgeInsets.symmetric(vertical: 12.h), minimumSize: Size(double.infinity, 48.h), ), child: Text( 'talkLater'.tr, // 稍后再说 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), ), ], // 跳过绑定按钮 if (showSkipBindingButton) ...[ SizedBox(height: 12.h), TextButton( onPressed: () => controller.onSkipBindingTap(), style: TextButton.styleFrom( foregroundColor: const Color(0xFF0066CC), padding: EdgeInsets.symmetric(vertical: 12.h), minimumSize: Size(double.infinity, 48.h), ), child: Text( 'skipBinding'.tr, // 跳过绑定 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), ), ], //重新检测 if (showRetryChectButton) ...[ SizedBox(height: 12.h), TextButton( onPressed: () => controller.onRetryTap(), style: TextButton.styleFrom( foregroundColor: const Color(0xFF0066CC), padding: EdgeInsets.symmetric(vertical: 12.h), minimumSize: Size(double.infinity, 48.h), ), child: Text( 'rescan'.tr, // 重新扫描 style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w500, ), ), ), ], ], ), ); }); } // 构建设备关联动画 - 连接线条效果 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, ), ), ), ], ), ), ), ], ); }, ); } }