From 0ea6bd419c85bcf02a795c91f1a23f33fab44d57 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Thu, 14 Aug 2025 12:08:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96ota=E5=8D=87=E7=BA=A7?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=BB=93=E6=9E=84=EF=BC=8C=E5=8E=BB=E5=A4=84?= =?UTF-8?q?ssl=E8=AF=81=E4=B9=A6=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/ota_upgrade_controller.dart | 872 +++++------------- .../ota_upgrade/services/device_checker.dart | 99 ++ .../services/download_manager.dart | 153 +++ .../services/ota_status_manager.dart | 43 + .../ota_upgrade/services/ui_manager.dart | 117 +++ .../ota_upgrade/services/version_manager.dart | 114 +++ 6 files changed, 735 insertions(+), 663 deletions(-) create mode 100644 lib/modules/ota_upgrade/services/device_checker.dart create mode 100644 lib/modules/ota_upgrade/services/download_manager.dart create mode 100644 lib/modules/ota_upgrade/services/ota_status_manager.dart create mode 100644 lib/modules/ota_upgrade/services/ui_manager.dart create mode 100644 lib/modules/ota_upgrade/services/version_manager.dart diff --git a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart index 91e28e89d..9e84456af 100644 --- a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart +++ b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart @@ -1,747 +1,293 @@ import 'package:classic_bluetooth/classic_bluetooth.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'package:voitrans/data/models/appconfig_model.dart'; -import '/routes/app_routes.dart'; -import 'package:logger/web.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:path/path.dart'; -import 'dart:async'; -import '../../../data/services/network/api.dart'; -import '../model/ota_upgrade_model.dart'; -import 'dart:io'; -import 'package:dio/dio.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:logger/web.dart'; import 'package:ota/ota.dart'; import '../../../data/services/ble_manager.dart'; -import '../../../data/models/appconfig.dart'; - +import '../services/version_manager.dart'; +import '../services/download_manager.dart'; +import '../services/device_checker.dart'; +import '../services/ui_manager.dart'; +import '../services/ota_status_manager.dart'; + +/// OTA升级主控制器 - 重构后的简化版本 +/// 负责协调各个服务,管理升级流程的整体状态 class OtaUpgradeController extends GetxController { - static const int NOT_STARTED = -1; // 未开始 - static const int VALIDATION_FILE = 0; // 验证文件 - static const int ONUPGRADE = 1; // 升级中 - static const int START_UPGRADING = 2; // 开始升级 - static const int UPGRADE_SUCCESS = 3; // 升级成功 - static const int UPGRADE_FAILED = 4; // 升级失败 - static const int CONNECTION_DISCONNECTION = 5; // 连接断开 - static const int CONNECTION_SUCCESS = 6; // 连接成功 - static const int DOWNLOADING_UP_FILES = 7; // 下载升级文件 - -// 错误信息,用于存储错误提示 - final RxString errorMessage = ''.obs; + // 服务依赖 + final _versionManager = VersionManager(); + final _downloadManager = DownloadManager(); + final _deviceChecker = DeviceChecker(); + final _uiManager = UiManager(); + final _bleManager = Get.find(); + final _jlota = Ota.instance; -// 最新版本号 + // 状态管理 + final RxString errorMessage = ''.obs; final RxString latestVersion = ''.obs; - // 最新更新内容 - final RxString updatedescription = ''.obs; - -// 当前设备固件版本号 final RxString currentVersion = ''.obs; - - String _address = ''; - -// 更新包地址,用于下载更新文件 - String updatepackageaddress = ''; - -// 下载进度,范围为 0.0 到 1.0 + final RxString detailedStatus = ''.obs; + final RxString updatedescription = ''.obs; // 添加缺失的更新描述属性 final RxDouble downloadProgress = 0.0.obs; - -// 是否有可用更新的状态 - final isUpdateAvailable = true.obs; - -// 当前升级进度,范围为 0.0 到 1.0 - // final RxDouble progress = 0.0.obs; - -// OTA 单例实例,用于处理升级相关逻辑 - final jlota = Ota.instance; - -// 是否正在下载更新包的状态 + final RxDouble progress = 0.0.obs; + final RxInt type = OtaStatusManager.NOT_STARTED.obs; + final RxBool isUpdateAvailable = true.obs; final RxBool isDownloading = false.obs; - -// 蓝牙管理服务,用于获取设备信息和通信 - final _bleManager = Get.find(); - final maxRetryAttempts = 3; // 最大重试次数 - int currentRetryCount = 0; - - final RxBool isFetchingDeviceInfo = true.obs; - - final RxDouble progress = 0.0.obs; // 控制器自己的可观察属性 - final RxInt type = OtaEvent.NOT_STARTED.obs; final RxBool isUpgrade = false.obs; - - // 新增:详细状态提示 - final RxString detailedStatus = ''.obs; + final RxBool isFetchingDeviceInfo = true.obs; final RxBool showDetailedStatus = false.obs; + String _address = ''; + String _updatePackageAddress = ''; + @override void onInit() { super.onInit(); - _getCurrentVersion(); + _initializeController(); } @override void onClose() { - jlota.onOtaClose(); + _jlota.onOtaClose(); _bleManager.setEnterOta(false); isUpgrade.value = false; super.onClose(); } - // 添加重试方法 - void retryConnection() { - errorMessage.value = ''; - _getCurrentVersion(); - } - - String getStatusMessage(int type) { - switch (type) { - case NOT_STARTED: // -1 - return "未开始".tr; - case VALIDATION_FILE: // 0 - return "验证文件中".tr; - case ONUPGRADE: // 1 - return "升级中".tr; - case START_UPGRADING: // 2 - return "开始升级".tr; - case UPGRADE_SUCCESS: // 3 - return "升级成功".tr; - case UPGRADE_FAILED: // 4 - return "升级失败".tr; - case CONNECTION_DISCONNECTION: // 5 - return "连接断开".tr; - case CONNECTION_SUCCESS: // 6 - return "连接成功".tr; - case DOWNLOADING_UP_FILES: // 7 (新增) - return "下载升级文件".tr; - default: - return "未知状态".tr; // 更安全的默认值 - } - } - - // 添加更新方法 - Future startUpdate() async { - try { - checkUpdate(); - - // 实现更新逻辑... - } catch (e) { - errorMessage.value = e.toString(); - Get.snackbar( - '更新失败', - e.toString(), - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[600], - colorText: Colors.white, - icon: const Icon( - Icons.error_outline, - color: Colors.white, - ), - ); - } + /// 初始化控制器 + /// 设置事件监听并加载版本信息 + Future _initializeController() async { + _listenToOtaEvents(); + await _loadVersionInfo(); } - Future _getCurrentVersion() async { - _listenToEvents(); + /// 加载版本信息 + /// 获取当前设备版本和最新版本信息,检查是否有更新 + Future _loadVersionInfo() async { try { isFetchingDeviceInfo.value = true; errorMessage.value = ''; - // 添加获取设备信息的重试机制 - DeviceInfo? deviceInfo = _bleManager.deviceInfo.value; - - // // 第一次检查设备信息 - // if (deviceInfo == null) { - // // 尝试重新获取设备信息 - // await _bleManager.fetchDeviceInfo(); - // } - - while (currentRetryCount < maxRetryAttempts) { - deviceInfo = _bleManager.deviceInfo.value; - - // 检查信息有效性 - if (deviceInfo != null && - deviceInfo.firmwareVersion.isNotEmpty && - deviceInfo.firmwareProductId.isNotEmpty) { - break; - } - - // 获取设备信息 - await _bleManager.fetchDeviceInfo(); - await Future.delayed(const Duration(seconds: 5)); - currentRetryCount++; - } - // 最终验证 - if (deviceInfo == null || - deviceInfo.firmwareVersion.isEmpty || - deviceInfo.firmwareProductId.isEmpty) { - throw "获取设备信息失败,请检查连接"; - } - // 拆分 firmwareVersion 并判断两边是否一致 - final firmwareParts = deviceInfo.firmwareVersion.split('.'); - - // 从第三个 `.` 开始分割 - final firstPart = firmwareParts.sublist(0, 3).join('.'); - final secondPart = firmwareParts.sublist(3).join('.'); - Logger().d("firstPart${firstPart},secondPart=${secondPart}"); - // 判断两部分是否一致 - if (firstPart != secondPart) { - throw "固件版本格式错误:${deviceInfo.firmwareVersion}"; - } - // 正常流程 - currentVersion.value = firstPart; - Logger().d('getequipmentinfo,id: ${deviceInfo.firmwareProductId}'); + // 获取当前版本信息 + final currentInfo = await _versionManager.getCurrentVersionInfo(); + currentVersion.value = currentInfo['currentVersion']; + // 获取蓝牙地址 final connectedDevices = await ClassicBluetooth.getConnectedDevices(); if (connectedDevices.isNotEmpty) { _address = connectedDevices.first.address; } - DBProduct? product = AppConfig.getproduct("DeapSound"); - - // // API请求 - // final response = await Api.getproduct({ - // "id": deviceInfo.firmwareProductId, - // }); - - // // 处理API响应 - // if (response['product'] == null) { - // throw const FormatException('API 响应格式错误'); - // } - - // final resp = OtaConfigResp.fromJson(response); - // Logger().d('getequipmentinfo,resp.systemversion: ${resp.systemversion}'); - latestVersion.value = product?.version ?? ""; - // updatedescription.value = resp.updatedescription!; - updatepackageaddress = product!.updatepackageaddress!; + // 获取最新版本信息 + final latestInfo = await _versionManager.getLatestVersionInfo(); + latestVersion.value = latestInfo['latestVersion']; + _updatePackageAddress = latestInfo['updatePackageAddress']; - // 版本检查 - bool hasUpdate = _isNewVersionAvailable(product.version!); + // 设置更新描述信息 + updatedescription.value = + latestInfo['updateDescription'] ?? '本次更新包含性能优化和错误修复。'; - isUpdateAvailable.value = hasUpdate; - } catch (e) { - // 统一错误处理 - errorMessage.value = e.toString(); - Get.snackbar( - '错误', - e.toString(), - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[600], - colorText: Colors.white, - icon: const Icon( - Icons.error_outline, - color: Colors.white, - ), + // 检查是否有更新 + isUpdateAvailable.value = _versionManager.isNewVersionAvailable( + currentVersion.value, + latestVersion.value, ); - // 重要:重新抛出异常以便上层处理(可选) + } catch (e) { + _handleError('获取版本信息失败', e.toString()); } finally { isFetchingDeviceInfo.value = false; } } - // 版本比较方法优化 - 支持升级和降级 - bool _isNewVersionAvailable(String latestVersion) { - try { - List currentParts = currentVersion.value - .split('.') - .map((e) => int.parse(e.trim())) - .toList(); - List latestParts = - latestVersion.split('.').map((e) => int.parse(e.trim())).toList(); - - // 确保两个版本号长度一致,短的用0补齐 - int maxLength = currentParts.length > latestParts.length - ? currentParts.length - : latestParts.length; - - while (currentParts.length < maxLength) { - currentParts.add(0); - } - while (latestParts.length < maxLength) { - latestParts.add(0); - } - - // 比较每一段,只要有不同就返回true(支持升级和降级) - for (int i = 0; i < maxLength; i++) { - if (latestParts[i] != currentParts[i]) { - return true; // 版本不同,允许更新(升级或降级) - } - } - - return false; // 版本完全相同,无需更新 - } catch (e) { - Logger().d('版本比较错误: $e'); - return false; - } - } - - // 版本更新 + /// 检查更新 + /// 显示更新确认对话框或错误提示 Future checkUpdate() async { try { - if (latestVersion.value != "") { - _showUpdateDialog(latestVersion.value); + if (latestVersion.value.isNotEmpty) { + _uiManager.showUpdateDialog(latestVersion.value, _startUpdateProcess); } else { - Get.snackbar( - '错误', - '获取最新版本信息失败', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[800], - colorText: Colors.white, - icon: const Icon( - Icons.error_outline, - color: Colors.white, - ), - ); + _uiManager.showErrorSnackbar('错误', '获取最新版本信息失败', + backgroundColor: Colors.orange[800]); } } catch (e) { - Get.snackbar( - '错误', - '获取版本信息失败', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[800], - colorText: Colors.white, - icon: const Icon( - Icons.error_outline, - color: Colors.white, - ), - ); + _uiManager.showErrorSnackbar('错误', '获取版本信息失败', + backgroundColor: Colors.orange[800]); } } - void _showUpdateDialog(String latest) async { - Logger().d('显示更新对话框'); - Get.dialog( - AlertDialog( - title: Text('发现新版本 $latest'), - content: Text('是否立即更新?'), - actions: [ - TextButton( - onPressed: () async { - Logger().d('取消按钮被点击'); - - Get.back(); - Future.delayed(Duration(milliseconds: 300)); - }, - child: Text('取消'), - ), - TextButton( - onPressed: () async { - Logger().d('更新按钮被点击'); - - Get.back(); - Future.delayed(Duration(milliseconds: 300)); - - // 检查设备电量 - if (!_checkBatteryLevel()) { - return; // 电量不足,不执行升级 - } - - startDownload(); - }, - child: Text('更新'), - ), - ], - ), - ); + /// 开始更新流程的公共方法 + /// 供视图调用的更新入口点 + Future startUpdate() async { + await _startUpdateProcess(); } -// 修改后的startDownload方法 - Future startDownload() async { - await _bleManager.setEnterOta(true); - await Future.delayed(const Duration(seconds: 2)); - jlota.init(); - - isDownloading.value = true; - showDetailedStatus.value = true; - detailedStatus.value = '准备下载固件包...'; - - int lastIndex = updatepackageaddress.lastIndexOf('/'); - String fileName = updatepackageaddress.substring(lastIndex + 1); - String directoryName = "ota_upgrades"; + /// 检查设备状态后开始下载和升级 + Future _startUpdateProcess() async { + // 检查设备电量 + if (!_deviceChecker.checkBatteryLevel()) { + return; + } - final dio = Dio(BaseOptions( - connectTimeout: const Duration(seconds: 5), - receiveTimeout: Duration.zero, - sendTimeout: const Duration(seconds: 5), - headers: {'Accept-Encoding': 'gzip'}, - )); + await _startDownload(); + } - type.value = DOWNLOADING_UP_FILES; + /// 开始下载固件 + /// 初始化OTA环境并下载固件包 + Future _startDownload() async { try { - detailedStatus.value = '正在获取下载目录...'; - final Directory? downloadsDir = await getApplicationSupportDirectory(); - if (downloadsDir == null) { - throw FileSystemException('无法访问下载目录'); - } - - final targetDir = - Directory('${downloadsDir.path}/Download/$directoryName'); - if (!await targetDir.exists()) { - detailedStatus.value = '创建下载目录...'; - await targetDir.create(recursive: true); - } - - final savePath = '${targetDir.path}/$fileName'; - - // 新增:检查同名文件逻辑 - detailedStatus.value = '检查本地文件...'; - bool existingSameFile = false; - try { - var files = await targetDir.list().toList(); - for (var entity in files) { - if (await FileSystemEntity.isFile(entity.path)) { - String name = basename(entity.path); // 使用path包中的basename - if (name == fileName) { - existingSameFile = true; - break; - } - } - } - } catch (e) { - Get.snackbar( - '错误', - '检查本地文件失败: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); - - return; - } - - if (existingSameFile) { - //Get.snackbar('文件已存在', '跳过下载,直接更新'); - detailedStatus.value = '文件已存在,准备升级...'; - startUpgrading(savePath); - return; - } - - // 新增:删除其他文件逻辑 - detailedStatus.value = '清理旧文件...'; - try { - var files = await targetDir.list().toList(); - for (var entity in files) { - if (await FileSystemEntity.isFile(entity.path)) { - await entity.delete(); - } - } - } catch (e) { - Get.snackbar( - '错误', - '删除旧文件失败: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); - //isUpgrade.value = false; - return; - } - - Logger().d('文件保存路径: $savePath'); - - detailedStatus.value = '开始下载固件包...'; - final options = Options( - receiveTimeout: Duration.zero, - followRedirects: true, - maxRedirects: 5, - ); - // 修改下载部分 - await dio.download( - updatepackageaddress, - savePath, - options: options, - onReceiveProgress: (received, total) { - if (total != -1) { - downloadProgress.value = received / total; - detailedStatus.value = - '正在下载固件包... ${(received / total * 100).toStringAsFixed(1)}%'; - } + await _bleManager.setEnterOta(true); + await Future.delayed(const Duration(seconds: 2)); + _jlota.init(); + + isDownloading.value = true; + showDetailedStatus.value = true; + type.value = OtaStatusManager.DOWNLOADING_UP_FILES; + + final filePath = await _downloadManager.downloadFirmware( + downloadUrl: _updatePackageAddress, + onProgress: (progress) { + downloadProgress.value = progress; + }, + onStatusUpdate: (status) { + detailedStatus.value = status; }, ); - File file = File(savePath); - if (await file.exists()) { - // Get.snackbar('下载完成', '文件已保存至: $savePath'); - detailedStatus.value = '下载完成,准备升级...'; - } else { - Get.snackbar( - '错误', - '文件未找到,请检查写入权限', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); - detailedStatus.value = '下载失败'; - showDetailedStatus.value = false; - return; - } + isDownloading.value = false; - startUpgrading(savePath); - return; - } on DioException catch (e) { - Get.snackbar( - '下载失败', - '错误: ${e.message}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); - } on FileSystemException catch (e) { - Get.snackbar( - '存储失败', - '无法写入文件: ${e.message}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); + await _startUpgrading(filePath); } catch (e) { isDownloading.value = false; detailedStatus.value = '下载失败,请检查网络连接'; showDetailedStatus.value = false; - Get.snackbar( - '下载失败', - '请检查网络连接: $e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); + _handleError('下载失败', e.toString()); Logger().d('下载失败: $e'); } } - // 开始升级 - void startUpgrading(String path) async { + /// 开始升级 + /// [filePath] 固件文件路径 + /// 启动OTA升级过程 + Future _startUpgrading(String filePath) async { detailedStatus.value = '准备设备连接...'; await Future.delayed(const Duration(milliseconds: 500)); detailedStatus.value = '正在连接设备...'; - jlota.startUpgrading(address: _address, filePath: path); - return; - // isUpgrade.value = false; + _jlota.startUpgrading(address: _address, filePath: filePath); } - // 停止升级 - void stopUpgrading() async { - jlota.onOtaClose(); - return; - // isUpgrade.value = false; + /// 停止升级 + /// 关闭OTA连接 + void stopUpgrading() { + _jlota.onOtaClose(); } - // 检查设备电量是否满足升级条件 - bool _checkBatteryLevel() { - try { - final deviceInfo = _bleManager.deviceInfo.value; - - if (deviceInfo == null) { - Get.snackbar( - '电量检查失败', - '无法获取设备信息,请检查设备连接', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[600], - colorText: Colors.white, - icon: Icon( - Icons.warning_amber, - color: Colors.white, - ), - ); - return false; - } + /// 重试连接 + /// 清除错误信息并重新加载版本信息 + void retryConnection() { + errorMessage.value = ''; + _loadVersionInfo(); + } - final leftBattery = deviceInfo.leftEarBattery; - final rightBattery = deviceInfo.rightEarBattery; - - // 检查左右耳电量是否都大于30% - if (leftBattery <= 30 || rightBattery <= 30) { - String message = '设备电量不足,无法进行固件升级\n'; - message += '升级要求:左右耳电量均需大于30%\n'; - message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n'; - message += '请充电后再试'; - - Get.dialog( - AlertDialog( - title: Text( - '电量不足', - style: TextStyle( - color: Colors.orange[700], - fontWeight: FontWeight.w600, - ), - ), - content: Text( - message, - style: TextStyle( - color: Colors.orange[600], - ), - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - style: TextButton.styleFrom( - foregroundColor: Colors.orange[700], - ), - child: const Text('确定'), - ), - ], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide( - color: Colors.orange.withValues(alpha: 0.3), - width: 1, - ), - ), - backgroundColor: Colors.orange[50], - ), - ); - return false; - } + /// 获取状态描述 + /// [type] 状态类型 + /// 返回对应的状态描述文本 + String getStatusMessage(int type) { + return OtaStatusManager.getStatusMessage(type); + } - return true; - } catch (e) { - Get.snackbar( - '电量检查失败', - '检查设备电量时发生错误:$e', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange[600], - colorText: Colors.white, - icon: const Icon( - Icons.warning_amber, - color: Colors.white, - ), - ); - return false; - } + /// 监听OTA事件 + /// 设置OTA事件流的监听器 + void _listenToOtaEvents() { + _jlota.eventStream.listen((event) { + _handleOtaEvent(event); + }); } - void _listenToEvents() { - Ota.instance.eventStream.listen((event) { - if (event.event == 'onProgress' && - event.type != null && - event.progress != null) { - progress.value = event.progress!; - type.value = event.type!; - isUpgrade.value = true; // 开始升级 - detailedStatus.value = - '升级进度: ${(event.progress! * 100).toStringAsFixed(1)}%'; - } - if (event.event == 'onStartOTA') { - type.value = START_UPGRADING; - isUpgrade.value = true; // 开始升级 + /// 处理OTA事件 + /// [event] OTA事件对象 + /// 根据不同的事件类型执行相应的处理逻辑 + void _handleOtaEvent(dynamic event) { + switch (event.event) { + case 'onProgress': + if (event.type != null && event.progress != null) { + progress.value = event.progress!; + type.value = event.type!; + isUpgrade.value = true; + detailedStatus.value = + '升级进度: ${(event.progress! * 100).toStringAsFixed(1)}%'; + } + break; + case 'onStartOTA': + type.value = OtaStatusManager.START_UPGRADING; + isUpgrade.value = true; detailedStatus.value = 'OTA升级已开始...'; - } - if (event.event == 'onStopOTA') { - type.value = UPGRADE_SUCCESS; - isUpgrade.value = false; - detailedStatus.value = '升级成功完成'; - showDetailedStatus.value = false; - - // 升级成功,更新版本信息显示 - currentVersion.value = latestVersion.value; - isUpdateAvailable.value = false; - - // 显示升级成功对话框,引导用户重新配对 - Get.dialog( - AlertDialog( - title: Row( - children: [ - Icon( - Icons.check_circle, - color: Colors.green[600], - size: 28, - ), - SizedBox(width: 12.w), - Text( - '升级成功', - style: TextStyle( - color: Colors.green[700], - fontWeight: FontWeight.w600, - ), - ), - ], - ), - content: Text( - '固件升级已完成!\n\n设备需要重新配对才能正常使用,现在跳转到配对页面吗?', - style: TextStyle( - fontSize: 16, - height: 1.4, - ), - ), - actions: [ - TextButton( - onPressed: () { - Get.back(); // 关闭对话框 - }, - child: Text( - '稍后配对', - style: TextStyle(color: Colors.grey[600]), - ), - ), - ElevatedButton( - onPressed: () { - Get.back(); // 关闭对话框 - Get.toNamed(Routes.pairing); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green[600], - foregroundColor: Colors.white, - ), - child: Text('立即配对'), - ), - ], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - barrierDismissible: false, // 防止用户意外关闭 - ); - } - if (event.event == 'onError') { - type.value = UPGRADE_FAILED; - isUpgrade.value = false; - detailedStatus.value = '升级失败,错误码:${event.code}'; - showDetailedStatus.value = false; - Get.snackbar( - '升级失败', - '请检查设备连接或升级包,错误码:${event.code}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[600], - colorText: Colors.white, - icon: Icon( - Icons.error_outline, - color: Colors.white, - ), - ); - } - if (event.event == 'NOCONNECTION') { - type.value = CONNECTION_DISCONNECTION; - isUpgrade.value = false; - detailedStatus.value = '设备连接断开'; - showDetailedStatus.value = false; - } - if (event.event == 'CONNECTION_SUCCESS') { - type.value = CONNECTION_SUCCESS; - isUpgrade.value = true; // 开始升级 - detailedStatus.value = '设备连接成功'; - } - }); + break; + case 'onStopOTA': + _handleUpgradeSuccess(); + break; + case 'onError': + _handleUpgradeError(event.code); + break; + case 'NOCONNECTION': + _handleConnectionLost(); + break; + case 'CONNECTION_SUCCESS': + _handleConnectionSuccess(); + break; + } + } + + /// 处理升级成功 + /// 更新状态并显示成功对话框 + void _handleUpgradeSuccess() { + type.value = OtaStatusManager.UPGRADE_SUCCESS; + isUpgrade.value = false; + detailedStatus.value = '升级成功完成'; + showDetailedStatus.value = false; + + // 更新版本信息 + currentVersion.value = latestVersion.value; + isUpdateAvailable.value = false; + + // 显示成功对话框 + _uiManager.showUpgradeSuccessDialog(); + } + + /// 处理升级错误 + /// [errorCode] 错误代码 + /// 更新状态并显示错误提示 + void _handleUpgradeError(dynamic errorCode) { + type.value = OtaStatusManager.UPGRADE_FAILED; + isUpgrade.value = false; + detailedStatus.value = '升级失败,错误码:$errorCode'; + showDetailedStatus.value = false; + _uiManager.showErrorSnackbar( + '升级失败', + '请检查设备连接或升级包,错误码:$errorCode', + ); + } + + /// 处理连接断开 + /// 更新连接状态 + void _handleConnectionLost() { + type.value = OtaStatusManager.CONNECTION_DISCONNECTION; + isUpgrade.value = false; + detailedStatus.value = '设备连接断开'; + showDetailedStatus.value = false; + } + + /// 处理连接成功 + /// 更新连接状态 + void _handleConnectionSuccess() { + type.value = OtaStatusManager.CONNECTION_SUCCESS; + isUpgrade.value = true; + detailedStatus.value = '设备连接成功'; + } + + /// 统一错误处理 + /// [title] 错误标题 + /// [message] 错误信息 + /// 设置错误状态并显示错误提示 + void _handleError(String title, String message) { + errorMessage.value = message; + _uiManager.showErrorSnackbar(title, message); } } diff --git a/lib/modules/ota_upgrade/services/device_checker.dart b/lib/modules/ota_upgrade/services/device_checker.dart new file mode 100644 index 000000000..7b3fb2531 --- /dev/null +++ b/lib/modules/ota_upgrade/services/device_checker.dart @@ -0,0 +1,99 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../../data/services/ble_manager.dart'; + +/// 设备检查服务 +/// 负责检查设备状态,如电量检查等 +class DeviceChecker { + final BleManager _bleManager = Get.find(); + + /// 检查设备电量是否满足升级条件 + /// 返回true表示电量充足,可以进行升级 + /// 返回false表示电量不足,不能进行升级 + bool checkBatteryLevel() { + try { + final deviceInfo = _bleManager.deviceInfo.value; + + if (deviceInfo == null) { + _showBatteryCheckFailedSnackbar('无法获取设备信息,请检查设备连接'); + return false; + } + + final leftBattery = deviceInfo.leftEarBattery; + final rightBattery = deviceInfo.rightEarBattery; + + // 检查左右耳电量是否都大于30% + if (leftBattery <= 30 || rightBattery <= 30) { + _showLowBatteryDialog(leftBattery, rightBattery); + return false; + } + + return true; + } catch (e) { + _showBatteryCheckFailedSnackbar('检查设备电量时发生错误:$e'); + return false; + } + } + + /// 显示电量不足对话框 + /// [leftBattery] 左耳电量 + /// [rightBattery] 右耳电量 + void _showLowBatteryDialog(int leftBattery, int rightBattery) { + String message = '设备电量不足,无法进行固件升级\n'; + message += '升级要求:左右耳电量均需大于30%\n'; + message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n'; + message += '请充电后再试'; + + Get.dialog( + AlertDialog( + title: Text( + '电量不足', + style: TextStyle( + color: Colors.orange[700], + fontWeight: FontWeight.w600, + ), + ), + content: Text( + message, + style: TextStyle( + color: Colors.orange[600], + ), + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + style: TextButton.styleFrom( + foregroundColor: Colors.orange[700], + ), + child: const Text('确定'), + ), + ], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide( + color: Colors.orange.withValues(alpha: 0.3), + width: 1, + ), + ), + backgroundColor: Colors.orange[50], + ), + ); + } + + /// 显示电量检查失败提示 + /// [message] 错误信息 + void _showBatteryCheckFailedSnackbar(String message) { + Get.snackbar( + '电量检查失败', + message, + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange[600], + colorText: Colors.white, + icon: const Icon( + Icons.warning_amber, + color: Colors.white, + ), + ); + } +} \ No newline at end of file diff --git a/lib/modules/ota_upgrade/services/download_manager.dart b/lib/modules/ota_upgrade/services/download_manager.dart new file mode 100644 index 000000000..b98aeeaac --- /dev/null +++ b/lib/modules/ota_upgrade/services/download_manager.dart @@ -0,0 +1,153 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart'; +import 'package:logger/web.dart'; + +/// 文件下载管理器 +/// 负责处理固件包的下载、文件管理等功能 +class DownloadManager { + late final Dio _dio; + + /// 构造函数 + /// 初始化Dio实例并配置SSL证书处理 + DownloadManager() { + _dio = Dio(BaseOptions( + connectTimeout: const Duration(seconds: 30), // 增加连接超时时间 + receiveTimeout: Duration.zero, + sendTimeout: const Duration(seconds: 30), // 增加发送超时时间 + headers: {'Accept-Encoding': 'gzip'}, + )); + + // 配置SSL证书验证 + (_dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () { + final client = HttpClient(); + + // 处理SSL证书验证 + client.badCertificateCallback = + (X509Certificate cert, String host, int port) { + // 在生产环境中,应该进行适当的证书验证 + // 这里为了解决证书过期问题,暂时跳过验证 + // 建议:在生产环境中实现更安全的证书验证逻辑 + Logger().w('SSL证书验证跳过: $host:$port'); + return true; + }; + + return client; + }; + } + + /// 下载固件包 + /// [downloadUrl] 下载地址 + /// [onProgress] 下载进度回调 + /// [onStatusUpdate] 状态更新回调 + /// 返回下载文件的本地路径 + /// 抛出异常如果下载失败 + Future downloadFirmware({ + required String downloadUrl, + required Function(double) onProgress, + required Function(String) onStatusUpdate, + }) async { + onStatusUpdate('准备下载固件包...'); + + final fileName = downloadUrl.substring(downloadUrl.lastIndexOf('/') + 1); + const directoryName = "ota_upgrades"; + + // 获取下载目录 + onStatusUpdate('正在获取下载目录...'); + final Directory? downloadsDir = await getApplicationSupportDirectory(); + if (downloadsDir == null) { + throw FileSystemException('无法访问下载目录'); + } + + final targetDir = Directory('${downloadsDir.path}/Download/$directoryName'); + if (!await targetDir.exists()) { + onStatusUpdate('创建下载目录...'); + await targetDir.create(recursive: true); + } + + final savePath = '${targetDir.path}/$fileName'; + + // 检查是否已存在同名文件 + onStatusUpdate('检查本地文件...'); + if (await _checkExistingFile(targetDir, fileName)) { + onStatusUpdate('文件已存在,准备升级...'); + return savePath; + } + + // 清理旧文件 + onStatusUpdate('清理旧文件...'); + await _cleanOldFiles(targetDir); + + // 开始下载 + onStatusUpdate('开始下载固件包...'); + Logger().d('文件保存路径: $savePath'); + + final options = Options( + receiveTimeout: Duration.zero, + followRedirects: true, + maxRedirects: 5, + ); + + await _dio.download( + downloadUrl, + savePath, + options: options, + onReceiveProgress: (received, total) { + if (total != -1) { + final progress = received / total; + onProgress(progress); + onStatusUpdate('正在下载固件包... ${(progress * 100).toStringAsFixed(1)}%'); + } + }, + ); + + // 验证文件是否下载成功 + final file = File(savePath); + if (!await file.exists()) { + throw FileSystemException('文件下载失败,请检查写入权限'); + } + + onStatusUpdate('下载完成,准备升级...'); + return savePath; + } + + /// 检查是否存在同名文件 + /// [targetDir] 目标目录 + /// [fileName] 文件名 + /// 返回true表示文件已存在 + Future _checkExistingFile(Directory targetDir, String fileName) async { + try { + final files = await targetDir.list().toList(); + for (var entity in files) { + if (await FileSystemEntity.isFile(entity.path)) { + final name = basename(entity.path); + if (name == fileName) { + return true; + } + } + } + return false; + } catch (e) { + Logger().d('检查本地文件失败: $e'); + return false; + } + } + + /// 清理旧文件 + /// [targetDir] 目标目录 + /// 抛出异常如果清理失败 + Future _cleanOldFiles(Directory targetDir) async { + try { + final files = await targetDir.list().toList(); + for (var entity in files) { + if (await FileSystemEntity.isFile(entity.path)) { + await entity.delete(); + } + } + } catch (e) { + throw Exception('删除旧文件失败: $e'); + } + } +} diff --git a/lib/modules/ota_upgrade/services/ota_status_manager.dart b/lib/modules/ota_upgrade/services/ota_status_manager.dart new file mode 100644 index 000000000..38142633f --- /dev/null +++ b/lib/modules/ota_upgrade/services/ota_status_manager.dart @@ -0,0 +1,43 @@ +import 'package:get/get.dart'; + +/// OTA升级状态管理器 +/// 负责管理OTA升级过程中的各种状态常量和状态描述 +class OtaStatusManager { + static const int NOT_STARTED = -1; // 未开始 + static const int VALIDATION_FILE = 0; // 验证文件 + static const int ONUPGRADE = 1; // 升级中 + static const int START_UPGRADING = 2; // 开始升级 + static const int UPGRADE_SUCCESS = 3; // 升级成功 + static const int UPGRADE_FAILED = 4; // 升级失败 + static const int CONNECTION_DISCONNECTION = 5; // 连接断开 + static const int CONNECTION_SUCCESS = 6; // 连接成功 + static const int DOWNLOADING_UP_FILES = 7; // 下载升级文件 + + /// 获取状态描述信息 + /// [type] 状态类型 + /// 返回对应的状态描述文本 + static String getStatusMessage(int type) { + switch (type) { + case NOT_STARTED: + return "未开始".tr; + case VALIDATION_FILE: + return "验证文件中".tr; + case ONUPGRADE: + return "升级中".tr; + case START_UPGRADING: + return "开始升级".tr; + case UPGRADE_SUCCESS: + return "升级成功".tr; + case UPGRADE_FAILED: + return "升级失败".tr; + case CONNECTION_DISCONNECTION: + return "连接断开".tr; + case CONNECTION_SUCCESS: + return "连接成功".tr; + case DOWNLOADING_UP_FILES: + return "下载升级文件".tr; + default: + return "未知状态".tr; + } + } +} \ No newline at end of file diff --git a/lib/modules/ota_upgrade/services/ui_manager.dart b/lib/modules/ota_upgrade/services/ui_manager.dart new file mode 100644 index 000000000..a2f4288d4 --- /dev/null +++ b/lib/modules/ota_upgrade/services/ui_manager.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:logger/web.dart'; +import '/routes/app_routes.dart'; + +/// UI交互管理器 +/// 负责处理所有与用户界面交互相关的功能 +class UiManager { + /// 显示更新确认对话框 + /// [latestVersion] 最新版本号 + /// [onConfirm] 确认更新的回调函数 + void showUpdateDialog(String latestVersion, VoidCallback onConfirm) { + Logger().d('显示更新对话框'); + Get.dialog( + AlertDialog( + title: Text('发现新版本 $latestVersion'), + content: const Text('是否立即更新?'), + actions: [ + TextButton( + onPressed: () { + Logger().d('取消按钮被点击'); + Get.back(); + }, + child: const Text('取消'), + ), + TextButton( + onPressed: () { + Logger().d('更新按钮被点击'); + Get.back(); + Future.delayed(const Duration(milliseconds: 300)); + onConfirm(); + }, + child: const Text('更新'), + ), + ], + ), + ); + } + + /// 显示升级成功对话框 + /// 引导用户进行设备重新配对 + void showUpgradeSuccessDialog() { + Get.dialog( + AlertDialog( + title: Row( + children: [ + Icon( + Icons.check_circle, + color: Colors.green[600], + size: 28, + ), + SizedBox(width: 12.w), + Text( + '升级成功', + style: TextStyle( + color: Colors.green[700], + fontWeight: FontWeight.w600, + ), + ), + ], + ), + content: const Text( + '固件升级已完成!\n\n设备需要重新配对才能正常使用,现在跳转到配对页面吗?', + style: TextStyle( + fontSize: 16, + height: 1.4, + ), + ), + actions: [ + TextButton( + onPressed: () { + Get.back(); + }, + child: Text( + '稍后配对', + style: TextStyle(color: Colors.grey[600]), + ), + ), + ElevatedButton( + onPressed: () { + Get.back(); + Get.toNamed(Routes.pairing); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[600], + foregroundColor: Colors.white, + ), + child: const Text('立即配对'), + ), + ], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + barrierDismissible: false, + ); + } + + /// 显示错误提示 + /// [title] 错误标题 + /// [message] 错误信息 + /// [backgroundColor] 背景颜色,可选 + void showErrorSnackbar(String title, String message, {Color? backgroundColor}) { + Get.snackbar( + title, + message, + snackPosition: SnackPosition.BOTTOM, + backgroundColor: backgroundColor ?? Colors.red[600], + colorText: Colors.white, + icon: const Icon( + Icons.error_outline, + color: Colors.white, + ), + ); + } +} \ No newline at end of file diff --git a/lib/modules/ota_upgrade/services/version_manager.dart b/lib/modules/ota_upgrade/services/version_manager.dart new file mode 100644 index 000000000..579da63c6 --- /dev/null +++ b/lib/modules/ota_upgrade/services/version_manager.dart @@ -0,0 +1,114 @@ +import 'package:logger/web.dart'; +import '../../../data/services/ble_manager.dart'; +import '../../../data/models/appconfig.dart'; +import '../../../data/models/appconfig_model.dart'; +import 'package:get/get.dart'; + +/// 版本管理服务 +/// 负责处理设备版本信息获取、版本比较等功能 +class VersionManager { + final BleManager _bleManager = Get.find(); + final int maxRetryAttempts = 3; + int currentRetryCount = 0; + + /// 获取当前设备版本信息 + /// 返回包含当前版本、产品ID和设备信息的Map + /// 抛出异常如果获取失败 + Future> getCurrentVersionInfo() async { + DeviceInfo? deviceInfo = _bleManager.deviceInfo.value; + + // 重试机制获取设备信息 + while (currentRetryCount < maxRetryAttempts) { + deviceInfo = _bleManager.deviceInfo.value; + + if (deviceInfo != null && + deviceInfo.firmwareVersion.isNotEmpty && + deviceInfo.firmwareProductId.isNotEmpty) { + break; + } + + await _bleManager.fetchDeviceInfo(); + await Future.delayed(const Duration(seconds: 5)); + currentRetryCount++; + } + + if (deviceInfo == null || + deviceInfo.firmwareVersion.isEmpty || + deviceInfo.firmwareProductId.isEmpty) { + throw "获取设备信息失败,请检查连接"; + } + + // 验证固件版本格式 + final firmwareParts = deviceInfo.firmwareVersion.split('.'); + final firstPart = firmwareParts.sublist(0, 3).join('.'); + final secondPart = firmwareParts.sublist(3).join('.'); + + Logger().d("firstPart: $firstPart, secondPart: $secondPart"); + + if (firstPart != secondPart) { + throw "固件版本格式错误:${deviceInfo.firmwareVersion}"; + } + + return { + 'currentVersion': firstPart, + 'productId': deviceInfo.firmwareProductId, + 'deviceInfo': deviceInfo, + }; + } + + /// 获取最新版本信息 + /// 返回包含最新版本、更新描述和更新包地址的Map + /// 抛出异常如果获取失败 + Future> getLatestVersionInfo() async { + DBProduct? product = AppConfig.getproduct("DeapSound"); + + if (product == null) { + throw "无法获取产品信息"; + } + + return { + 'latestVersion': product.version ?? "", + 'updateDescription': product.updatedescription ?? "", + 'updatePackageAddress': product.updatepackageaddress ?? "", + }; + } + + /// 比较版本号,判断是否有新版本可用 + /// [currentVersion] 当前版本号 + /// [latestVersion] 最新版本号 + /// 返回true表示有新版本可用(支持升级和降级) + bool isNewVersionAvailable(String currentVersion, String latestVersion) { + try { + List currentParts = currentVersion + .split('.') + .map((e) => int.parse(e.trim())) + .toList(); + List latestParts = + latestVersion.split('.').map((e) => int.parse(e.trim())).toList(); + + // 确保两个版本号长度一致,短的用0补齐 + int maxLength = currentParts.length > latestParts.length + ? currentParts.length + : latestParts.length; + + while (currentParts.length < maxLength) { + currentParts.add(0); + } + while (latestParts.length < maxLength) { + latestParts.add(0); + } + + // 比较每一段,只要有不同就返回true(支持升级和降级) + for (int i = 0; i < maxLength; i++) { + if (latestParts[i] != currentParts[i]) { + return true; + } + } + + return false; + } catch (e) { + Logger().d('版本比较错误: $e'); + return false; + } + } +} \ No newline at end of file