diff --git a/assets/langs/en_US.json b/assets/langs/en_US.json index acd7b41..38110f4 100644 --- a/assets/langs/en_US.json +++ b/assets/langs/en_US.json @@ -192,7 +192,7 @@ "人员资料": "User information", "实时体征": "Real-time vitals", "消息回看": "Message review", - "睡眠报告": "Health report", + "睡眠报告": "Sleep report", "首页展示": "Home display", "设备详情": "Device details", "重命名": "Rename", @@ -255,7 +255,7 @@ "呼吸": "Respiration", "呼吸暂停": "Apnea", "请保持静止": "Please remain still", - "睡眠报告": "Health report", + "睡眠报告": "Sleep report", "修改人员名称": "Modify user name", "在线": "Online", "离线": "Offline", diff --git a/assets/mhlangs/en_US.json b/assets/mhlangs/en_US.json index 17a5bc8..a8cac56 100644 --- a/assets/mhlangs/en_US.json +++ b/assets/mhlangs/en_US.json @@ -298,7 +298,6 @@ "请先": "Please", "登录": "Login", "后,再查看睡眠报告": "to view sleep reports", - "睡眠报告": "Health Report", "暂无数据": "No Data", "发现新版本": "New Version Available", "知道了": "Back", diff --git a/lib/common/util/BluetoothFirmwareUpdater.dart b/lib/common/util/BluetoothFirmwareUpdater.dart new file mode 100644 index 0000000..11f557c --- /dev/null +++ b/lib/common/util/BluetoothFirmwareUpdater.dart @@ -0,0 +1,441 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:easydevice/easydevice.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:http/http.dart' as http; +import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:ef/ef.dart'; // THapp 所在的包 + +// 固件升级配置(区分平台) +class UpgradeConfig { + static const int androidInitialFps = 50; + static const int androidSubsequentFps = 150; + static const int androidMaxFrameSize = 120; + + static const int iosInitialFps = 30; + static const int iosSubsequentFps = 100; + static const int iosMaxFrameSize = 100; + + static const Duration initialPhaseDuration = Duration(seconds: 15); + + static int get maxFrameSize => + Platform.isAndroid ? androidMaxFrameSize : iosMaxFrameSize; + + static int getFps(Duration elapsedTime) { + if (elapsedTime <= initialPhaseDuration) { + return Platform.isAndroid ? androidInitialFps : iosInitialFps; + } else { + return Platform.isAndroid ? androidSubsequentFps : iosSubsequentFps; + } + } + + static Duration getFrameInterval(Duration elapsedTime) { + final fps = getFps(elapsedTime); + return Duration(milliseconds: (1000 / fps).round()); + } +} + +/// 升级任务状态 +class UpgradeTask { + final String mac; + final THapp thapp; + int progress; + String status; + Completer completer; + DateTime startTime; + DateTime? endTime; + + UpgradeTask({ + required this.mac, + required this.thapp, + this.progress = 0, + this.status = 'waiting', + required this.completer, + required this.startTime, + }); +} + +/// 多设备蓝牙固件升级管理器 +class MultiDeviceFirmwareUpdater { + static final MultiDeviceFirmwareUpdater _instance = + MultiDeviceFirmwareUpdater._internal(); + factory MultiDeviceFirmwareUpdater() => _instance; + MultiDeviceFirmwareUpdater._internal(); + + final MHTBlueToothController _btController = Get.find(); + + final Map _upgradeTasks = {}; + final Map _firmwareDataCache = {}; + final int _maxConcurrentUpgrades = 2; + int _currentConcurrentUpgrades = 0; + + Future startUpgrade({ + required THapp thapp, + required String mac, + required String firmwareUrl, + }) async { + if (_upgradeTasks.containsKey(mac)) { + final existingTask = _upgradeTasks[mac]!; + if (existingTask.status == 'upgrading' || + existingTask.status == 'downloading') { + throw Exception("[蓝牙指令执行日志] 设备 $mac 正在升级中".tr); + } + } + + final completer = Completer(); + final task = UpgradeTask( + mac: mac, + thapp: thapp, + completer: completer, + startTime: DateTime.now(), + ); + + _upgradeTasks[mac] = task; + _updateUiProgress(mac, 0, 'waiting'); + + _processUpgradeQueue(); + + return completer.future; + } + + void _processUpgradeQueue() { + final waitingTasks = + _upgradeTasks.values.where((task) => task.status == 'waiting').toList(); + + for (final task in waitingTasks) { + if (_currentConcurrentUpgrades < _maxConcurrentUpgrades) { + _currentConcurrentUpgrades++; + _startSingleUpgrade(task); + } + //screen shots + } + } + + Future _startSingleUpgrade(UpgradeTask task) async { + try { + task.status = 'downloading'; + + if (!task.thapp.isConnected) { + throw Exception("[蓝牙指令执行日志] 设备未连接".tr); + } + + if (!_firmwareDataCache.containsKey(task.mac)) { + await _downloadFirmware(task); + } + + // await _sendCommand(task.thapp, "blog disable"); + _sendCommand(task.thapp, "blog disable"); + await Future.delayed(Duration(milliseconds: 200)); + + task.status = 'upgrading'; + _updateUiProgress(task.mac, 0, 'upgrading'); + + await _sendFirmwareFrames(task); + + await _sendCommand(task.thapp, "blog enable"); + await _verifyFirmware(task); + + task.status = 'completed'; + task.endTime = DateTime.now(); + _updateUiProgress(task.mac, 100, 'completed'); + + TopSlideNotification.show(Get.context!, + text: "设备 ${task.mac} 固件升级成功".tr, textColor: Colors.green); + + task.completer.complete(); + } catch (e, stack) { + ef.log("[蓝牙指令执行日志] 设备 ${task.mac} 升级失败: $e\n$stack"); + + task.status = 'failed'; + task.endTime = DateTime.now(); + _updateUiProgress(task.mac, -1, 'failed'); + + // TopSlideNotification.show(Get.context!, + // text: "设备 ${task.mac} 升级失败".tr, textColor: Colors.red); + + task.completer.completeError(e); + } finally { + _currentConcurrentUpgrades--; + _cleanupTask(task.mac); + _processUpgradeQueue(); + + try { + await _sendCommand(task.thapp, "blog enable"); + } catch (e, stack) { + ef.log("[蓝牙指令执行日志] 重新开启日志失败: $e\n$stack"); + } + } + } + + Future _downloadFirmware(UpgradeTask task) async { + try { + final firmwareUrl = _btController.currentUpgradeUrl; + if (firmwareUrl == null) { + throw Exception("[蓝牙指令执行日志] 固件URL为空".tr); + } + + final response = await http.get(Uri.parse(firmwareUrl)); + + if (response.statusCode != 200) { + throw Exception("[蓝牙指令执行日志] 固件下载失败,状态码:${response.statusCode}".tr); + } + + _firmwareDataCache[task.mac] = response.bodyBytes; + } catch (e, stack) { + ef.log("[蓝牙指令执行日志] 下载固件失败: $e\n$stack"); + throw Exception("[蓝牙指令执行日志] 固件下载失败".tr); + } + } + + Future _sendFirmwareFrames(UpgradeTask task) async { + final firmwareData = _firmwareDataCache[task.mac]; + if (firmwareData == null) { + throw Exception("[蓝牙指令执行日志] 固件数据为空".tr); + } + + final totalFrames = + (firmwareData.length / UpgradeConfig.maxFrameSize).ceil(); + final stopwatch = Stopwatch()..start(); + DateTime lastFrameTime = DateTime.now(); + + for (int sentFrames = 0; sentFrames < totalFrames; sentFrames++) { + if (task.status == 'cancelled') { + throw Exception("[蓝牙指令执行日志] 升级已取消".tr); + } + + final elapsed = stopwatch.elapsed; + final frameInterval = UpgradeConfig.getFrameInterval(elapsed); + final now = DateTime.now(); + + if (now.difference(lastFrameTime) < frameInterval) { + await Future.delayed(Duration(milliseconds: 10)); + continue; + } + + final offset = sentFrames * UpgradeConfig.maxFrameSize; + final end = offset + UpgradeConfig.maxFrameSize; + final frameData = firmwareData.sublist( + offset, + end > firmwareData.length ? firmwareData.length : end, + ); + + final base64Data = base64Encode(frameData); + final mmxCommand = + '''mmx path="/root/mtd/update" write base64 len=${frameData.length} offset=$offset data="$base64Data"'''; + + try { + await _sendCommand(task.thapp, mmxCommand); + } catch (e, stack) { + ef.log("[蓝牙指令执行日志] 发送固件帧失败: $e\n$stack"); + rethrow; + } + + final waitTime = frameInterval - now.difference(lastFrameTime); + if (waitTime > Duration.zero) await Future.delayed(waitTime); + + final progress = ((sentFrames + 1) / totalFrames * 100).round(); + _updateUiProgress(task.mac, progress, 'upgrading'); + + lastFrameTime = now; + } + + stopwatch.stop(); + } + + Future _verifyFirmware(UpgradeTask task) async { + try { + final success = await _sendVotaCommand(task.thapp); + + if (!success) { + throw Exception("[蓝牙指令执行日志] 固件验证失败".tr); + } + + await Future.delayed(Duration(seconds: 10)); + + bool deviceRestarted = false; + for (int i = 0; i < 10; i++) { + if (!task.thapp.isConnected) { + deviceRestarted = true; + break; + } + await Future.delayed(Duration(seconds: 1)); + } + + if (!deviceRestarted) { + throw Exception("[蓝牙指令执行日志] 设备未重启,可能升级失败".tr); + } + } catch (e, stack) { + ef.log("[蓝牙指令执行日志] 固件校验失败: $e\n$stack"); + throw Exception("[蓝牙指令执行日志] 固件校验失败".tr); + } +} + + Future _sendCommand(THapp thapp, String command, + {bool needResponse = false}) async { + try { + if (needResponse) { + // 需要响应的命令(如 vota 命令) + return await thapp.send(command, true, // withResponse = true + (log) { + ef.log("[蓝牙指令执行日志] ${log.log},指令为:$command"); + + // 这里根据具体命令判断是否成功完成 + if (log.log.contains("SUCCESS") || + log.log.contains("OK") || + log.log.contains("COMPLETE") || + log.log.contains("successful")) { + log.result = true; // 设置成功结果 + return true; // 停止监听 + } + + // 如果包含错误信息,也返回完成 + if (log.log.contains("ERROR") || + log.log.contains("FAIL") || + log.log.contains("failed")) { + log.result = false; // 设置失败结果 + return true; // 停止监听 + } + + return false; // 继续监听 + }, + 0, // retry = 0 + 15000 // timeoutms = 15000(15秒超时) + ); + } else { + // 不需要响应的命令(如 mmx write、blog disable/enable) + final success = await thapp.send( + command, + false, // withResponse = false + null, // 不需要回调 + 0, // retry = 0 + 5000 // timeoutms = 5000(5秒超时) + ); + + if (!success) { + ef.log("[蓝牙指令执行日志] 命令发送可能失败: $command"); + } + return success; + } + } catch (e, stack) { + ef.log("[蓝牙指令执行日志] 发送命令失败: $e\n$stack"); + throw Exception("[蓝牙指令执行日志] 发送命令失败".tr); + } + } + + void _updateUiProgress(String mac, int progress, String status) { + if (Get.isRegistered()) { + final controller = Get.find(); + if (controller.localUpgradeMac.containsKey(mac)) { + controller.localUpgradeMac[mac] = { + ...controller.localUpgradeMac[mac]!, + "progress": progress, + "status": status, + "updateTime": DateTime.now().millisecondsSinceEpoch, + }; + controller.update(); + } + } + } + + void _cleanupTask(String mac) { + _firmwareDataCache.remove(mac); + } + + void cancelUpgrade(String mac) { + final task = _upgradeTasks[mac]; + if (task != null) { + task.status = 'cancelled'; + _updateUiProgress(mac, -1, 'cancelled'); + task.completer.completeError(Exception("[蓝牙指令执行日志] 升级已取消".tr)); + + TopSlideNotification.show(Get.context!, text: "设备 $mac 升级已取消".tr); + } + } + + void cancelAllUpgrades() { + for (final mac in _upgradeTasks.keys) { + cancelUpgrade(mac); + } + } + + Map? getUpgradeStatus(String mac) { + final task = _upgradeTasks[mac]; + if (task == null) return null; + + return { + 'mac': task.mac, + 'progress': task.progress, + 'status': task.status, + 'startTime': task.startTime, + 'endTime': task.endTime, + }; + } + + Map> getAllUpgradeStatus() { + final Map> status = {}; + for (final task in _upgradeTasks.values) { + status[task.mac] = { + 'progress': task.progress, + 'status': task.status, + 'startTime': task.startTime, + 'endTime': task.endTime, + }; + } + return status; + } + + bool isDeviceUpgrading(String mac) { + final task = _upgradeTasks[mac]; + return task != null && + (task.status == 'downloading' || task.status == 'upgrading'); + } + + void cleanupCompletedTasks() { + final completedMacs = _upgradeTasks.entries + .where((entry) => + ['completed', 'failed', 'cancelled'].contains(entry.value.status)) + .map((entry) => entry.key) + .toList(); + + for (final mac in completedMacs) { + _upgradeTasks.remove(mac); + } + } + + Future _sendVotaCommand(THapp thapp) async { + try { + final result = + await thapp.send('vota path="/root/mtd/update"', true, (log) { + ef.log("[vota命令响应] ${log.log}"); + + // vota 命令特定的成功判断 + if (log.log.contains("verification complete") || + log.log.contains("update successful") || + log.log.contains("rebooting")) { + log.result = true; + return true; + } + + // vota 命令特定的失败判断 + if (log.log.contains("verification failed") || + log.log.contains("update error")) { + log.result = false; + return true; + } + + return false; + }, 0, 20000 // vota 命令可能需要更长时间 + ); + + return result == true; + } catch (e, stack) { + ef.log("[vota命令异常] $e\n$stack"); + return false; + } + } +} diff --git a/lib/common/util/DailyLogUtils.dart b/lib/common/util/DailyLogUtils.dart index f1560d9..5ac0d80 100644 --- a/lib/common/util/DailyLogUtils.dart +++ b/lib/common/util/DailyLogUtils.dart @@ -1,4 +1,5 @@ import 'dart:io'; +import 'package:img_picker/img_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:intl/intl.dart'; @@ -32,7 +33,7 @@ class DailyLogUtils { // 写入 warning 日志 static Future writeWarning(String content) async { - print("[dailylog-->waring] $content]"); + print("[dailylog-->waring] $content]"); await _writeLogWithLevel('WARNING', content); } @@ -54,7 +55,7 @@ class DailyLogUtils { } // 读取当天日志 - static Future readTodayLog() async { + static Future readTodayLog() async { final file = await _getLogFile(); return await file.readAsString(); } @@ -93,4 +94,51 @@ class DailyLogUtils { return logFiles; } + + static Future readLogsByDateRange( + DateTime fromDate, DateTime toDate) async { + try { + final dir = await getApplicationDocumentsDirectory(); + final dateFormat = DateFormat('yyyy-MM-dd'); + String combinedLogs = ''; + + // 遍历从 fromDate 到 toDate 的日期 + for (DateTime date = fromDate; + !date.isAfter(toDate); + date = date.add(Duration(days: 1))) { + final dateStr = dateFormat.format(date); // 格式化日期 + final file = File('${dir.path}/$dateStr.log'); // 日志文件路径 + + if (await file.exists()) { + final logContent = await file.readAsString(); + combinedLogs += '日志日期: $dateStr\n$logContent\n\n'; // 合并日志内容 + } + } + + if (combinedLogs.isNotEmpty) { + return combinedLogs; // 返回合并后的日志 + } else { + return '该时间段内没有日志记录'; // 如果没有日志 + } + } catch (e) { + return '读取日志失败: $e'; + } + } + + // 添加分享功能 + // static Future exportLog(DateTime date) async { + // try { + // final dir = await getApplicationDocumentsDirectory(); + // final dateStr = DateFormat('yyyy-MM-dd').format(date); + // final file = File('${dir.path}/$dateStr.log'); + // if (await file.exists()) { + // await Share.shareXFiles( + // [XFile(file.path)], + // text: '应用日志 $dateStr', + // ); + // } + // } catch (e) { + // print('导出日志失败: $e'); + // } + // } } diff --git a/lib/common/util/FirmwareVersionService.dart b/lib/common/util/FirmwareVersionService.dart new file mode 100644 index 0000000..77504c5 --- /dev/null +++ b/lib/common/util/FirmwareVersionService.dart @@ -0,0 +1,140 @@ +import 'dart:convert'; +import 'dart:io'; + +class FirmwareVersionInfo { + final String version; + final String fileName; + final String downloadUrl; + + FirmwareVersionInfo({ + required this.version, + required this.fileName, + required this.downloadUrl, + }); + + @override + String toString() { + return 'FirmwareVersionInfo{version: $version, fileName: $fileName, downloadUrl: $downloadUrl}'; + } +} + +class FirmwareVersionService { + /// 从远程URL获取最新的固件版本信息 + /// [baseUrl] 基础URL,如 "https://share.file.he-info.cn" + /// [firmwarePath] 固件路径,如 "/ota/aithv2/" + static Future getLatestFirmwareVersion({ + required String baseUrl, + required String firmwarePath, + }) async { + try { + // 构建完整的API URL + final apiUrl = '$baseUrl/~/api/get_file_list?uri=$firmwarePath'; + + // 发送HTTP请求 - 使用基础的http客户端,不依赖项目特定实现 + final response = await _makeHttpRequest(apiUrl); + + if (response == null) { + return null; + } + + // 解析响应数据 + return _parseFirmwareData(response, baseUrl, firmwarePath); + } catch (e) { + print('获取固件版本信息失败: $e'); + return null; + } + } + + /// 发送HTTP请求 + static Future _makeHttpRequest(String url) async { + try { + // 使用基础的http客户端 + final httpClient = HttpClient(); + + final request = await httpClient.getUrl(Uri.parse(url)); + final response = await request.close(); + + if (response.statusCode != 200) { + return null; + } + + final responseBody = await response.transform(utf8.decoder).join(); + return jsonDecode(responseBody); + } catch (e) { + print('HTTP请求失败: $e'); + return null; + } + } + + /// 解析固件数据 + static FirmwareVersionInfo? _parseFirmwareData( + dynamic data, + String baseUrl, + String firmwarePath + ) { + if (data == null || data['list'] == null) { + return null; + } + + final versionList = data['list'] as List; + int? maxVersionNum; + String? latestFileName; + + for (var item in versionList) { + if (item is Map && item.containsKey('n')) { + final fileName = item['n'] as String; + try { + // 解析版本号 - 假设文件名格式为 "something-version.extension" + final versionMatch = _extractVersionFromFileName(fileName); + + if (versionMatch != null) { + final versionNum = int.parse(versionMatch); + + if (maxVersionNum == null || versionNum > maxVersionNum) { + maxVersionNum = versionNum; + latestFileName = fileName; + } + } + } catch (e) { + // 解析失败跳过 + continue; + } + } + } + + if (latestFileName != null && maxVersionNum != null) { + // 构建下载URL + final downloadUrl = '$baseUrl/${firmwarePath.replaceFirst('/', '')}$latestFileName'; + + return FirmwareVersionInfo( + version: maxVersionNum.toString(), + fileName: latestFileName, + downloadUrl: downloadUrl, + ); + } + + return null; + } + + /// 从文件名中提取版本号 + static String? _extractVersionFromFileName(String fileName) { + // 多种可能的文件名格式处理 + final patterns = [ + // 格式: something-123.bin + RegExp(r'-(\d+)\.'), + // 格式: v123.bin + RegExp(r'v(\d+)\.'), + // 格式: firmware_123.bin + RegExp(r'_(\d+)\.'), + ]; + + for (final pattern in patterns) { + final match = pattern.firstMatch(fileName); + if (match != null && match.groupCount >= 1) { + return match.group(1); + } + } + + return null; + } +} \ No newline at end of file diff --git a/lib/component/tool/cmd.dart b/lib/component/tool/cmd.dart index 6977bbe..70781e5 100644 --- a/lib/component/tool/cmd.dart +++ b/lib/component/tool/cmd.dart @@ -1,11 +1,8 @@ //蓝牙指令 -import 'dart:typed_data'; - import 'package:EasyDartModule/EasyDartModule.dart' as edm; import 'package:easydevice/src/app/thapp.dart'; import 'package:vbvs_app/common/util/DailyLogUtils.dart'; -import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; // wifi列表指令 getWifiList(THapp tHapp) async { @@ -111,63 +108,6 @@ Future sendWifiSetting(wifiItem, String password, THapp tHapp) async { } } -// getDeviceWifiStatus(THapp tHapp, int times) async { -// edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令"); -// DailyLogUtils.writeLog("发送请求设备已配置网络状态指令"); -// try { -// var result = await tHapp.send("at+system info", true, (ss) { -// var log = ss.log; - -// // 匹配设备状态 -// final statusMatch = RegExp(r'Status=([^\s]+)').firstMatch(log); -// final status = statusMatch?.group(1); -// if (status != null) { -// print('提取到的 status: $status'); - -// // 如果设备连接状态是 "connect",继续检测 -// if (status.contains('connect')) { -// ss.result = true; -// ss.over = true; - -// // 匹配 Wi-Fi 连接信息 -// final wifiInfoMatch = RegExp( -// r'WIFI CONNECTED INFO:SSID=([^\s]+),RSSI=([-0-9]+),AUTH=([0-9]+),CH=([0-9]+),BSSID=([A-F0-9]+)') -// .firstMatch(log); -// if (wifiInfoMatch != null) { -// final ssid = wifiInfoMatch.group(1); -// final rssi = wifiInfoMatch.group(2); -// final auth = wifiInfoMatch.group(3); -// final ch = wifiInfoMatch.group(4); -// final bssid = wifiInfoMatch.group(5); - -// // 打印并返回 Wi-Fi 信息 -// print( -// 'Wi-Fi 信息: SSID=$ssid, RSSI=$rssi, AUTH=$auth, CH=$ch, BSSID=$bssid'); - -// // 停止监听并返回信息 -// ss.result = { -// 'ssid': ssid, -// 'rssi': rssi, -// 'auth': auth, -// 'ch': ch, -// 'bssid': bssid, -// }; -// ss.over = true; -// return ss.result; -// } -// } -// } - -// // 未找到状态或Wi-Fi信息时,返回 false -// return false; -// }, times); - -// return result; -// } catch (e) { -// print(e); -// } -// } - getDeviceWifiStatus(THapp tHapp, int times) async { edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令"); DailyLogUtils.writeLog("发送请求设备已配置网络状态指令"); @@ -212,7 +152,6 @@ getDeviceWifiStatus(THapp tHapp, int times) async { } } } - // 继续监听 return false; }, times); diff --git a/lib/controller/device/blueteeth_bind_controller.dart b/lib/controller/device/blueteeth_bind_controller.dart index 27b8ffe..c866d8a 100644 --- a/lib/controller/device/blueteeth_bind_controller.dart +++ b/lib/controller/device/blueteeth_bind_controller.dart @@ -23,6 +23,7 @@ class BlueteethBindModel { int? read = 1; double? singal = -90; + bool? bluetooth = false; //蓝牙开关 @JsonKey(ignore: true) List? devicelist = []; //蓝牙扫描 @JsonKey(ignore: true) diff --git a/lib/pages/device/instant_body_page.dart b/lib/pages/device/instant_body_page.dart index e736619..344bdc4 100644 --- a/lib/pages/device/instant_body_page.dart +++ b/lib/pages/device/instant_body_page.dart @@ -8,6 +8,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/util/CommonVariables.dart'; +import 'package:vbvs_app/common/util/DailyLogUtils.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; @@ -70,6 +71,9 @@ class _InstantBodyPageState extends State void _initWebSocket() { // 发送WebSocket请求 + edm.EasyDartModule.logger + .info("[webscoekt]发送请求:数据-->${{"mac": widget.personInfo['mac']}}"); + DailyLogUtils.writeLog("[webscoekt]发送请求:数据-->${{"mac": widget.personInfo['mac']}}"); edm.EasyDartModule.websocket.sendData(jsonEncode(WebSocketMessage( path: "/vsbs/web/rt/marttress", type: 1, @@ -606,22 +610,20 @@ class _InstantBodyPageState extends State "MAC号".tr + ": ${widget.personInfo['mac'] ?? '未知数据'.tr}", style: TextStyle( - color: themeController.currentColor.sc4 - .withOpacity(0.2), + color: themeController.currentColor.sc4, fontSize: - AppConstants().smaller_text_fontSize, + AppConstants().middler_text_fontSize, ), ), SizedBox( - height: 10.rpx, + height: 4.rpx, ), Text( "睡眠报告提示".tr, style: TextStyle( - color: themeController.currentColor.sc4 - .withOpacity(0.2), + color: themeController.currentColor.sc4, fontSize: - AppConstants().smaller_text_fontSize, + AppConstants().middler_text_fontSize, ), ), ], diff --git a/lib/pages/device_bind/blueteeth_device_page.dart b/lib/pages/device_bind/blueteeth_device_page.dart index 8382210..a7cc6cd 100644 --- a/lib/pages/device_bind/blueteeth_device_page.dart +++ b/lib/pages/device_bind/blueteeth_device_page.dart @@ -8,6 +8,7 @@ import 'package:flutter_svg/svg.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:permission_handler/permission_handler.dart'; // 引入permission_handler import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/BluetoothHelper.dart'; import 'package:vbvs_app/common/util/CommonVariables.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; @@ -60,6 +61,23 @@ class _BlueteethDevicePageState extends State { Get.find().startStatusPolling(); blueteethBindController.search.value = ""; blueteethBindController.currentDeviceMac?.value = ""; + BluetoothHelper.listenBluetoothState((isOn) { + blueteethBindController.model.bluetooth = isOn; + if (isOn) { + isScanning = false; + _startScanning(); + } + blueteethBindController.updateAll(); + if (!isOn && !_isDialogShowing) { + _isDialogShowing = true; + blueteethBindController.model.devicelist = []; + blueteethBindController.model.betDevicelist = []; + blueteethBindController.updateAll(); + _showBluetoothNotEnabledDialog().then((_) { + _isDialogShowing = false; + }); + } + }); } // 检查蓝牙权限 @@ -77,27 +95,6 @@ class _BlueteethDevicePageState extends State { } } - // Future _requestBluetoothPermission() async { - // Map statuses = await [ - // Permission.bluetoothScan, - // Permission.bluetoothConnect, - // Permission.location, // Android 12 及以下仍需要位置权限来进行扫描 - // ].request(); - - // bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true && - // statuses[Permission.bluetoothConnect]?.isGranted == true && - // statuses[Permission.location]?.isGranted == true; - - // if (allGranted) { - // // 用户授予了权限,开始扫描 - // _startScanning(); - // _startPeriodicScan(); - // } else { - // // 权限被拒绝,提示用户 - // _showPermissionDeniedDialog(); - // } - // } - Future _requestBluetoothPermission(BuildContext context) async { Map statuses = {}; bool dialogShown = false; // 标记是否弹过权限提示弹窗 @@ -200,15 +197,15 @@ class _BlueteethDevicePageState extends State { if (!mounted || isScanning) return; _scanSubscription?.cancel(); - var bluetoothState = await FlutterBluePlus.isOn; - if (!bluetoothState && !_isDialogShowing) { - _isDialogShowing = true; - blueteethBindController.model.blelist = []; - blueteethBindController.updateAll(); - await _showBluetoothNotEnabledDialog(); - _isDialogShowing = false; - return; - } + // var bluetoothState = await FlutterBluePlus.isOn; + // if (!bluetoothState && !_isDialogShowing) { + // _isDialogShowing = true; + // blueteethBindController.model.blelist = []; + // blueteethBindController.updateAll(); + // await _showBluetoothNotEnabledDialog(); + // _isDialogShowing = false; + // return; + // } if (!isScanning) { setState(() { diff --git a/lib/pages/mh_page/Vital_signs_sensor.dart b/lib/pages/mh_page/Vital_signs_sensor.dart index 0d787c5..cf1816e 100644 --- a/lib/pages/mh_page/Vital_signs_sensor.dart +++ b/lib/pages/mh_page/Vital_signs_sensor.dart @@ -1,16 +1,30 @@ import 'dart:async'; +import 'dart:io'; +import 'dart:typed_data'; +import 'package:easydevice/easydevice.dart'; import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.dart'; +import 'package:vbvs_app/common/util/BluetoothHelper.dart'; +import 'package:vbvs_app/common/util/CommonVariables.dart'; +import 'package:vbvs_app/common/util/FirmwareVersionService.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/NullDataComponentWidget.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/controller/mh_controller/device_list_controller.dart'; +import 'package:vbvs_app/pages/common/selectDialog.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart'; +import 'package:vbvs_app/pages/mh_page/device/mht_blueteeth_device_page.dart'; +import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart'; class VitalSignsSensorPage extends StatefulWidget { Map data; @@ -23,13 +37,24 @@ class _VitalSignsSensorState extends State { BoxConstraints? bodysize; DeviceListController controller = Get.find(); Timer? _timer; + late FlutterBluePlus flutterBlue; + StreamSubscription>? _scanSubscription; + MHTBlueToothController mhtBlueToothController = Get.find(); + @override void initState() { + //blog发送的时候关闭,发完时候进行开启,以便接受vota path="/root/mtd/update"的日志 + //blog enable + //控制每1s的数量 + //前面的15s的速度每秒安卓50/苹果30帧,后面安卓150帧/苹果100帧 + //苹果base64编码前每帧不超过100字节,安卓180字节 super.initState(); _fetchDeviceList(); _timer = Timer.periodic(Duration(seconds: 5), (timer) { _fetchDeviceList(); }); + flutterBlue = FlutterBluePlus(); + _fetchFirmwareInfo(); } _fetchDeviceList() async { @@ -39,7 +64,6 @@ class _VitalSignsSensorState extends State { @override void dispose() { _timer?.cancel(); // 页面销毁时取消定时器 - super.dispose(); } @@ -82,6 +106,53 @@ class _VitalSignsSensorState extends State { left: 0.rpx, child: returnIconButtomNew(), ), + Positioned( + right: 0.rpx, + child: ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.symmetric( + horizontal: 30.rpx, vertical: 30.rpx), + onTap: () { + // TopSlideNotification.show(context); + //检测蓝牙是否开启,没有开启提示开启蓝牙 + //如果已经开启,则进行x秒的蓝牙扫描 + BluetoothHelper.listenBluetoothState((isOn) { + mhtBlueToothController.model.bluetooth = isOn; + mhtBlueToothController.updateAll(); + if (!isOn) { + mhtBlueToothController.model.blueRawData = []; + mhtBlueToothController + .model.deviceDataStatus = []; + mhtBlueToothController.updateAll(); + _showBluetoothNotEnabledDialog(); + } + }); + _checkBluetoothPermission(); + }, + child: Obx(() { + return SizedBox( + width: 34.rpx, + height: mhtBlueToothController.isScanning.value + ? 34.rpx + : 24.rpx, + child: mhtBlueToothController.isScanning.value + ? CircularProgressIndicator( + strokeWidth: 2, + valueColor: + AlwaysStoppedAnimation( + themeController.currentColor.sc3, + ), + ) + : SvgPicture.asset( + 'assets/img/icon/upgrade.svg', + fit: BoxFit.cover, + color: Colors.white, + ), + ); + }), + ), + ), ], ), ), @@ -111,7 +182,10 @@ class _VitalSignsSensorState extends State { final item = dataList[index]; final title = index == 0 ? '体征传感器A'.tr : '体征传感器B'.tr; // ✅ 只处理 A/B 场景 - return VitalWidget(title: title, data: item); + return VitalWidget( + title: title, + data: item, + ); }).divide(SizedBox(height: 30.rpx)), SizedBox(height: 30.rpx), ], @@ -119,23 +193,792 @@ class _VitalSignsSensorState extends State { ), ); } + + Future _checkBluetoothPermission() async { + PermissionStatus bluetoothStatus = await Permission.bluetooth.status; + PermissionStatus locationStatus = await Permission.location.status; + + if (bluetoothStatus.isGranted && locationStatus.isGranted) { + _startScanning(); + } else { + _requestBluetoothPermission(); + } + } + + void _startScanning() async { + try { + if (!mounted) return; + + _scanSubscription?.cancel(); + + mhtBlueToothController.isScanning.value = true; + + _scanSubscription = FlutterBluePlus.scanResults.listen((results) { + if (!mounted) return; + final filteredResults = results.map((r) { + Map d = { + "updateTime": DateTime.now().millisecondsSinceEpoch, + "name": r.advertisementData.localName?.trim() ?? + r.advertisementData.advName?.trim() ?? + r.device.name ?? + "", + "rssi": r.rssi, + "device": r.device, + "connectable": r.advertisementData.connectable, + }; + + // 从 manufacturerData 解析设备唯一 ID + Map> m_d = r.advertisementData.manufacturerData; + String? deviceId; + int? version; + + m_d.forEach((key, value) { + if (value == null || value.isEmpty) return; + + if (key == 65517) { + List a = [0, 0, ...value]; + advertisDataFormatter(a, d); // 你原来的处理 + if (d['adData']?['deviceId'] != null) { + deviceId = d['adData']['deviceId']; + version = d['adData']['version']; + } + } else if (key == 11125 && value.length == 8) { + deviceId = ab2str(value.sublist(2, 8)).toUpperCase(); + } else if (value.length == 8 && isQuanShiDevice(d["name"])) { + deviceId = ab2str(value.sublist(2, 8)).toUpperCase(); + } else if ((value.length == 4 || value.length == 6) && + isMHTSWES(d["name"])) { + List a; + if (value.length == 4) { + ByteData bd = ByteData(2); + bd.setUint16(0, key, Endian.little); + a = [bd.getUint8(0), bd.getUint8(1), ...value]; + } else { + a = [...value]; + } + deviceId = ab2str(a).toUpperCase(); + } + }); + + d['id'] = deviceId ?? r.device.remoteId.str; + if (version != null) { + d['version'] = version; + } + + return BlueToothDataModel.fromScanResult( + r, + 1, + bind: false, + name: d['name'], + mac: d['id'], + version: d['version'], + ); + }).where((d) { + List macList = controller.model.vitalList + .map((e) => e['mac']?.toString() ?? "") + .toList(); + //todo 过滤,跟远程的版本信息比对 + // return macList.contains(d.mac) && d.version != null; + if (formatString(d.mac) == "48CA43B2E0B4") { + ef.log(""); + } + return macList.contains(formatString(d.mac)); + }).toList(); + print("$filteredResults"); + for (var item in filteredResults) { + // 如果不存在才加入,初始进度设为-1,代表未开始升级 + mhtBlueToothController.localUpgradeMac.putIfAbsent( + item.mac, + () => + {"progress": -1, "curversion": item.version, 'device': item}); + } + }); + + await FlutterBluePlus.startScan(timeout: Duration(seconds: 5)); + + FlutterBluePlus.isScanning.listen((scanning) { + mhtBlueToothController.isScanning.value = scanning; + }); + } catch (e) { + ef.log("$e"); + } + } + + Future _requestBluetoothPermission() async { + Map statuses = {}; + bool dialogShown = false; // 标记是否弹过权限提示弹窗 + if (Platform.isIOS) { + PermissionStatus isBleGranted = await Permission.bluetooth.request(); + print('checkBlePermissions-ios, isBleGranted=$isBleGranted'); + if (isBleGranted.isGranted) { + // startBluetoothScanning(); + _startScanning(); + } else { + // showToast("蓝牙开关或蓝牙权限未开启,请开启蓝牙开关与蓝牙权限".tr, closeTime: 7); + try { + _startScanning(); + } catch (e) { + TopSlideNotification.show(context, + text: "蓝牙权限未开启,请在设置中开启蓝牙权限".tr, + textColor: themeController.currentColor.sc9); + } + } + } else if (Platform.isAndroid) { + try { + // 检查是否已授权 + bool alreadyGranted = await Permission.bluetoothScan.isGranted && + await Permission.bluetoothConnect.isGranted && + await Permission.location.isGranted; + + if (!alreadyGranted) { + // 弹出自定义提示 + showPermissionInfoDialog( + Get.context!, CommonVariables().bluetoothpermissionInfo); + dialogShown = true; + + await Future.delayed(const Duration(milliseconds: 300)); + + // 请求权限 + statuses = await [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, + ].request(); + + ef.log("权限状态: $statuses"); + } else { + statuses = { + Permission.bluetoothScan: PermissionStatus.granted, + Permission.bluetoothConnect: PermissionStatus.granted, + Permission.location: PermissionStatus.granted, + }; + } + } catch (e) { + ef.log("申请权限出错: $e"); + } finally { + // 只有真的弹过提示才关闭 + if (dialogShown && Get.context != null) { + Navigator.of(Get.context!, rootNavigator: true).pop(); + } + } + bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true && + statuses[Permission.bluetoothConnect]?.isGranted == true && + statuses[Permission.location]?.isGranted == true; + + if (allGranted) { + _startScanning(); + } else { + _showPermissionDeniedDialog(context); + } + } else { + TopSlideNotification.show(context, + text: "当前系统不支持蓝牙,无法使用此功能".tr, + textColor: themeController.currentColor.sc9); + } + } + + void _showPermissionDeniedDialog(BuildContext context) { + TopSlideNotification.show(context, + text: "应用需要蓝牙和位置权限才能扫描设备。请授予权限。".tr, + textColor: themeController.currentColor.sc9); + } + + _showBluetoothNotEnabledDialog() async { + await showTipDialog( + colors: AppConstants().mhtNormalButton, + backgroundColor: Colors.white, + context, + Column( + children: [ + Text( + "蓝牙未开启".tr, + style: TextStyle( + fontSize: AppConstants().title_text_fontSize, + color: stringToColor("#333333"), + ), + ), + SizedBox( + height: 20.rpx, + ), + Text( + "请先打开蓝牙在进行设备扫描".tr, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: stringToColor("#333333"), + ), + ), + ], + )); + } + + void _fetchFirmwareInfo() async { + final firmwareInfo = await FirmwareVersionService.getLatestFirmwareVersion( + baseUrl: "https://share.file.he-info.cn", + firmwarePath: "/ota/aithv2/", + ); + if (firmwareInfo != null) { + print("最新版本号: ${firmwareInfo.version}"); + print("最新文件名: ${firmwareInfo.fileName}"); + print("最新文件下载地址: ${firmwareInfo.downloadUrl}"); + mhtBlueToothController.currentUpgradeVersion = firmwareInfo.version; + mhtBlueToothController.currentUpgradeName = firmwareInfo.fileName; + // 如果需要,也可以存储下载URL + mhtBlueToothController.currentUpgradeUrl = firmwareInfo.downloadUrl; + } else { + print("未找到有效版本"); + TopSlideNotification.show(context, + text: "获取固件列表失败,请检查网络".tr, + textColor: themeController.currentColor.sc9); + } + } + + String formatString(String input) { + /// 格式化字符串:移除所有空格和冒号,并将小写字母转换为大写 + return input.replaceAll(' ', '').replaceAll(':', '').toUpperCase(); + } } +// class VitalWidget extends StatelessWidget { +// final String title; +// final Map data; + +// const VitalWidget({ +// super.key, +// required this.title, +// required this.data, +// }); + +// @override +// Widget build(BuildContext context) { +// MHTBlueToothController mhtBlueToothController = Get.find(); +// final String id = data['mac'.tr] ?? '--'; +// final int? timestamp = data['status']['updateTime']; +// final String time = (timestamp != null) +// ? DateFormat('yyyy-MM-dd HH:mm') +// .format(DateTime.fromMillisecondsSinceEpoch(timestamp)) +// : '--'; +// return ClickableContainer( +// backgroundColor: Color(0xFF003058), +// highlightColor: Color(0xFF055466), +// borderRadius: 20.rpx, +// padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 30.rpx, 30.rpx, 40.rpx), +// onTap: () {}, +// child: Column( +// mainAxisSize: MainAxisSize.max, +// children: [ +// Container( +// width: double.infinity, +// // constraints: BoxConstraints( +// // minHeight: 60.rpx, +// // ), +// child: Align( +// alignment: AlignmentDirectional(-1, 0), +// child: Text( +// // '实时监测结果通知'.tr, +// title, +// style: TextStyle( +// fontFamily: 'Inter', +// fontSize: 30.rpx, +// letterSpacing: 0.0, +// color: themeController.currentColor.sc3, +// ), +// ), +// ), +// ), +// Row( +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// Container( +// width: MediaQuery.sizeOf(context).width * 0.14, +// constraints: BoxConstraints(minWidth: 110.rpx), +// child: Text( +// "设备ID".tr, +// style: TextStyle( +// color: stringToColor("#929699"), +// fontSize: 26.rpx, +// ), +// maxLines: 2, +// overflow: TextOverflow.ellipsis, +// ), +// ), +// Text( +// id, +// style: TextStyle(fontSize: 26.rpx, color: Color(0xFFFFFFFF)), +// ), +// ].divide(SizedBox(width: 33.rpx)), +// ), +// Row( +// mainAxisAlignment: MainAxisAlignment.start, +// children: [ +// Container( +// width: MediaQuery.sizeOf(context).width * 0.14, +// constraints: BoxConstraints(minWidth: 110.rpx), +// child: Text( +// "更新时间".tr, +// style: TextStyle( +// color: stringToColor("#929699"), +// fontSize: 26.rpx, +// ), +// maxLines: 2, +// overflow: TextOverflow.ellipsis, +// ), +// ), +// Text( +// time, +// style: TextStyle(fontSize: 26.rpx, color: Color(0xFFFFFFFF)), +// ), +// ].divide(SizedBox(width: 33.rpx)), +// ), +// Row( +// mainAxisSize: MainAxisSize.max, +// children: [ +// Container( +// width: MediaQuery.sizeOf(context).width * 0.14, +// constraints: BoxConstraints(minWidth: 110.rpx), +// child: Text( +// "设备状态".tr, +// style: TextStyle( +// color: stringToColor("#929699"), +// fontSize: 26.rpx, +// ), +// maxLines: 2, +// overflow: TextOverflow.ellipsis, +// ), +// ), +// Row( +// mainAxisSize: MainAxisSize.max, +// children: [ +// if (data['status']['signal'] != null && +// data['status']['signal'] != -1 && +// data['status']['status'] != null && +// data['status']['status'] == 1) +// ClickableContainer( +// backgroundColor: Colors.transparent, +// highlightColor: Colors.grey, // 或根据你主题定制颜色 +// padding: EdgeInsets.zero, +// borderRadius: 0, +// onTap: () { +// // 点击事件 +// showTipDialog( +// context, +// Container( +// child: RichText( +// text: TextSpan( +// text: "信号强度".tr, // 文本前缀部分 +// style: TextStyle( +// color: Colors.black, +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// children: [ +// TextSpan( +// text: getBedSignal( +// data['status']['signal']), // 状态部分 +// style: TextStyle( +// color: themeController +// .currentColor.sc2, // 同样颜色,也可改成其他颜色 +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// ], +// ), +// ), +// ), +// backgroundColor: Color(0xFFFFFFFF), +// colors: [ +// Color(0XFF1592AA), +// Color(0xFF0C83A7), +// Color(0xFF006FA3) +// ], +// ); +// }, + +// child: Container( +// width: 30.rpx, +// height: 24.rpx, +// child: Image.asset( +// 'assets/img/signal${_getSignalLevel(data['status']['signal'])}.png'), +// ), +// ), +// if (data['status']['inBed'] != null && +// data['status']['status'] != null && +// data['status']['status'] == 1) +// ClickableContainer( +// backgroundColor: Colors.transparent, +// highlightColor: Colors.grey, +// padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), +// borderRadius: 0, +// onTap: () { +// showTipDialog( +// context, +// Container( +// child: RichText( +// text: TextSpan( +// text: "是否在床".tr, // 文本前缀部分 +// style: TextStyle( +// color: Colors.black, +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// children: [ +// TextSpan( +// text: getBedStatus( +// data['status']['inBed']), // 状态部分 +// style: TextStyle( +// color: themeController +// .currentColor.sc2, // 同样颜色,也可改成其他颜色 +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// ], +// ), +// ), +// ), +// backgroundColor: Color(0xFFFFFFFF), +// colors: [ +// Color(0XFF1592AA), +// Color(0xFF0C83A7), +// Color(0xFF006FA3) +// ], +// ); +// }, +// child: SizedBox( +// width: 16.rpx, +// height: 36.rpx, +// child: SvgPicture.asset( +// data['status']['inBed'] == 0 +// ? 'assets/img/icon/not_bed.svg' +// : 'assets/img/icon/in_bed.svg', +// fit: BoxFit.fill, +// ), +// ), +// ), +// if (data['status']['failure'] != 0 && +// data['status']['status'] != null && +// data['status']['status'] == 1) +// ClickableContainer( +// backgroundColor: Colors.transparent, +// highlightColor: Colors.grey, // 可自定义点击水波纹颜色 +// padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), +// borderRadius: 0, +// onTap: () { +// showTipDialog( +// context, +// Container( +// child: RichText( +// text: TextSpan( +// text: "设备故障".tr, // 文本前缀部分 +// style: TextStyle( +// color: Colors.black, +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// children: [ +// // TextSpan( +// // text:widget +// // .device['status']['status'] == 1?"在线".tr:"离线".tr, // 状态部分 +// // style: TextStyle( +// // color: themeController.currentColor +// // .sc2, // 同样颜色,也可改成其他颜色 +// // fontSize: AppConstants() +// // .title_text_fontSize, +// // ), +// // ), +// ], +// ), +// ), +// ), +// backgroundColor: Color(0xFFFFFFFF), +// colors: [ +// Color(0XFF1592AA), +// Color(0xFF0C83A7), +// Color(0xFF006FA3) +// ], +// ); +// }, +// child: SizedBox( +// width: 27.rpx, +// height: 27.rpx, +// child: SvgPicture.asset( +// 'assets/img/icon/device_issue.svg', +// fit: BoxFit.cover, +// ), +// ), +// ), +// if (data['status']['status'] != null && +// data['status']['status'] == 0) +// ClickableContainer( +// backgroundColor: Colors.transparent, +// highlightColor: Colors.grey, // 可替换为点击高亮色 +// padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), +// borderRadius: 0, +// onTap: () { +// showTipDialog( +// context, +// Container( +// child: RichText( +// text: TextSpan( +// text: "网络状态".tr, // 文本前缀部分 +// style: TextStyle( +// color: Colors.black, +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// children: [ +// TextSpan( +// text: data['status']['status'] == 1 +// ? "在线".tr +// : "离线".tr, // 状态部分 +// style: TextStyle( +// color: themeController +// .currentColor.sc2, // 同样颜色,也可改成其他颜色 +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// ], +// ), +// ), +// ), +// backgroundColor: Color(0xFFFFFFFF), +// colors: [ +// Color(0XFF1592AA), +// Color(0xFF0C83A7), +// Color(0xFF006FA3) +// ], +// ); +// }, +// child: SizedBox( +// width: 27.rpx, +// height: 27.rpx, +// child: SvgPicture.asset( +// // data['status']['status'] == 1 +// // ? 'assets/img/icon/device_online.svg' +// // : 'assets/img/icon/device_offline.svg', +// 'assets/img/icon/device_issue.svg', +// fit: BoxFit.cover, +// ), +// ), +// ), +// if (mhtBlueToothController.localUpgradeMac +// .containsKey(data['mac'])) +// ClickableContainer( +// backgroundColor: Colors.transparent, +// highlightColor: Colors.grey, // 可自定义点击效果颜色 +// padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), +// borderRadius: 0, +// onTap: () { +// String curCersion = mhtBlueToothController +// .localUpgradeMac[data['mac']]!['curversion'] +// .toString(); +// String newVersion = +// mhtBlueToothController.currentUpgradeVersion ?? +// ""; +// if (mhtBlueToothController +// .localUpgradeMac[data['mac']]!['progress'] == +// -1) { +// showConfirmUpDialog( +// context, +// Column( +// children: [ +// SizedBox( +// width: 94.rpx, +// height: 70.rpx, +// child: SvgPicture.asset( +// 'assets/img/icon/upgrade.svg', +// fit: BoxFit.cover, +// ), +// ), +// Text( +// "设备升级".tr, +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// Text( +// "当前版本".tr + ":" + "${curCersion}", +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// Text( +// "最新版本".tr + ":" + "${newVersion}", +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// ].divide(SizedBox(height: 37.rpx)), +// ), +// "".tr, onConfirm: () async { +// final deviceData = mhtBlueToothController +// .localUpgradeMac[data['mac']]!; +// final firmwareUrl = +// mhtBlueToothController.currentUpgradeUrl; + +// if (firmwareUrl == null) { +// TopSlideNotification.show(context, +// text: "未获取到固件下载地址".tr); +// return; +// } + +// try { +// final scanResult = +// deviceData['device'].scanResult; +// final thapp = THapp(device: scanResult.device); + +// if (!thapp.isConnected) { +// await thapp.device.connect(); +// } + +// await MultiDeviceFirmwareUpdater().startUpgrade( +// thapp: thapp, +// mac: data['mac'], +// firmwareUrl: firmwareUrl, +// ); +// } catch (e) { +// print("升级失败: $e"); +// TopSlideNotification.show(context, +// text: "升级失败: ${e.toString()}".tr, +// textColor: Colors.red); +// } +// }, +// onCancel: () {}, +// backgroundColor: Colors.white, +// confirmText: "立即升级".tr, +// cancelText: "下次再说".tr); +// // return; +// } else { +// showConfirmCancelDialog( +// context, +// Column( +// children: [ +// SizedBox( +// width: 94.rpx, +// height: 70.rpx, +// child: SvgPicture.asset( +// 'assets/img/icon/upgrade.svg', +// fit: BoxFit.cover, +// // color: themeController.currentColor.sc3, // 若你想加颜色控制可取消注释 +// ), +// ), +// Text( +// "设备升级".tr, +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// Text( +// "当前版本".tr + +// ":" + +// "${mhtBlueToothController.localUpgradeMac[data['mac']]!['curversion']}", +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// Text( +// "最新版本".tr + ":" + "${newVersion}", +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// Text( +// "升级进度".tr + +// ":" + +// "${mhtBlueToothController.localUpgradeMac[data['mac']]!['progress']}" + +// "%", +// style: TextStyle( +// color: stringToColor("#333333"), +// fontSize: +// AppConstants().title_text_fontSize, +// ), +// ), +// ].divide(SizedBox(height: 37.rpx)), +// ), +// "".tr, onConfirm: () async { +// //todo 取消升级 将进度改为-1 +// }, +// onCancel: () {}, +// backgroundColor: Colors.white, +// confirmText: "取消升级".tr, +// cancelText: "返回".tr, +// confirmButtonColor: stringToColor("#FF7159")); +// } +// }, +// child: Obx(() { +// var aa = mhtBlueToothController.isScanning; +// print(aa); +// return Row( +// children: [ +// SizedBox( +// width: 34.rpx, +// height: 24.rpx, +// child: SvgPicture.asset( +// 'assets/img/icon/upgrade.svg', +// fit: BoxFit.cover, +// // color: themeController.currentColor.sc3, // 若你想加颜色控制可取消注释 +// ), +// ), +// if ((mhtBlueToothController.localUpgradeMac[ +// data['mac']]!['progress']) != +// -1) +// Text( +// "${mhtBlueToothController.localUpgradeMac[data['mac']]!['progress']}" + +// "%", +// style: TextStyle( +// color: Colors.white, +// fontSize: 26.rpx, +// ), +// ), +// ].divide(SizedBox( +// width: 10.rpx, +// )), +// ); +// }), +// ), +// ].divide(SizedBox(width: 50.rpx)), +// ), +// ].divide(SizedBox(width: 34.rpx)), +// ), +// ].divide( +// SizedBox(height: 20.rpx), +// )), +// ); +// } +// } + class VitalWidget extends StatelessWidget { final String title; final Map data; - const VitalWidget({super.key, required this.title, required this.data}); + const VitalWidget({ + super.key, + required this.title, + required this.data, + }); @override Widget build(BuildContext context) { + MHTBlueToothController mhtBlueToothController = Get.find(); final String id = data['mac'.tr] ?? '--'; final int? timestamp = data['status']['updateTime']; final String time = (timestamp != null) ? DateFormat('yyyy-MM-dd HH:mm') .format(DateTime.fromMillisecondsSinceEpoch(timestamp)) : '--'; - final List icons = data['icons'] ?? []; return ClickableContainer( backgroundColor: Color(0xFF003058), @@ -144,88 +987,85 @@ class VitalWidget extends StatelessWidget { padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 30.rpx, 30.rpx, 40.rpx), onTap: () {}, child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: double.infinity, - // constraints: BoxConstraints( - // minHeight: 60.rpx, - // ), - child: Align( - alignment: AlignmentDirectional(-1, 0), - child: Text( - // '实时监测结果通知'.tr, - title, - style: TextStyle( - fontFamily: 'Inter', - fontSize: 30.rpx, - letterSpacing: 0.0, - color: themeController.currentColor.sc3, - ), + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: double.infinity, + child: Align( + alignment: AlignmentDirectional(-1, 0), + child: Text( + title, + style: TextStyle( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, ), ), ), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - width: MediaQuery.sizeOf(context).width * 0.14, - constraints: BoxConstraints(minWidth: 110.rpx), - child: Text( - "设备ID".tr, - style: TextStyle( - color: stringToColor("#929699"), - fontSize: 26.rpx, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.14, + constraints: BoxConstraints(minWidth: 110.rpx), + child: Text( + "设备ID".tr, + style: TextStyle( + color: stringToColor("#929699"), + fontSize: 26.rpx, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - Text( - id, - style: TextStyle(fontSize: 26.rpx, color: Color(0xFFFFFFFF)), - ), - ].divide(SizedBox(width: 33.rpx)), - ), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - width: MediaQuery.sizeOf(context).width * 0.14, - constraints: BoxConstraints(minWidth: 110.rpx), - child: Text( - "更新时间".tr, - style: TextStyle( - color: stringToColor("#929699"), - fontSize: 26.rpx, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + ), + Text( + id, + style: TextStyle(fontSize: 26.rpx, color: Color(0xFFFFFFFF)), + ), + ].divide(SizedBox(width: 33.rpx)), + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.14, + constraints: BoxConstraints(minWidth: 110.rpx), + child: Text( + "更新时间".tr, + style: TextStyle( + color: stringToColor("#929699"), + fontSize: 26.rpx, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - Text( - time, - style: TextStyle(fontSize: 26.rpx, color: Color(0xFFFFFFFF)), - ), - ].divide(SizedBox(width: 33.rpx)), - ), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: MediaQuery.sizeOf(context).width * 0.14, - constraints: BoxConstraints(minWidth: 110.rpx), - child: Text( - "设备状态".tr, - style: TextStyle( - color: stringToColor("#929699"), - fontSize: 26.rpx, - ), - maxLines: 2, - overflow: TextOverflow.ellipsis, + ), + Text( + time, + style: TextStyle(fontSize: 26.rpx, color: Color(0xFFFFFFFF)), + ), + ].divide(SizedBox(width: 33.rpx)), + ), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: MediaQuery.sizeOf(context).width * 0.14, + constraints: BoxConstraints(minWidth: 110.rpx), + child: Text( + "设备状态".tr, + style: TextStyle( + color: stringToColor("#929699"), + fontSize: 26.rpx, ), + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - Row( + ), + Expanded( + child: Row( mainAxisSize: MainAxisSize.max, children: [ if (data['status']['signal'] != null && @@ -234,17 +1074,16 @@ class VitalWidget extends StatelessWidget { data['status']['status'] == 1) ClickableContainer( backgroundColor: Colors.transparent, - highlightColor: Colors.grey, // 或根据你主题定制颜色 + highlightColor: Colors.grey, padding: EdgeInsets.zero, borderRadius: 0, onTap: () { - // 点击事件 showTipDialog( context, Container( child: RichText( text: TextSpan( - text: "信号强度".tr, // 文本前缀部分 + text: "信号强度".tr, style: TextStyle( color: Colors.black, fontSize: @@ -253,10 +1092,9 @@ class VitalWidget extends StatelessWidget { children: [ TextSpan( text: getBedSignal( - data['status']['signal']), // 状态部分 + data['status']['signal']), style: TextStyle( - color: themeController - .currentColor.sc2, // 同样颜色,也可改成其他颜色 + color: themeController.currentColor.sc2, fontSize: AppConstants().title_text_fontSize, ), @@ -273,7 +1111,6 @@ class VitalWidget extends StatelessWidget { ], ); }, - child: Container( width: 30.rpx, height: 24.rpx, @@ -295,7 +1132,7 @@ class VitalWidget extends StatelessWidget { Container( child: RichText( text: TextSpan( - text: "是否在床".tr, // 文本前缀部分 + text: "是否在床".tr, style: TextStyle( color: Colors.black, fontSize: @@ -303,11 +1140,10 @@ class VitalWidget extends StatelessWidget { ), children: [ TextSpan( - text: getBedStatus( - data['status']['inBed']), // 状态部分 + text: + getBedStatus(data['status']['inBed']), style: TextStyle( - color: themeController - .currentColor.sc2, // 同样颜色,也可改成其他颜色 + color: themeController.currentColor.sc2, fontSize: AppConstants().title_text_fontSize, ), @@ -340,7 +1176,7 @@ class VitalWidget extends StatelessWidget { data['status']['status'] == 1) ClickableContainer( backgroundColor: Colors.transparent, - highlightColor: Colors.grey, // 可自定义点击水波纹颜色 + highlightColor: Colors.grey, padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), borderRadius: 0, onTap: () { @@ -349,24 +1185,13 @@ class VitalWidget extends StatelessWidget { Container( child: RichText( text: TextSpan( - text: "设备故障".tr, // 文本前缀部分 + text: "设备故障".tr, style: TextStyle( color: Colors.black, fontSize: AppConstants().title_text_fontSize, ), - children: [ - // TextSpan( - // text:widget - // .device['status']['status'] == 1?"在线".tr:"离线".tr, // 状态部分 - // style: TextStyle( - // color: themeController.currentColor - // .sc2, // 同样颜色,也可改成其他颜色 - // fontSize: AppConstants() - // .title_text_fontSize, - // ), - // ), - ], + children: const [], ), ), ), @@ -387,32 +1212,11 @@ class VitalWidget extends StatelessWidget { ), ), ), - if (data['status']['upgrade'] != 0 && - data['status']['status'] != null && - data['status']['status'] == 1) - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: Colors.grey, // 可自定义点击效果颜色 - padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), - borderRadius: 0, - onTap: () { - //todo 升级 - }, - child: SizedBox( - width: 34.rpx, - height: 24.rpx, - child: SvgPicture.asset( - 'assets/img/icon/upgrade.svg', - fit: BoxFit.cover, - // color: themeController.currentColor.sc3, // 若你想加颜色控制可取消注释 - ), - ), - ), if (data['status']['status'] != null && data['status']['status'] == 0) ClickableContainer( backgroundColor: Colors.transparent, - highlightColor: Colors.grey, // 可替换为点击高亮色 + highlightColor: Colors.grey, padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), borderRadius: 0, onTap: () { @@ -421,7 +1225,7 @@ class VitalWidget extends StatelessWidget { Container( child: RichText( text: TextSpan( - text: "网络状态".tr, // 文本前缀部分 + text: "网络状态".tr, style: TextStyle( color: Colors.black, fontSize: @@ -431,10 +1235,9 @@ class VitalWidget extends StatelessWidget { TextSpan( text: data['status']['status'] == 1 ? "在线".tr - : "离线".tr, // 状态部分 + : "离线".tr, style: TextStyle( - color: themeController - .currentColor.sc2, // 同样颜色,也可改成其他颜色 + color: themeController.currentColor.sc2, fontSize: AppConstants().title_text_fontSize, ), @@ -455,58 +1258,255 @@ class VitalWidget extends StatelessWidget { width: 27.rpx, height: 27.rpx, child: SvgPicture.asset( - // data['status']['status'] == 1 - // ? 'assets/img/icon/device_online.svg' - // : 'assets/img/icon/device_offline.svg', 'assets/img/icon/device_issue.svg', fit: BoxFit.cover, ), ), ), - ].divide(SizedBox(width: 50.rpx)), + Obx(() { + final upgradeInfo = + mhtBlueToothController.localUpgradeMac[data['mac']]; + if (upgradeInfo == null) { + return Container(); + } + final int progress = upgradeInfo?['progress'] ?? -1; + final String status = upgradeInfo?['status'] ?? ''; + // 只有在有升级信息时才显示 + if (mhtBlueToothController.localUpgradeMac + .containsKey(data['mac'])) { + return ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.grey, + padding: + EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), + borderRadius: 0, + onTap: () { + if (progress == -1) { + _showUpgradeDialog(context, data['mac']); + } else { + _showCancelDialog(context, data['mac']); + } + }, + child: Row( + children: [ + // 升级图标 + SizedBox( + width: 34.rpx, + height: 24.rpx, + child: SvgPicture.asset( + 'assets/img/icon/upgrade.svg', + fit: BoxFit.cover, + color: _getUpgradeIconColor(status), + ), + ), + SizedBox(width: 10.rpx), + // 进度数字和状态指示 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // 进度百分比 + if (progress >= 0 && progress <= 100) + Text( + "$progress%", + style: TextStyle( + color: _getProgressTextColor(progress), + fontSize: 26.rpx, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ], + ), + ); + } + return Container(); + }), + ].divide(SizedBox(width: 50.rpx)), // 减少图标间距 ), - ].divide(SizedBox(width: 34.rpx)), - ), - ].divide( - SizedBox(height: 20.rpx), - )), + ), + ].divide(SizedBox(width: 34.rpx)), + ), + ].divide(SizedBox(height: 20.rpx)), + ), ); } - getBedSignal(signal) { - if (signal <= 1) { - return "较弱".tr; + // 获取状态文本 + String _getStatusText(String status) { + switch (status) { + case 'waiting': + return '等待'.tr; + case 'downloading': + return '下载'.tr; + case 'upgrading': + return '升级'.tr; + case 'completed': + return '完成'.tr; + case 'failed': + return '失败'.tr; + case 'cancelled': + return '取消'.tr; + default: + return status; } - if (signal > 1 && signal <= 2) { - return "弱".tr; - } - if (signal > 2 && signal <= 3) { - return "一般".tr; - } - if (signal > 3) { - return "强".tr; - } - return "未知数据".tr; } - String getBedStatus(int status) { - if (status == 0) { - return "离床".tr; - } else if (status == 1) { - return "在床".tr; + // 获取状态文字颜色 + Color _getStatusTextColor(String status) { + switch (status) { + case 'completed': + return Colors.green; + case 'failed': + return Colors.red; + case 'cancelled': + return Colors.orange; + default: + return Colors.blue; } - return ""; } - int _getSignalLevel(int signal) { - if (signal <= 25) { - return 1; - } else if (signal <= 50) { - return 2; - } else if (signal <= 75) { - return 3; - } else { - return 4; + // 获取进度文字颜色 + Color _getProgressTextColor(int progress) { + if (progress == 100) return Colors.green; + if (progress < 0) return Colors.red; + return Colors.white; + } + + // 获取升级图标颜色 + Color _getUpgradeIconColor(String status) { + switch (status) { + case 'completed': + return Colors.green; + case 'failed': + return Colors.red; + case 'cancelled': + return Colors.orange; + default: + return themeController.currentColor.sc3; } } + + // 显示升级对话框 + void _showUpgradeDialog(BuildContext context, String mac) { + MHTBlueToothController mhtBlueToothController = Get.find(); + showConfirmUpDialog( + context, + Column( + children: [ + SizedBox( + width: 94.rpx, + height: 70.rpx, + child: SvgPicture.asset( + 'assets/img/icon/upgrade.svg', + fit: BoxFit.cover, + ), + ), + Text( + "设备升级".tr, + style: TextStyle( + color: stringToColor("#333333"), + fontSize: AppConstants().title_text_fontSize, + ), + ), + ].divide(SizedBox(height: 37.rpx)), + ), + "", onConfirm: () async { + final deviceData = mhtBlueToothController.localUpgradeMac[mac]!; + final firmwareUrl = mhtBlueToothController.currentUpgradeUrl; + if (firmwareUrl == null) { + TopSlideNotification.show(context, text: "未获取到固件下载地址".tr); + return; + } + try { + final scanResult = deviceData['device'].scanResult; + final thapp = THapp(device: scanResult.device); + if (!thapp.isConnected) { + await thapp.device.connect(); + } + await MultiDeviceFirmwareUpdater().startUpgrade( + thapp: thapp, + mac: mac, + firmwareUrl: firmwareUrl, + ); + } catch (e) { + TopSlideNotification.show(context, + text: "升级失败: ${e.toString()}".tr, textColor: Colors.red); + } + }, + onCancel: () {}, + backgroundColor: Colors.white, + confirmText: "立即升级".tr, + cancelText: "下次再说".tr); + } + + // 显示取消对话框 + void _showCancelDialog(BuildContext context, String mac) { + showConfirmCancelDialog( + context, + Column( + children: [ + Text( + "确认取消升级".tr, + style: TextStyle( + color: stringToColor("#333333"), + fontSize: AppConstants().title_text_fontSize, + ), + ), + Text( + "确定要取消设备 $mac 的升级吗?".tr, + style: TextStyle( + color: stringToColor("#333333"), + fontSize: AppConstants().normal_text_fontSize, + ), + ), + ], + ), + "", onConfirm: () { + MultiDeviceFirmwareUpdater().cancelUpgrade(mac); + }, + onCancel: () {}, + backgroundColor: Colors.white, + confirmText: "确认取消".tr, + cancelText: "继续升级".tr, + confirmButtonColor: Colors.red); + } +} + +getBedSignal(signal) { + if (signal <= 1) { + return "较弱".tr; + } + if (signal > 1 && signal <= 2) { + return "弱".tr; + } + if (signal > 2 && signal <= 3) { + return "一般".tr; + } + if (signal > 3) { + return "强".tr; + } + return "未知数据".tr; +} + +String getBedStatus(int status) { + if (status == 0) { + return "离床".tr; + } else if (status == 1) { + return "在床".tr; + } + return ""; +} + +int _getSignalLevel(int signal) { + if (signal <= 25) { + return 1; + } else if (signal <= 50) { + return 2; + } else if (signal <= 75) { + return 3; + } else { + return 4; + } } diff --git a/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart b/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart index 536820c..ff78abe 100644 --- a/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart +++ b/lib/pages/mh_page/device/controller/mht_bluetooth_controller.dart @@ -73,7 +73,10 @@ class MHTBlueToothController extends GetControllerEx { RxString? cid = "".obs; RxBool isScanning = false.obs; - Map localUpgradeMac = {}; //mac 进度 + RxMap localUpgradeMac = {}.obs; //mac 进度 + String? currentUpgradeVersion;//最新版本号 + String? currentUpgradeName;//最新固件名 + String? currentUpgradeUrl;//最新固件名 void startStatusPolling() { updateDeviceStatus().then((res) { diff --git a/lib/pages/sleep_report/new_sleep_report_page.dart b/lib/pages/sleep_report/new_sleep_report_page.dart index 137c981..0b8b06a 100644 --- a/lib/pages/sleep_report/new_sleep_report_page.dart +++ b/lib/pages/sleep_report/new_sleep_report_page.dart @@ -875,22 +875,20 @@ class _NewSleepReportPageState extends State { "MAC号".tr + ": ${widget.data['mac'] ?? '未知数据'.tr}", style: TextStyle( - color: themeController.currentColor.sc4 - .withOpacity(0.2), + color: themeController.currentColor.sc4, fontSize: - AppConstants().smaller_text_fontSize, + AppConstants().middler_text_fontSize, ), ), SizedBox( - height: 10.rpx, + height: 4.rpx, ), Text( "睡眠报告提示".tr, style: TextStyle( - color: themeController.currentColor.sc4 - .withOpacity(0.2), + color: themeController.currentColor.sc4, fontSize: - AppConstants().smaller_text_fontSize, + AppConstants().middler_text_fontSize, ), ), ],