import '../../../routes/app_routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import '/core/theme/app_colors.dart'; import '/data/models/user_Info.dart'; import '/data/services/ble_manager.dart'; import '../controllers/devices_controller.dart'; // 设备类型枚举 enum DeviceType { Unknown(0), Classic_bluetooth_Headset(1), BLE_Bluetooth_Headset(2), Smart_Voice_Recorder(3), SmartEye(4); const DeviceType(this.value); final int value; String get displayName { switch (this) { case DeviceType.Unknown: return "deviceTypeUnknown".tr; case DeviceType.Classic_bluetooth_Headset: return "deviceTypeClassicBluetoothHeadset".tr; case DeviceType.BLE_Bluetooth_Headset: return "deviceTypeBleBluetoothHeadset".tr; case DeviceType.Smart_Voice_Recorder: return "deviceTypeSmartVoiceRecorder".tr; case DeviceType.SmartEye: return "deviceTypeSmartEye".tr; } } static DeviceType fromValue(int value) { return DeviceType.values.firstWhere( (type) => type.value == value, orElse: () => DeviceType.Unknown, ); } IconData get icon { switch (this) { case DeviceType.Classic_bluetooth_Headset: case DeviceType.BLE_Bluetooth_Headset: return Icons.headphones; case DeviceType.Smart_Voice_Recorder: return Icons.mic; case DeviceType.SmartEye: return Icons.visibility; default: return Icons.device_unknown; } } } class DevicesView extends GetView { const DevicesView({super.key}); bool _isConnected(UserDevice device) { try { final bleManager = Get.find(); return bleManager.currentDeviceAddress != null && bleManager.currentDeviceAddress == device.devicemac; } catch (_) { return false; } } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; final bg = isDark ? const Color(0xFF0A0A0F) : const Color(0xFFF5F6FA); final topBg = isDark ? const Color(0xFF0F1018) : const Color(0xFFF0F1F6); return Scaffold( backgroundColor: bg, body: Column( children: [ // 顶部区域 _buildTopArea(context, isDark, bg, topBg), // 设备列表 Expanded(child: _buildDevicesList(isDark, topBg)), ], ), ); } Widget _buildTopArea( BuildContext context, bool isDark, Color bg, Color topBg) { return Container( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 8.h, left: 20.w, right: 20.w, bottom: 24.h, ), decoration: BoxDecoration(color: bg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 导航栏 Row( children: [ GestureDetector( onTap: () => Get.back(), child: Container( width: 40.w, height: 40.w, decoration: BoxDecoration( shape: BoxShape.circle, color: isDark ? Colors.white.withOpacity(0.06) : Colors.black.withOpacity(0.04), ), child: Icon( Icons.arrow_back_ios_new, size: 18.sp, color: isDark ? Colors.white : const Color(0xFF222222), ), ), ), SizedBox(width: 16.w), Text( "deviceManagement".tr, style: TextStyle( fontSize: 26.sp, fontWeight: FontWeight.w800, color: isDark ? Colors.white : const Color(0xFF111111), letterSpacing: -0.5, ), ), ], ), SizedBox(height: 20.h), // 摘要卡片 Obx(() { final totalDevices = controller.devices.values .fold(0, (sum, list) => sum + list.length); final connectedCount = controller.devices.values .expand((list) => list) .where((d) => _isConnected(d)) .length; return Row( children: [ _buildSummaryCard( number: totalDevices.toString(), label: "boundDevices".tr, isActive: true, isDark: isDark, ), SizedBox(width: 12.w), _buildSummaryCard( number: connectedCount.toString(), label: "currentConnection".tr, isActive: false, isDark: isDark, ), ], ); }), ], ), ); } Widget _buildSummaryCard({ required String number, required String label, required bool isActive, required bool isDark, }) { final activeGradient = isDark ? [ const Color(0xFF6366F1).withOpacity(0.15), const Color(0xFF8B5CF6).withOpacity(0.1), ] : [const Color(0xFFEEF2FF), const Color(0xFFF5F3FF)]; return Expanded( child: Container( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 14.h), decoration: BoxDecoration( borderRadius: BorderRadius.circular(16.r), gradient: isActive ? LinearGradient(colors: activeGradient) : null, color: isActive ? null : (isDark ? Colors.white.withOpacity(0.04) : Colors.white), border: Border.all( color: isActive ? (isDark ? const Color(0xFF6366F1).withOpacity(0.2) : const Color(0xFF6366F1).withOpacity(0.15)) : (isDark ? Colors.white.withOpacity(0.06) : Colors.black.withOpacity(0.04)), ), boxShadow: isDark ? null : [ BoxShadow( color: Colors.black.withOpacity(0.04), blurRadius: 4, offset: const Offset(0, 1), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( number, style: TextStyle( fontSize: 24.sp, fontWeight: FontWeight.w700, color: isActive ? (isDark ? const Color(0xFFA5B4FC) : const Color(0xFF6366F1)) : (isDark ? Colors.white : const Color(0xFF222222)), ), ), SizedBox(height: 2.h), Text( label, style: TextStyle( fontSize: 11.sp, fontWeight: FontWeight.w500, color: isActive ? (isDark ? const Color(0xFFA5B4FC).withOpacity(0.6) : const Color(0xFF818CF8)) : (isDark ? Colors.white.withOpacity(0.35) : const Color(0xFF999999)), ), ), ], ), ), ); } Widget _buildDevicesList(bool isDark, Color topBg) { return Obx(() { final groupedDevices = controller.devices; if (groupedDevices.isEmpty) { return _buildEmptyState(isDark); } return ListView( padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h), children: [ ...groupedDevices.entries.map((entry) { final deviceType = DeviceType.fromValue(entry.key); final deviceList = entry.value; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 分类标题 Padding( padding: EdgeInsets.only(left: 8.w, bottom: 12.h), child: Text( deviceType.displayName.toUpperCase(), style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w600, color: isDark ? Colors.white.withOpacity(0.25) : const Color(0xFFAAAAAA), letterSpacing: 1.5, ), ), ), ...deviceList.map((device) => _buildDeviceCard( device: device, deviceType: deviceType, isDark: isDark, )), SizedBox(height: 16.h), ], ); }), ], ); }); } Widget _buildEmptyState(bool isDark) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.devices_other, size: 80.sp, color: isDark ? Colors.white54 : Colors.grey[400], ), SizedBox(height: 20.h), Text( "noDevicesBound".tr, style: TextStyle( fontSize: 16.sp, color: isDark ? Colors.white70 : Colors.grey[600], ), ), SizedBox(height: 12.h), Text( "pleaseBindDeviceFirst".tr, style: TextStyle( fontSize: 14.sp, color: isDark ? Colors.white54 : Colors.grey[500], ), ), ], ), ); } Widget _buildDeviceCard({ required UserDevice device, required DeviceType deviceType, required bool isDark, }) { final connected = _isConnected(device); final connectedColor = const Color(0xFF22C55E); return GestureDetector( onTap: () => Get.toNamed(Routes.eqSettings), child: Container( margin: EdgeInsets.only(bottom: 10.h), padding: EdgeInsets.all(20.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.r), color: isDark ? (connected ? connectedColor.withOpacity(0.04) : Colors.white.withOpacity(0.03)) : (connected ? connectedColor.withOpacity(0.03) : Colors.white), border: Border.all( color: connected ? connectedColor.withOpacity(0.2) : (isDark ? Colors.white.withOpacity(0.05) : Colors.black.withOpacity(0.04)), ), boxShadow: isDark ? null : [ BoxShadow( color: Colors.black.withOpacity(0.03), blurRadius: 12, offset: const Offset(0, 2), ), ], ), child: Column( children: [ // 上部:图标 + 名称/MAC + 解绑 Row( children: [ // 设备图标 Container( width: 46.w, height: 46.w, decoration: BoxDecoration( borderRadius: BorderRadius.circular(14.r), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: connected ? [connectedColor, const Color(0xFF16A34A)] : [const Color(0xFF6366F1), const Color(0xFF8B5CF6)], ), boxShadow: [ BoxShadow( color: (connected ? connectedColor : const Color(0xFF6366F1)) .withOpacity(0.3), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: Icon( deviceType.icon, color: Colors.white, size: 22.sp, ), ), SizedBox(width: 14.w), // 设备名 + MAC Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( device.devicename.isNotEmpty ? device.devicename : "deviceTypeUnknown".tr, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w700, color: isDark ? Colors.white : const Color(0xFF1A1A2E), ), ), if (connected) ...[ SizedBox(width: 8.w), Container( width: 6.w, height: 6.w, decoration: BoxDecoration( shape: BoxShape.circle, color: connectedColor, boxShadow: [ BoxShadow( color: connectedColor.withOpacity(0.6), blurRadius: 8, ), ], ), ), SizedBox(width: 6.w), Container( padding: EdgeInsets.symmetric( horizontal: 8.w, vertical: 3.h), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.r), color: connectedColor.withOpacity(0.1), ), child: Text( "connected".tr, style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: connectedColor, ), ), ), ], ], ), SizedBox(height: 3.h), Text( device.devicemac.isNotEmpty ? device.devicemac : "noMacAddress".tr, style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w500, color: isDark ? Colors.white.withOpacity(0.3) : const Color(0xFF999999), fontFamily: 'Menlo', letterSpacing: 0.5, ), ), ], ), ), // 解绑按钮 GestureDetector( onTap: () => _showUnbindDialog(device, isDark), child: Container( padding: EdgeInsets.symmetric( horizontal: 14.w, vertical: 8.h), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.r), border: Border.all( color: isDark ? Colors.white.withOpacity(0.08) : Colors.black.withOpacity(0.08), ), ), child: Text( "unbind".tr, style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w600, color: isDark ? Colors.white.withOpacity(0.4) : const Color(0xFF999999), ), ), ), ), ], ), // 下部:固件版本 / 授权码 / CMEI Obx(() { final bleManager = Get.find(); // 必须无条件读取 deviceInfo.value,否则未连接设备的 Obx 内没有任何 // observable,会触发 "improper use of a GetX" 错误 final info = bleManager.deviceInfo.value; final fwVersion = connected ? (info?.firmwareVersion ?? '') : ''; // 固件版本原始格式:左 3 段 + 右 3 段("0.0.1.0.0.1"),只展示左版本 final parts = fwVersion.split('.'); final fwDisplay = parts.length >= 3 ? parts.take(3).join('.') : fwVersion; final hasFirmware = fwVersion.isNotEmpty; final hasLicense = device.license.isNotEmpty; final hasCmei = device.cmei.isNotEmpty; if (!hasFirmware && !hasLicense && !hasCmei) { return const SizedBox.shrink(); } return Padding( padding: EdgeInsets.only(top: 16.h), child: Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.r), color: isDark ? Colors.white.withOpacity(0.03) : const Color(0xFFF7F7FA), ), child: Column( children: [ if (hasFirmware) _buildFieldRow( label: "firmwareVersion".tr, value: fwDisplay, isDark: isDark, ), if (hasFirmware && (hasLicense || hasCmei)) SizedBox(height: 8.h), if (hasLicense) _buildFieldRow( label: "licenseCode".tr, value: device.license, isDark: isDark, ), if (hasLicense && hasCmei) SizedBox(height: 8.h), if (hasCmei) _buildFieldRow( label: "CMEI", value: device.cmei, isDark: isDark, ), ], ), ), ); }), ], ), ), ); } Widget _buildFieldRow({ required String label, required String value, required bool isDark, }) { return Row( children: [ SizedBox( width: 72.w, child: Text( label, style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: isDark ? Colors.white.withOpacity(0.2) : const Color(0xFFBBBBBB), letterSpacing: 0.5, ), ), ), SizedBox(width: 8.w), Expanded( child: Text( value, style: TextStyle( fontSize: 11.sp, fontWeight: FontWeight.w500, color: isDark ? Colors.white.withOpacity(0.5) : const Color(0xFF666666), fontFamily: 'Menlo', letterSpacing: 0.3, ), overflow: TextOverflow.ellipsis, ), ), ], ); } void _showUnbindDialog(UserDevice device, bool isDark) { Get.dialog( AlertDialog( backgroundColor: isDark ? AppColors.cardBackground : Colors.white, title: Text( "confirmUnbind".tr, style: TextStyle( color: isDark ? Colors.white : Colors.black87, ), ), content: Text( "confirmUnbindMessage" .tr .replaceAll('{deviceName}', device.devicename), style: TextStyle( color: isDark ? Colors.white70 : Colors.black54, ), ), actions: [ TextButton( onPressed: () => Get.back(), child: Text( "cancel".tr, style: TextStyle( color: isDark ? Colors.white70 : Colors.grey[600], ), ), ), TextButton( onPressed: () { Get.back(); controller.unbindearphone(device); }, child: Text( "confirmUnbindAction".tr, style: const TextStyle(color: Colors.red), ), ), ], ), ); } }