You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

581 lines
19 KiB

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import '/data/models/user_Info.dart';
import '../controllers/my_devices_controller.dart';
enum _MyDeviceCategory {
unknown(0),
classicHeadset(1),
bleHeadset(2),
voiceRecorder(4),
smartEye(5);
const _MyDeviceCategory(this.value);
final int value;
static _MyDeviceCategory fromValue(int value) =>
_MyDeviceCategory.values.firstWhere(
(c) => c.value == value,
orElse: () => _MyDeviceCategory.unknown,
);
String get displayName {
switch (this) {
case _MyDeviceCategory.unknown:
return 'deviceTypeUnknown'.tr;
case _MyDeviceCategory.classicHeadset:
return 'deviceTypeClassicBluetoothHeadset'.tr;
case _MyDeviceCategory.bleHeadset:
return 'deviceTypeBleBluetoothHeadset'.tr;
case _MyDeviceCategory.voiceRecorder:
return 'deviceTypeSmartVoiceRecorder'.tr;
case _MyDeviceCategory.smartEye:
return 'deviceTypeSmartEye'.tr;
}
}
IconData get icon {
switch (this) {
case _MyDeviceCategory.classicHeadset:
case _MyDeviceCategory.bleHeadset:
return Icons.headphones;
case _MyDeviceCategory.voiceRecorder:
return Icons.mic;
case _MyDeviceCategory.smartEye:
return Icons.visibility;
default:
return Icons.device_unknown;
}
}
}
class MyDevicesView extends GetView<MyDevicesController> {
const MyDevicesView({super.key});
@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),
Expanded(child: _buildDevicesList(isDark, topBg)),
_buildAddDeviceButton(isDark),
],
),
);
}
Widget _buildTopArea(BuildContext context, bool isDark, Color bg) {
return Container(
padding: EdgeInsets.only(
top: MediaQuery.of(context).padding.top + 8.h,
left: 20.w,
right: 20.w,
bottom: 20.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.withValues(alpha: 0.06)
: Colors.black.withValues(alpha: 0.04),
),
child: Icon(
Icons.arrow_back_ios_new,
size: 18.sp,
color: isDark ? Colors.white : const Color(0xFF222222),
),
),
),
SizedBox(width: 16.w),
Expanded(
child: Text(
'deviceManagement'.tr,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w800,
color: isDark ? Colors.white : const Color(0xFF111111),
letterSpacing: -0.5,
),
),
),
Obx(() => controller.isLoading.value
? SizedBox(
width: 18.w,
height: 18.w,
child: const CircularProgressIndicator(strokeWidth: 2),
)
: IconButton(
onPressed: controller.loadDevices,
icon: Icon(
Icons.refresh,
size: 20.sp,
color: isDark ? Colors.white70 : Colors.black54,
),
)),
],
),
SizedBox(height: 16.h),
Obx(() {
final total = controller.devicesByType.values
.fold<int>(0, (sum, list) => sum + list.length);
final connected = controller.devicesByType.values
.expand((list) => list)
.where(controller.isConnected)
.length;
return Row(
children: [
_buildSummaryCard(
number: '$total',
label: 'boundDevices'.tr,
isActive: true,
isDark: isDark),
SizedBox(width: 12.w),
_buildSummaryCard(
number: '$connected',
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).withValues(alpha: 0.15),
const Color(0xFF8B5CF6).withValues(alpha: 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.withValues(alpha: 0.04)
: Colors.white),
border: Border.all(
color: isActive
? const Color(0xFF6366F1).withValues(alpha: 0.18)
: (isDark
? Colors.white.withValues(alpha: 0.06)
: Colors.black.withValues(alpha: 0.04)),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
number,
style: TextStyle(
fontSize: 22.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: isDark
? Colors.white.withValues(alpha: 0.5)
: const Color(0xFF999999),
),
),
],
),
),
);
}
Widget _buildDevicesList(bool isDark, Color topBg) {
return Obx(() {
final groups = controller.devicesByType;
if (groups.isEmpty) {
return _buildEmptyState(isDark);
}
return ListView(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
children: groups.entries.map((entry) {
final category = _MyDeviceCategory.fromValue(entry.key);
final list = entry.value;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(left: 8.w, bottom: 10.h),
child: Text(
category.displayName.toUpperCase(),
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: isDark
? Colors.white.withValues(alpha: 0.25)
: const Color(0xFFAAAAAA),
letterSpacing: 1.2,
),
),
),
...list.map((d) => _buildDeviceCard(d, category, isDark)),
SizedBox(height: 14.h),
],
);
}).toList(),
);
});
}
Widget _buildEmptyState(bool isDark) {
return Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.devices_other,
size: 72.sp,
color: isDark ? Colors.white54 : Colors.grey[400],
),
SizedBox(height: 16.h),
Text(
'noDevicesBound'.tr,
style: TextStyle(
fontSize: 16.sp,
color: isDark ? Colors.white70 : Colors.grey[700],
),
),
SizedBox(height: 8.h),
Text(
'pleaseBindDeviceFirst'.tr,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.sp,
color: isDark ? Colors.white54 : Colors.grey[500],
),
),
],
),
),
);
}
Widget _buildDeviceCard(
UserDevice device, _MyDeviceCategory category, bool isDark) {
final connected = controller.isConnected(device);
const connectedColor = Color(0xFF22C55E);
return Container(
margin: EdgeInsets.only(bottom: 10.h),
padding: EdgeInsets.all(18.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(18.r),
color: isDark
? (connected
? connectedColor.withValues(alpha: 0.04)
: Colors.white.withValues(alpha: 0.03))
: Colors.white,
border: Border.all(
color: connected
? connectedColor.withValues(alpha: 0.25)
: (isDark
? Colors.white.withValues(alpha: 0.05)
: Colors.black.withValues(alpha: 0.04)),
),
boxShadow: isDark
? null
: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.03),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
Row(
children: [
Container(
width: 44.w,
height: 44.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: connected
? [connectedColor, const Color(0xFF16A34A)]
: [const Color(0xFF6366F1), const Color(0xFF8B5CF6)],
),
),
child: Icon(
category.icon,
color: Colors.white,
size: 22.sp,
),
),
SizedBox(width: 14.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
device.devicename.isNotEmpty
? device.devicename
: 'deviceTypeUnknown'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w700,
color: isDark
? Colors.white
: const Color(0xFF1A1A2E),
),
),
),
if (connected) ...[
SizedBox(width: 8.w),
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 3.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.r),
color: connectedColor.withValues(alpha: 0.12),
),
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: 11.sp,
fontWeight: FontWeight.w500,
color: isDark
? Colors.white.withValues(alpha: 0.3)
: const Color(0xFF999999),
fontFamily: 'Menlo',
letterSpacing: 0.5,
),
),
],
),
),
GestureDetector(
onTap: () => _showUnbindDialog(device, isDark),
child: Container(
padding:
EdgeInsets.symmetric(horizontal: 12.w, vertical: 7.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.r),
border: Border.all(
color: isDark
? Colors.white.withValues(alpha: 0.08)
: Colors.black.withValues(alpha: 0.08),
),
),
child: Text(
'unbind'.tr,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w600,
color: isDark
? Colors.white.withValues(alpha: 0.4)
: const Color(0xFF999999),
),
),
),
),
],
),
if (device.license.isNotEmpty || device.cmei.isNotEmpty) ...[
SizedBox(height: 14.h),
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
color: isDark
? Colors.white.withValues(alpha: 0.03)
: const Color(0xFFF7F7FA),
),
child: Column(
children: [
if (device.license.isNotEmpty)
_buildFieldRow(
label: 'licenseCode'.tr,
value: device.license,
isDark: isDark),
if (device.license.isNotEmpty && device.cmei.isNotEmpty)
SizedBox(height: 8.h),
if (device.cmei.isNotEmpty)
_buildFieldRow(
label: 'CMEI', value: device.cmei, isDark: isDark),
],
),
),
],
],
),
);
}
Widget _buildFieldRow({
required String label,
required String value,
required bool isDark,
}) {
return Row(
children: [
SizedBox(
width: 50.w,
child: Text(
label,
style: TextStyle(
fontSize: 10.sp,
fontWeight: FontWeight.w600,
color: isDark
? Colors.white.withValues(alpha: 0.25)
: 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.withValues(alpha: 0.5)
: const Color(0xFF666666),
fontFamily: 'Menlo',
letterSpacing: 0.3,
),
overflow: TextOverflow.ellipsis,
),
),
],
);
}
Widget _buildAddDeviceButton(bool isDark) {
return Padding(
padding: EdgeInsets.fromLTRB(20.w, 8.h, 20.w, 20.h),
child: SizedBox(
width: double.infinity,
height: 48.h,
child: ElevatedButton.icon(
onPressed: controller.goToScan,
icon: Icon(Icons.add, size: 18.sp),
label: Text(
'addDevice'.tr,
style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600),
),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF0066CC),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(14.r),
),
elevation: 0,
),
),
),
);
}
void _showUnbindDialog(UserDevice device, bool isDark) {
Get.dialog(
AlertDialog(
backgroundColor:
isDark ? const Color(0xFF1C1C1E) : 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.unbindDevice(device);
},
child: Text(
'confirmUnbindAction'.tr,
style: const TextStyle(color: Colors.red),
),
),
],
),
);
}
}