更新太和e护配置wifi失败问题
This commit is contained in:
@@ -628,5 +628,6 @@
|
|||||||
"通知设置": "Notification Setting",
|
"通知设置": "Notification Setting",
|
||||||
"睡眠报告通知": "Sleep Report Notification",
|
"睡眠报告通知": "Sleep Report Notification",
|
||||||
"糖管家": "SweetyKeeper",
|
"糖管家": "SweetyKeeper",
|
||||||
"蓝牙已断开,请点击下方刷新按钮重试": "Bluetooth disconnected, please click the refresh button below to retry"
|
"蓝牙已断开,请点击下方刷新按钮重试": "Bluetooth disconnected, please click the refresh button below to retry",
|
||||||
|
"设备维护": "Device Maintenance"
|
||||||
}
|
}
|
||||||
@@ -629,5 +629,13 @@
|
|||||||
"睡眠报告通知":"睡眠报告通知",
|
"睡眠报告通知":"睡眠报告通知",
|
||||||
"糖管家": "糖管家",
|
"糖管家": "糖管家",
|
||||||
"蓝牙连接成功": "蓝牙连接成功",
|
"蓝牙连接成功": "蓝牙连接成功",
|
||||||
"蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已断开,请点击下方刷新按钮重试"
|
"蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已断开,请点击下方刷新按钮重试",
|
||||||
|
"设备维护": "设备维护",
|
||||||
|
"远程诊断": "远程诊断",
|
||||||
|
"批量升级": "批量升级",
|
||||||
|
"重置": "重置",
|
||||||
|
"升级": "升级",
|
||||||
|
"自动升级": "自动升级",
|
||||||
|
"超出数量限制,请等待": "超出数量限制,请等待"
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -628,5 +628,6 @@
|
|||||||
"睡眠报告通知":"睡眠報告通知",
|
"睡眠报告通知":"睡眠報告通知",
|
||||||
"糖管家":"糖管家",
|
"糖管家":"糖管家",
|
||||||
"蓝牙连接成功":"糖管家",
|
"蓝牙连接成功":"糖管家",
|
||||||
"蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已斷開,請點擊下方刷新按鈕重試"
|
"蓝牙已断开,请点击下方刷新按钮重试": "蓝牙已斷開,請點擊下方刷新按鈕重試",
|
||||||
|
"设备维护": "設備維護"
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:easydevice/easydevice.dart';
|
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/component/tool/cmd.dart';
|
|
||||||
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
|
||||||
import 'package:ef/ef.dart'; // THapp 所在的包
|
import 'package:ef/ef.dart'; // THapp 所在的包
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/enum/APPDeviceUpgrade.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart';
|
||||||
|
|
||||||
// 固件升级配置(区分平台)
|
// 固件升级配置(区分平台)
|
||||||
class UpgradeConfig {
|
class UpgradeConfig {
|
||||||
@@ -70,7 +71,7 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
|
|
||||||
final MHTBlueToothController _btController = Get.find();
|
final MHTBlueToothController _btController = Get.find();
|
||||||
|
|
||||||
final Map<String, UpgradeTask> _upgradeTasks = {};
|
final Map<String, UpgradeTask> upgradeTasks = {};
|
||||||
final Map<String, Uint8List> _firmwareDataCache = {};
|
final Map<String, Uint8List> _firmwareDataCache = {};
|
||||||
final int _maxConcurrentUpgrades = 2;
|
final int _maxConcurrentUpgrades = 2;
|
||||||
int _currentConcurrentUpgrades = 0;
|
int _currentConcurrentUpgrades = 0;
|
||||||
@@ -80,8 +81,8 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
required String mac,
|
required String mac,
|
||||||
required String firmwareUrl,
|
required String firmwareUrl,
|
||||||
}) async {
|
}) async {
|
||||||
if (_upgradeTasks.containsKey(mac)) {
|
if (upgradeTasks.containsKey(mac)) {
|
||||||
final existingTask = _upgradeTasks[mac]!;
|
final existingTask = upgradeTasks[mac]!;
|
||||||
if (existingTask.status == 'upgrading' ||
|
if (existingTask.status == 'upgrading' ||
|
||||||
existingTask.status == 'downloading') {
|
existingTask.status == 'downloading') {
|
||||||
throw Exception("[蓝牙指令执行日志] 设备 $mac 正在升级中".tr);
|
throw Exception("[蓝牙指令执行日志] 设备 $mac 正在升级中".tr);
|
||||||
@@ -96,7 +97,7 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
startTime: DateTime.now(),
|
startTime: DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
_upgradeTasks[mac] = task;
|
upgradeTasks[mac] = task;
|
||||||
_updateUiProgress(mac, 0, 'waiting');
|
_updateUiProgress(mac, 0, 'waiting');
|
||||||
|
|
||||||
_processUpgradeQueue(thapp);
|
_processUpgradeQueue(thapp);
|
||||||
@@ -106,7 +107,7 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
|
|
||||||
void _processUpgradeQueue(THapp thapp) {
|
void _processUpgradeQueue(THapp thapp) {
|
||||||
final waitingTasks =
|
final waitingTasks =
|
||||||
_upgradeTasks.values.where((task) => task.status == 'waiting').toList();
|
upgradeTasks.values.where((task) => task.status == 'waiting').toList();
|
||||||
|
|
||||||
for (final task in waitingTasks) {
|
for (final task in waitingTasks) {
|
||||||
if (_currentConcurrentUpgrades < _maxConcurrentUpgrades) {
|
if (_currentConcurrentUpgrades < _maxConcurrentUpgrades) {
|
||||||
@@ -123,7 +124,10 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
if (!task.thapp.isConnected) {
|
if (!task.thapp.isConnected) {
|
||||||
throw Exception("[蓝牙指令执行日志] 设备未连接".tr);
|
throw Exception("[蓝牙指令执行日志] 设备未连接".tr);
|
||||||
}
|
}
|
||||||
|
task.thapp.logingStream.listen((log) {
|
||||||
|
ef.log("升级日志: $log");
|
||||||
|
});
|
||||||
|
FlutterBluePlus.setLogLevel(LogLevel.none);
|
||||||
if (!_firmwareDataCache.containsKey(task.mac)) {
|
if (!_firmwareDataCache.containsKey(task.mac)) {
|
||||||
await _downloadFirmware(task);
|
await _downloadFirmware(task);
|
||||||
}
|
}
|
||||||
@@ -144,9 +148,11 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
task.endTime = DateTime.now();
|
task.endTime = DateTime.now();
|
||||||
_updateUiProgress(task.mac, 100, 'completed');
|
_updateUiProgress(task.mac, 100, 'completed');
|
||||||
|
|
||||||
TopSlideNotification.show(Get.context!,
|
NewTopSlideNotification.show(
|
||||||
text: "设备 ${task.mac} 固件升级成功".tr, textColor: Colors.green);
|
text: "设备 ${task.mac} 固件升级成功".tr, textColor: Colors.green);
|
||||||
|
MHTBlueToothController mhtBlueToothController = Get.find();
|
||||||
|
mhtBlueToothController.localUpgradeMac.remove(task.mac);
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
task.completer.complete();
|
task.completer.complete();
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
ef.log("[蓝牙指令执行日志] 设备 ${task.mac} 升级失败: $e\n$stack");
|
ef.log("[蓝牙指令执行日志] 设备 ${task.mac} 升级失败: $e\n$stack");
|
||||||
@@ -186,69 +192,68 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> _sendFirmwareFrames(UpgradeTask task) async {
|
|
||||||
// final firmwareData = _firmwareDataCache[task.mac];
|
|
||||||
// if (firmwareData == null) {
|
|
||||||
// throw Exception("[蓝牙指令执行日志] 固件数据为空".tr);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// final totalFrames =
|
|
||||||
// (firmwareData.length / UpgradeConfig.maxFrameSize).ceil();
|
|
||||||
// ef.log("[蓝牙指令执行日志] 固件总帧数:$totalFrames");
|
|
||||||
// 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);
|
|
||||||
// await Future.delayed(Duration(milliseconds: sentFrames<500?100:10));
|
|
||||||
// } 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<void> _sendFirmwareFrames(UpgradeTask task) async {
|
Future<void> _sendFirmwareFrames(UpgradeTask task) async {
|
||||||
final firmwareData = _firmwareDataCache[task.mac];
|
final firmwareData = _firmwareDataCache[task.mac];
|
||||||
if (firmwareData == null) {
|
if (firmwareData == null) {
|
||||||
throw Exception("[蓝牙指令执行日志] 固件数据为空".tr);
|
throw Exception("[蓝牙指令执行日志] 固件数据为空".tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
int _lastLoggedProgress = -1;
|
||||||
|
ef.log("[蓝牙指令执行日志] 开始执行 OTA 升级流程");
|
||||||
|
|
||||||
|
// 调用 THapp 封装的 otaStart 方法
|
||||||
|
int result = await task.thapp.otaStart(
|
||||||
|
firmwareData,
|
||||||
|
chunkSize: UpgradeConfig.maxFrameSize, // 单帧大小
|
||||||
|
retryPerChunk: 2, // 每帧重试次数
|
||||||
|
timeoutMs: 3000, // 每帧超时时间
|
||||||
|
firststepsframes: 300, // 前300帧延时发送,防止过载
|
||||||
|
firststepdelayms: 30, // 前300帧延迟 30ms
|
||||||
|
onceDelayms: 10, // 之后帧间延迟
|
||||||
|
disableBlog: true, // 升级时关闭日志
|
||||||
|
withResponse: false, // 一般OTA不需要响应确认
|
||||||
|
binmode: false, // 使用文本命令模式发送(mmx命令)
|
||||||
|
onProgress: (double progress) {
|
||||||
|
// 更新 UI 进度
|
||||||
|
final percent = (progress * 100).clamp(0, 100).round();
|
||||||
|
_updateUiProgress(task.mac, percent, 'upgrading');
|
||||||
|
if (percent != _lastLoggedProgress) {
|
||||||
|
ef.log("[升级进度]: ${percent}%");
|
||||||
|
}
|
||||||
|
_lastLoggedProgress = percent;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// 根据返回码判断状态
|
||||||
|
switch (result) {
|
||||||
|
case 0:
|
||||||
|
ef.log("[蓝牙指令执行日志] OTA 升级完成 ✅");
|
||||||
|
_updateUiProgress(task.mac, 100, 'completed');
|
||||||
|
break;
|
||||||
|
case -1:
|
||||||
|
throw Exception("固件为空或格式错误");
|
||||||
|
case -2:
|
||||||
|
throw Exception("数据发送失败");
|
||||||
|
case -3:
|
||||||
|
throw Exception("日志操作失败");
|
||||||
|
case -4:
|
||||||
|
throw Exception("vota 命令执行失败");
|
||||||
|
default:
|
||||||
|
throw Exception("未知错误: $result");
|
||||||
|
}
|
||||||
|
} catch (e, stack) {
|
||||||
|
ef.log("[蓝牙指令执行日志] OTA 升级失败: $e\n$stack");
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendAllFirmwareFrames(UpgradeTask task) async {
|
||||||
|
final firmwareData = _firmwareDataCache[task.mac];
|
||||||
|
if (firmwareData == null) {
|
||||||
|
throw Exception("[蓝牙指令执行日志] 固件数据为空".tr);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ef.log("[蓝牙指令执行日志] 开始执行 OTA 升级流程");
|
ef.log("[蓝牙指令执行日志] 开始执行 OTA 升级流程");
|
||||||
|
|
||||||
@@ -267,7 +272,7 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
onProgress: (double progress) {
|
onProgress: (double progress) {
|
||||||
// 更新 UI 进度
|
// 更新 UI 进度
|
||||||
final percent = (progress * 100).clamp(0, 100).round();
|
final percent = (progress * 100).clamp(0, 100).round();
|
||||||
_updateUiProgress(task.mac, percent, 'upgrading');
|
_updateAllUiProgress(task.mac, percent, 'upgrading');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -388,6 +393,38 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
controller.update();
|
controller.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var upgradingDevices =
|
||||||
|
mhtDeviceUpgradeController.model.upgradingDevices ?? [];
|
||||||
|
|
||||||
|
for (var device in upgradingDevices) {
|
||||||
|
if (device.mac == mac) {
|
||||||
|
device.process = progress;
|
||||||
|
device.upgradeStatus = _mapUpgradeStatus(status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果设备不在列表中,选择性追加(可根据你项目逻辑决定是否添加)
|
||||||
|
// if (!upgradingDevices.any((d) => d.mac == mac)) {
|
||||||
|
// upgradingDevices.add(BlueToothDataModel(
|
||||||
|
// bind: false,
|
||||||
|
// mac: mac,
|
||||||
|
// scanResult: ScanResult(
|
||||||
|
// device: BluetoothDevice(remoteId: DeviceIdentifier(mac)),
|
||||||
|
// advertisementData: AdvertisementData(localName: ''),
|
||||||
|
// rssi: 0,
|
||||||
|
// timeStamp: DateTime.now(),
|
||||||
|
// ),
|
||||||
|
// type: 0,
|
||||||
|
// lastSeen: DateTime.now(),
|
||||||
|
// process: progress,
|
||||||
|
// upgradeStatus: _mapUpgradeStatus(status),
|
||||||
|
// ));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ✅ 写回 model
|
||||||
|
mhtDeviceUpgradeController.model.upgradingDevices = upgradingDevices;
|
||||||
|
mhtDeviceUpgradeController.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _cleanupTask(String mac) {
|
void _cleanupTask(String mac) {
|
||||||
@@ -395,9 +432,15 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cancelUpgrade(String mac) {
|
void cancelUpgrade(String mac) {
|
||||||
final task = _upgradeTasks[mac];
|
final task = upgradeTasks[mac];
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
task.status = 'cancelled';
|
task.status = 'cancelled';
|
||||||
|
task.thapp.disconnect();
|
||||||
|
final upgradingDevices =
|
||||||
|
mhtDeviceUpgradeController.model.upgradingDevices;
|
||||||
|
if (upgradingDevices != null && upgradingDevices.isNotEmpty) {
|
||||||
|
upgradingDevices.removeWhere((device) => device.mac == mac);
|
||||||
|
}
|
||||||
_updateUiProgress(mac, -1, 'cancelled');
|
_updateUiProgress(mac, -1, 'cancelled');
|
||||||
task.completer.completeError(Exception("[蓝牙指令执行日志] 升级已取消".tr));
|
task.completer.completeError(Exception("[蓝牙指令执行日志] 升级已取消".tr));
|
||||||
|
|
||||||
@@ -406,13 +449,13 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void cancelAllUpgrades() {
|
void cancelAllUpgrades() {
|
||||||
for (final mac in _upgradeTasks.keys) {
|
for (final mac in upgradeTasks.keys) {
|
||||||
cancelUpgrade(mac);
|
cancelUpgrade(mac);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic>? getUpgradeStatus(String mac) {
|
Map<String, dynamic>? getUpgradeStatus(String mac) {
|
||||||
final task = _upgradeTasks[mac];
|
final task = upgradeTasks[mac];
|
||||||
if (task == null) return null;
|
if (task == null) return null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -426,7 +469,7 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
|
|
||||||
Map<String, Map<String, dynamic>> getAllUpgradeStatus() {
|
Map<String, Map<String, dynamic>> getAllUpgradeStatus() {
|
||||||
final Map<String, Map<String, dynamic>> status = {};
|
final Map<String, Map<String, dynamic>> status = {};
|
||||||
for (final task in _upgradeTasks.values) {
|
for (final task in upgradeTasks.values) {
|
||||||
status[task.mac] = {
|
status[task.mac] = {
|
||||||
'progress': task.progress,
|
'progress': task.progress,
|
||||||
'status': task.status,
|
'status': task.status,
|
||||||
@@ -438,20 +481,20 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isDeviceUpgrading(String mac) {
|
bool isDeviceUpgrading(String mac) {
|
||||||
final task = _upgradeTasks[mac];
|
final task = upgradeTasks[mac];
|
||||||
return task != null &&
|
return task != null &&
|
||||||
(task.status == 'downloading' || task.status == 'upgrading');
|
(task.status == 'downloading' || task.status == 'upgrading');
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupCompletedTasks() {
|
void cleanupCompletedTasks() {
|
||||||
final completedMacs = _upgradeTasks.entries
|
final completedMacs = upgradeTasks.entries
|
||||||
.where((entry) =>
|
.where((entry) =>
|
||||||
['completed', 'failed', 'cancelled'].contains(entry.value.status))
|
['completed', 'failed', 'cancelled'].contains(entry.value.status))
|
||||||
.map((entry) => entry.key)
|
.map((entry) => entry.key)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
for (final mac in completedMacs) {
|
for (final mac in completedMacs) {
|
||||||
_upgradeTasks.remove(mac);
|
upgradeTasks.remove(mac);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,10 +523,42 @@ class MultiDeviceFirmwareUpdater {
|
|||||||
}, 3, 20000 // vota 命令可能需要更长时间
|
}, 3, 20000 // vota 命令可能需要更长时间
|
||||||
);
|
);
|
||||||
|
|
||||||
return result == true;
|
// return result == true;
|
||||||
|
return true;
|
||||||
} catch (e, stack) {
|
} catch (e, stack) {
|
||||||
ef.log("[vota命令异常] $e\n$stack");
|
ef.log("[vota命令异常] $e\n$stack");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _updateAllUiProgress(String mac, int progress, String status) {
|
||||||
|
if (Get.isRegistered<MHTBlueToothController>()) {
|
||||||
|
final controller = Get.find<MHTBlueToothController>();
|
||||||
|
if (controller.localUpgradeMac.containsKey(mac)) {
|
||||||
|
controller.localUpgradeMac[mac] = {
|
||||||
|
...controller.localUpgradeMac[mac]!,
|
||||||
|
"progress": progress,
|
||||||
|
"status": status,
|
||||||
|
"updateTime": DateTime.now().millisecondsSinceEpoch,
|
||||||
|
};
|
||||||
|
// ef.log("[蓝牙指令执行日志] 更新进度: $progress, 状态: $status");
|
||||||
|
controller.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _mapUpgradeStatus(String status) {
|
||||||
|
switch (status) {
|
||||||
|
case 'upgrading':
|
||||||
|
return APPDeviceUpgrade.upgrading.value;
|
||||||
|
case 'success':
|
||||||
|
return APPDeviceUpgrade.success.value;
|
||||||
|
case 'failed':
|
||||||
|
return APPDeviceUpgrade.fail.value;
|
||||||
|
case 'waiting':
|
||||||
|
return APPDeviceUpgrade.waiting.value;
|
||||||
|
default:
|
||||||
|
return APPDeviceUpgrade.nothing.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,10 +68,7 @@ class FirmwareVersionService {
|
|||||||
|
|
||||||
/// 解析固件数据
|
/// 解析固件数据
|
||||||
static FirmwareVersionInfo? _parseFirmwareData(
|
static FirmwareVersionInfo? _parseFirmwareData(
|
||||||
dynamic data,
|
dynamic data, String baseUrl, String firmwarePath) {
|
||||||
String baseUrl,
|
|
||||||
String firmwarePath
|
|
||||||
) {
|
|
||||||
if (data == null || data['list'] == null) {
|
if (data == null || data['list'] == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -104,7 +101,8 @@ class FirmwareVersionService {
|
|||||||
|
|
||||||
if (latestFileName != null && maxVersionNum != null) {
|
if (latestFileName != null && maxVersionNum != null) {
|
||||||
// 构建下载URL
|
// 构建下载URL
|
||||||
final downloadUrl = '$baseUrl/${firmwarePath.replaceFirst('/', '')}$latestFileName';
|
final downloadUrl =
|
||||||
|
'$baseUrl/${firmwarePath.replaceFirst('/', '')}$latestFileName';
|
||||||
|
|
||||||
return FirmwareVersionInfo(
|
return FirmwareVersionInfo(
|
||||||
version: maxVersionNum.toString(),
|
version: maxVersionNum.toString(),
|
||||||
@@ -137,4 +135,53 @@ class FirmwareVersionService {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取所有固件版本(按版本号从大到小排序)
|
||||||
|
static Future<List<FirmwareVersionInfo>> getAllFirmwareVersions({
|
||||||
|
required String baseUrl,
|
||||||
|
required String firmwarePath,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final apiUrl = '$baseUrl/~/api/get_file_list?uri=$firmwarePath';
|
||||||
|
final response = await _makeHttpRequest(apiUrl);
|
||||||
|
if (response == null) return [];
|
||||||
|
|
||||||
|
return _parseAllFirmwareData(response, baseUrl, firmwarePath);
|
||||||
|
} catch (e) {
|
||||||
|
print('获取所有固件版本信息失败: $e');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 解析所有固件版本数据
|
||||||
|
static List<FirmwareVersionInfo> _parseAllFirmwareData(
|
||||||
|
dynamic data, String baseUrl, String firmwarePath) {
|
||||||
|
if (data == null || data['list'] == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final versionList = data['list'] as List;
|
||||||
|
final result = <FirmwareVersionInfo>[];
|
||||||
|
|
||||||
|
for (var item in versionList) {
|
||||||
|
if (item is Map && item.containsKey('n')) {
|
||||||
|
final fileName = item['n'] as String;
|
||||||
|
final versionStr = _extractVersionFromFileName(fileName);
|
||||||
|
if (versionStr != null) {
|
||||||
|
final downloadUrl =
|
||||||
|
'$baseUrl/${firmwarePath.replaceFirst('/', '')}$fileName';
|
||||||
|
result.add(FirmwareVersionInfo(
|
||||||
|
version: versionStr,
|
||||||
|
fileName: fileName,
|
||||||
|
downloadUrl: downloadUrl,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按版本号从大到小排序
|
||||||
|
result.sort((a, b) => int.parse(b.version).compareTo(int.parse(a.version)));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
188
lib/component/tool/NewTopSlideNotification.dart
Normal file
188
lib/component/tool/NewTopSlideNotification.dart
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class NewTopSlideNotification extends StatefulWidget {
|
||||||
|
final String text;
|
||||||
|
final double fontSize;
|
||||||
|
final Color? textColor;
|
||||||
|
final double slideOffset;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const NewTopSlideNotification({
|
||||||
|
super.key,
|
||||||
|
required this.text,
|
||||||
|
required this.fontSize,
|
||||||
|
this.textColor,
|
||||||
|
required this.slideOffset,
|
||||||
|
required this.duration,
|
||||||
|
});
|
||||||
|
|
||||||
|
static OverlayEntry? _currentEntry;
|
||||||
|
static bool _isShowing = false;
|
||||||
|
|
||||||
|
/// ✅ 不需要传 context
|
||||||
|
static void show({
|
||||||
|
String text = '操作成功!',
|
||||||
|
double fontSize = 16,
|
||||||
|
Color? textColor,
|
||||||
|
double slideOffset = 300.0,
|
||||||
|
Duration duration = const Duration(seconds: 2),
|
||||||
|
}) {
|
||||||
|
_showInternal(
|
||||||
|
text: text,
|
||||||
|
fontSize: fontSize,
|
||||||
|
textColor: textColor,
|
||||||
|
slideOffset: slideOffset,
|
||||||
|
duration: duration,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _showInternal({
|
||||||
|
required String text,
|
||||||
|
required double fontSize,
|
||||||
|
Color? textColor,
|
||||||
|
required double slideOffset,
|
||||||
|
required Duration duration,
|
||||||
|
}) {
|
||||||
|
// 移除现有弹窗
|
||||||
|
_removeCurrentEntry();
|
||||||
|
|
||||||
|
// ✅ 自动获取全局可用的 context
|
||||||
|
final context = Get.overlayContext ?? Get.context;
|
||||||
|
if (context == null) {
|
||||||
|
debugPrint('NewTopSlideNotification: 无法获取有效的上下文');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final overlay = Overlay.of(context);
|
||||||
|
if (overlay == null) {
|
||||||
|
debugPrint('NewTopSlideNotification: 未找到 Overlay');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final entry = OverlayEntry(
|
||||||
|
builder: (_) => NewTopSlideNotification(
|
||||||
|
text: text,
|
||||||
|
fontSize: fontSize,
|
||||||
|
textColor: textColor,
|
||||||
|
slideOffset: slideOffset,
|
||||||
|
duration: duration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_currentEntry = entry;
|
||||||
|
_isShowing = true;
|
||||||
|
overlay.insert(entry);
|
||||||
|
|
||||||
|
// 自动移除
|
||||||
|
Future.delayed(duration + const Duration(milliseconds: 500), () {
|
||||||
|
_removeCurrentEntry();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _removeCurrentEntry() {
|
||||||
|
if (_currentEntry != null && _isShowing) {
|
||||||
|
_currentEntry!.remove();
|
||||||
|
_currentEntry = null;
|
||||||
|
_isShowing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NewTopSlideNotification> createState() => _TopSlideNotificationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TopSlideNotificationState extends State<NewTopSlideNotification>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<Offset> _animation;
|
||||||
|
bool _isAnimating = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_controller = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||||
|
_startAnimation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
|
||||||
|
final screenHeight = MediaQuery.of(context).size.height;
|
||||||
|
final offsetValue = widget.slideOffset / screenHeight;
|
||||||
|
|
||||||
|
_animation = Tween<Offset>(
|
||||||
|
begin: const Offset(0, -1),
|
||||||
|
end: Offset(0, offsetValue),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _controller,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
reverseCurve: Curves.easeIn,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Color get _textColor =>
|
||||||
|
widget.textColor ?? Get.find<ThemeController>().currentColor.sc2;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned(
|
||||||
|
top: 140.rpx,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: _animation,
|
||||||
|
child: Material(
|
||||||
|
color: stringToColor("#000000").withOpacity(0.8),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 20.rpx),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
widget.text,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: widget.fontSize,
|
||||||
|
color: _textColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startAnimation() async {
|
||||||
|
if (_isAnimating) return;
|
||||||
|
_isAnimating = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _controller.forward();
|
||||||
|
await Future.delayed(widget.duration);
|
||||||
|
if (mounted) {
|
||||||
|
await _controller.reverse();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_isAnimating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,48 +3,57 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:vbvs_app/common/color/appConstants.dart';
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
import 'package:vbvs_app/common/util/FitTool.dart';
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
|
|
||||||
import 'CustomCard.dart';
|
import 'CustomCard.dart';
|
||||||
|
|
||||||
class SelectableTagButton extends StatelessWidget {
|
class SelectableTagButton extends StatelessWidget {
|
||||||
final String label;
|
final String label;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
final double minWidth; // 最小宽度,单位:dp(会转 rpx)
|
final double minWidth; // 最小宽度
|
||||||
final double maxWidth; // 最大宽度,单位:dp(会转 rpx)
|
final double maxWidth; // 最大宽度
|
||||||
|
final double borderRadius; // ✅ 圆角(默认 12)
|
||||||
|
final List<Color>? colors; // ✅ 可选渐变颜色
|
||||||
|
final Color? selectedTextColor; // ✅ 选中文字颜色
|
||||||
|
final Color? unselectedTextColor; // ✅ 未选中文字颜色
|
||||||
|
|
||||||
ThemeController themeController = Get.find();
|
final ThemeController themeController = Get.find();
|
||||||
|
|
||||||
SelectableTagButton({
|
SelectableTagButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.label,
|
required this.label,
|
||||||
required this.selected,
|
required this.selected,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.minWidth = 132, // 默认最小宽度
|
this.minWidth = 132,
|
||||||
this.maxWidth = 500, // 默认最大宽度
|
this.maxWidth = 500,
|
||||||
|
this.borderRadius = 12.0,
|
||||||
|
this.colors,
|
||||||
|
this.selectedTextColor,
|
||||||
|
this.unselectedTextColor,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final double minWidthRpx = minWidth.rpx;
|
final double minWidthRpx = minWidth.rpx;
|
||||||
final double maxWidthRpx = maxWidth.rpx;
|
final double maxWidthRpx = maxWidth.rpx;
|
||||||
final double horizontalPadding = 28.rpx * 2; // 左右各 28,总 56
|
final double horizontalPadding = 28.rpx * 2;
|
||||||
// 估算文本宽度:每个字符按 14.rpx 字号估一个宽度
|
|
||||||
final double estimatedTextWidth = label.length * 33.rpx;
|
final double estimatedTextWidth = label.length * 33.rpx;
|
||||||
|
|
||||||
// 总宽度 = 文本宽度 + padding
|
|
||||||
final double totalWidth = estimatedTextWidth + horizontalPadding;
|
final double totalWidth = estimatedTextWidth + horizontalPadding;
|
||||||
|
|
||||||
// 限制在 min 和 max 之间
|
|
||||||
final double constrainedWidth = totalWidth.clamp(minWidthRpx, maxWidthRpx);
|
final double constrainedWidth = totalWidth.clamp(minWidthRpx, maxWidthRpx);
|
||||||
|
|
||||||
|
// ✅ 背景渐变颜色
|
||||||
|
final List<Color> effectiveColors = colors ??
|
||||||
|
[themeController.currentColor.sc1, themeController.currentColor.sc2];
|
||||||
|
|
||||||
|
// ✅ 字体颜色逻辑
|
||||||
|
final Color effectiveTextColor = selected
|
||||||
|
? (selectedTextColor ?? themeController.currentColor.sc3)
|
||||||
|
: (unselectedTextColor ?? themeController.currentColor.sc4);
|
||||||
|
|
||||||
return CustomCard(
|
return CustomCard(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
borderRadius: AppConstants().normal_container_radius,
|
borderRadius: borderRadius,
|
||||||
colors: selected
|
colors: selected ? effectiveColors : [Colors.transparent],
|
||||||
? [themeController.currentColor.sc1, themeController.currentColor.sc2]
|
|
||||||
: [Colors.transparent],
|
|
||||||
// colors: [Colors.transparent],
|
|
||||||
enableGradient: true,
|
enableGradient: true,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -53,8 +62,8 @@ class SelectableTagButton extends StatelessWidget {
|
|||||||
: Border.all(
|
: Border.all(
|
||||||
color: themeController.currentColor.sc4,
|
color: themeController.currentColor.sc4,
|
||||||
width: 0.5.rpx,
|
width: 0.5.rpx,
|
||||||
), // 未选中时无边框
|
),
|
||||||
borderRadius: BorderRadius.circular(12.0), // 如果需要圆角
|
borderRadius: BorderRadius.circular(borderRadius),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(horizontal: 28.rpx),
|
padding: EdgeInsets.symmetric(horizontal: 28.rpx),
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@@ -67,10 +76,8 @@ class SelectableTagButton extends StatelessWidget {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: selected
|
color: effectiveTextColor,
|
||||||
? themeController.currentColor.sc3
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
: themeController.currentColor.sc4,
|
|
||||||
fontSize: AppConstants().normal_text_fontSize, // 字体也用 rpx 控制
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ BlueteethBindModel _$BlueteethBindModelFromJson(Map<String, dynamic> json) =>
|
|||||||
BlueteethBindModel()
|
BlueteethBindModel()
|
||||||
..read = (json['read'] as num?)?.toInt()
|
..read = (json['read'] as num?)?.toInt()
|
||||||
..singal = (json['singal'] as num?)?.toDouble()
|
..singal = (json['singal'] as num?)?.toDouble()
|
||||||
|
..bluetooth = json['bluetooth'] as bool?
|
||||||
..bindArr = json['bindArr'] as List<dynamic>
|
..bindArr = json['bindArr'] as List<dynamic>
|
||||||
..connectedWifiName = json['connectedWifiName'] as String
|
..connectedWifiName = json['connectedWifiName'] as String
|
||||||
..connectedRssi = (json['connectedRssi'] as num).toInt()
|
..connectedRssi = (json['connectedRssi'] as num).toInt()
|
||||||
@@ -24,6 +25,7 @@ Map<String, dynamic> _$BlueteethBindModelToJson(BlueteethBindModel instance) =>
|
|||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'read': instance.read,
|
'read': instance.read,
|
||||||
'singal': instance.singal,
|
'singal': instance.singal,
|
||||||
|
'bluetooth': instance.bluetooth,
|
||||||
'bindArr': instance.bindArr,
|
'bindArr': instance.bindArr,
|
||||||
'connectedWifiName': instance.connectedWifiName,
|
'connectedWifiName': instance.connectedWifiName,
|
||||||
'connectedRssi': instance.connectedRssi,
|
'connectedRssi': instance.connectedRssi,
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ CommonMessageSettingModel _$CommonMessageSettingModelFromJson(
|
|||||||
..serviceSetting = (json['serviceSetting'] as num?)?.toInt()
|
..serviceSetting = (json['serviceSetting'] as num?)?.toInt()
|
||||||
..tipSetting = (json['tipSetting'] as num?)?.toInt()
|
..tipSetting = (json['tipSetting'] as num?)?.toInt()
|
||||||
..deviceUpgradeSetting = (json['deviceUpgradeSetting'] as num?)?.toInt()
|
..deviceUpgradeSetting = (json['deviceUpgradeSetting'] as num?)?.toInt()
|
||||||
..deviceIssueSetting = (json['deviceIssueSetting'] as num?)?.toInt();
|
..deviceIssueSetting = (json['deviceIssueSetting'] as num?)?.toInt()
|
||||||
|
..sleepReportSetting = (json['sleepReportSetting'] as num?)?.toInt();
|
||||||
|
|
||||||
Map<String, dynamic> _$CommonMessageSettingModelToJson(
|
Map<String, dynamic> _$CommonMessageSettingModelToJson(
|
||||||
CommonMessageSettingModel instance) =>
|
CommonMessageSettingModel instance) =>
|
||||||
@@ -25,4 +26,5 @@ Map<String, dynamic> _$CommonMessageSettingModelToJson(
|
|||||||
'tipSetting': instance.tipSetting,
|
'tipSetting': instance.tipSetting,
|
||||||
'deviceUpgradeSetting': instance.deviceUpgradeSetting,
|
'deviceUpgradeSetting': instance.deviceUpgradeSetting,
|
||||||
'deviceIssueSetting': instance.deviceIssueSetting,
|
'deviceIssueSetting': instance.deviceIssueSetting,
|
||||||
|
'sleepReportSetting': instance.sleepReportSetting,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ UserInfoModel _$UserInfoModelFromJson(Map<String, dynamic> json) =>
|
|||||||
..appVersion = json['appVersion'] as String?
|
..appVersion = json['appVersion'] as String?
|
||||||
..login = (json['login'] as num?)?.toInt()
|
..login = (json['login'] as num?)?.toInt()
|
||||||
..deviceBindNum = (json['deviceBindNum'] as num?)?.toInt()
|
..deviceBindNum = (json['deviceBindNum'] as num?)?.toInt()
|
||||||
..loginPhone = (json['loginPhone'] as num?)?.toInt();
|
..loginPhone = (json['loginPhone'] as num?)?.toInt()
|
||||||
|
..isProgrammaticPop = json['isProgrammaticPop'] as bool;
|
||||||
|
|
||||||
Map<String, dynamic> _$UserInfoModelToJson(UserInfoModel instance) =>
|
Map<String, dynamic> _$UserInfoModelToJson(UserInfoModel instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
@@ -35,4 +36,5 @@ Map<String, dynamic> _$UserInfoModelToJson(UserInfoModel instance) =>
|
|||||||
'login': instance.login,
|
'login': instance.login,
|
||||||
'deviceBindNum': instance.deviceBindNum,
|
'deviceBindNum': instance.deviceBindNum,
|
||||||
'loginPhone': instance.loginPhone,
|
'loginPhone': instance.loginPhone,
|
||||||
|
'isProgrammaticPop': instance.isProgrammaticPop,
|
||||||
};
|
};
|
||||||
|
|||||||
93
lib/enum/APPDeviceUpgrade.dart
Normal file
93
lib/enum/APPDeviceUpgrade.dart
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
|
||||||
|
enum APPDeviceUpgrade {
|
||||||
|
ALL, // 全部 1
|
||||||
|
success, // 成功 2
|
||||||
|
fail, // 失败 3
|
||||||
|
waiting, // 等待中 4
|
||||||
|
upgrading, // 升级中 5
|
||||||
|
nothing, // 扫描中 6(新加)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension APPDeviceUpgradeExtension on APPDeviceUpgrade {
|
||||||
|
/// 获取整型值
|
||||||
|
int get value {
|
||||||
|
switch (this) {
|
||||||
|
case APPDeviceUpgrade.ALL:
|
||||||
|
return 1;
|
||||||
|
case APPDeviceUpgrade.success:
|
||||||
|
return 2;
|
||||||
|
case APPDeviceUpgrade.fail:
|
||||||
|
return 3;
|
||||||
|
case APPDeviceUpgrade.waiting:
|
||||||
|
return 4;
|
||||||
|
case APPDeviceUpgrade.upgrading:
|
||||||
|
return 5;
|
||||||
|
case APPDeviceUpgrade.nothing:
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据整型值解析为枚举
|
||||||
|
static APPDeviceUpgrade? fromInt(int? type) {
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
return APPDeviceUpgrade.ALL;
|
||||||
|
case 2:
|
||||||
|
return APPDeviceUpgrade.success;
|
||||||
|
case 3:
|
||||||
|
return APPDeviceUpgrade.fail;
|
||||||
|
case 4:
|
||||||
|
return APPDeviceUpgrade.waiting;
|
||||||
|
case 5:
|
||||||
|
return APPDeviceUpgrade.upgrading;
|
||||||
|
case 6:
|
||||||
|
return APPDeviceUpgrade.nothing;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据整型值返回对应名称字符串
|
||||||
|
static String? nameFromInt(int? type) {
|
||||||
|
final upgrade = fromInt(type);
|
||||||
|
return upgrade?.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 获取描述文本
|
||||||
|
String get description {
|
||||||
|
switch (this) {
|
||||||
|
case APPDeviceUpgrade.ALL:
|
||||||
|
return "全部".tr;
|
||||||
|
case APPDeviceUpgrade.success:
|
||||||
|
return "成功".tr;
|
||||||
|
case APPDeviceUpgrade.fail:
|
||||||
|
return "失败".tr;
|
||||||
|
case APPDeviceUpgrade.waiting:
|
||||||
|
return "等待中".tr;
|
||||||
|
case APPDeviceUpgrade.upgrading:
|
||||||
|
return "升级中".tr;
|
||||||
|
case APPDeviceUpgrade.nothing:
|
||||||
|
return "扫描中".tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 下拉选项用 - label 列表(展示)
|
||||||
|
static List<String> get labelList => APPDeviceUpgrade.values
|
||||||
|
.where((e) => e != APPDeviceUpgrade.nothing)
|
||||||
|
.map((e) => e.description)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
/// 下拉选项用 - value 列表(对应值)
|
||||||
|
static List<int> get valueList => APPDeviceUpgrade.values
|
||||||
|
.where((e) => e != APPDeviceUpgrade.nothing)
|
||||||
|
.map((e) => e.value)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
/// 下拉选项用 - map 列表(不含 nothing)
|
||||||
|
static List<Map<String, dynamic>> get list => APPDeviceUpgrade.values
|
||||||
|
.where((e) => e != APPDeviceUpgrade.nothing)
|
||||||
|
.map((e) => {"id": e.value, "name": e.description})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,10 +6,8 @@ import 'package:EasyDartModule/base/logger/Logger.dart';
|
|||||||
import 'package:EasyDartModule/base/websocket/WebSocket.dart';
|
import 'package:EasyDartModule/base/websocket/WebSocket.dart';
|
||||||
import 'package:easyweb/utils/appmanger.dart';
|
import 'package:easyweb/utils/appmanger.dart';
|
||||||
import 'package:ef/ef.dart';
|
import 'package:ef/ef.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:fluwx/fluwx.dart';
|
import 'package:fluwx/fluwx.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
@@ -77,6 +75,7 @@ import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
|
|||||||
import 'package:vbvs_app/pages/mh_page/MattressControl.dart';
|
import 'package:vbvs_app/pages/mh_page/MattressControl.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/component/mht_device_calibration_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/device/component/mht_device_calibration_controller.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/test/WebviewTestModel.dart';
|
import 'package:vbvs_app/pages/mh_page/test/WebviewTestModel.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/user/controller/bind_tel_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/user/controller/bind_tel_controller.dart';
|
||||||
@@ -902,6 +901,7 @@ class MyApp extends StatelessWidget {
|
|||||||
Get.lazyPut(() => PrivacyPdfController()),
|
Get.lazyPut(() => PrivacyPdfController()),
|
||||||
Get.lazyPut(() => AuthBindTelController()),
|
Get.lazyPut(() => AuthBindTelController()),
|
||||||
Get.lazyPut(() => CommonMessageSettingController()),
|
Get.lazyPut(() => CommonMessageSettingController()),
|
||||||
|
Get.lazyPut(() => MHTDeviceUpgradeController()),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class UserModel {
|
|||||||
int? created_at;
|
int? created_at;
|
||||||
String? email;
|
String? email;
|
||||||
bool? test;
|
bool? test;
|
||||||
|
bool? fac;//是否测试,可以全部升级
|
||||||
|
|
||||||
UserModel();
|
UserModel();
|
||||||
static UserModel fromJson(Map<String, dynamic> json) =>
|
static UserModel fromJson(Map<String, dynamic> json) =>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@ UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel()
|
|||||||
..deleted = (json['deleted'] as num?)?.toInt()
|
..deleted = (json['deleted'] as num?)?.toInt()
|
||||||
..status = json['status'] as String?
|
..status = json['status'] as String?
|
||||||
..created_at = (json['created_at'] as num?)?.toInt()
|
..created_at = (json['created_at'] as num?)?.toInt()
|
||||||
..email = json['email'] as String?;
|
..email = json['email'] as String?
|
||||||
|
..test = json['test'] as bool?
|
||||||
|
..fac = json['fac'] as bool?;
|
||||||
|
|
||||||
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
||||||
'uid': instance.uid,
|
'uid': instance.uid,
|
||||||
@@ -33,4 +35,6 @@ Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
|||||||
'status': instance.status,
|
'status': instance.status,
|
||||||
'created_at': instance.created_at,
|
'created_at': instance.created_at,
|
||||||
'email': instance.email,
|
'email': instance.email,
|
||||||
|
'test': instance.test,
|
||||||
|
'fac': instance.fac,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -91,9 +91,11 @@ class _InstantBodyPageState extends State<InstantBodyPage>
|
|||||||
bodyMotion = data['bodyMotion'] == null ? -1 : data['bodyMotion'];
|
bodyMotion = data['bodyMotion'] == null ? -1 : data['bodyMotion'];
|
||||||
breathrate = data["breathRate"] == null ? -1 : data["breathRate"];
|
breathrate = data["breathRate"] == null ? -1 : data["breathRate"];
|
||||||
heartrate = data['heartRate'] == null ? -1 : data['heartRate'];
|
heartrate = data['heartRate'] == null ? -1 : data['heartRate'];
|
||||||
snores = data['snores'] == null || data['snores'] == ""
|
snores = data['snores'] == null ||
|
||||||
|
data['snores'] == "" ||
|
||||||
|
data['snores'] == "否".tr
|
||||||
? "否".tr
|
? "否".tr
|
||||||
: "是".tr;
|
: "${data['snores']}".tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ class _MessagePageState extends State<MessagePage> {
|
|||||||
_pageController =
|
_pageController =
|
||||||
PageController(initialPage: messageController.model.type == 1 ? 0 : 1);
|
PageController(initialPage: messageController.model.type == 1 ? 0 : 1);
|
||||||
messageController.getMessageStatus();
|
messageController.getMessageStatus();
|
||||||
// _fetchMessageData();
|
_fetchMessageData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _fetchMessageData() {
|
void _fetchMessageData() {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import 'package:vbvs_app/model/BleDeviceData.dart';
|
|||||||
import 'package:vbvs_app/model/api_response.dart';
|
import 'package:vbvs_app/model/api_response.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/component/tool/BedControlService.dart';
|
import 'package:vbvs_app/pages/mh_page/device/component/tool/BedControlService.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/component/tool/DeviceType.dart';
|
|
||||||
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
||||||
@@ -177,7 +176,7 @@ class _DeviceComponentWidgetState extends State<DeviceComponentWidget> {
|
|||||||
),
|
),
|
||||||
].divide(SizedBox(width: 33.rpx)),
|
].divide(SizedBox(width: 33.rpx)),
|
||||||
),
|
),
|
||||||
].divide(SizedBox(height: 37.rpx)),
|
].divide(SizedBox(height: 20.rpx)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
CustomCard(
|
CustomCard(
|
||||||
@@ -340,7 +339,7 @@ class _DeviceComponentWidgetState extends State<DeviceComponentWidget> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
].divide(SizedBox(height: 37.rpx)),
|
].divide(SizedBox(height: 20.rpx)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
637
lib/pages/mh_page/device/component/UpgradeDevice.dart
Normal file
637
lib/pages/mh_page/device/component/UpgradeDevice.dart
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
||||||
|
import 'package:easydevice/easydevice.dart';
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||||
|
import 'package:mhtctrl/mhtctrl.dart';
|
||||||
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.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/common/util/requestWithLog.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
|
import 'package:vbvs_app/enum/APPDeviceUpgrade.dart';
|
||||||
|
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/component/tool/BedControlService.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
||||||
|
|
||||||
|
class UpgradeDevice extends StatefulWidget {
|
||||||
|
BlueToothDataModel bleDevice;
|
||||||
|
var deviceType;
|
||||||
|
|
||||||
|
UpgradeDevice({
|
||||||
|
super.key,
|
||||||
|
required this.bleDevice,
|
||||||
|
required this.deviceType,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<UpgradeDevice> createState() => _UpgradeDeviceState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _UpgradeDeviceState extends State<UpgradeDevice> {
|
||||||
|
ThemeController themeController = Get.find();
|
||||||
|
MHTBlueToothController blueteethBindController = Get.find();
|
||||||
|
MHTHomeController homeController = Get.find();
|
||||||
|
var lisObj;
|
||||||
|
late MattressControlService service;
|
||||||
|
late BedControlService bedService;
|
||||||
|
MHTBlueToothController mhtBlueToothController = Get.find();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Map device = {
|
||||||
|
"name": widget.bleDevice.name,
|
||||||
|
"mac".tr: widget.bleDevice.mac,
|
||||||
|
"rssi": widget.bleDevice.rssi,
|
||||||
|
"bind": widget.bleDevice.bind,
|
||||||
|
"version": widget.bleDevice.version,
|
||||||
|
"selected": widget.bleDevice.selected,
|
||||||
|
};
|
||||||
|
// ef.log("[rssi绘制]:--》${widget.bleDevice.rssi}");
|
||||||
|
return ClickableContainer(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
highlightColor: Colors.white,
|
||||||
|
borderRadius: 20.rpx,
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 36.rpx, 36.rpx),
|
||||||
|
onTap: () async {
|
||||||
|
widget.bleDevice.selected = !widget.bleDevice.selected!;
|
||||||
|
device['selected'] = !device['selected'];
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${device['name']}",
|
||||||
|
style: TextStyle(
|
||||||
|
color: stringToColor("#333333"), fontSize: 30.rpx),
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
var aa = mhtBlueToothController.allSelect.value;
|
||||||
|
print("${aa}");
|
||||||
|
double h = 33.rpx;
|
||||||
|
bool check = device['selected'] ?? false;
|
||||||
|
if (widget.bleDevice.upgradeStatus! ==
|
||||||
|
APPDeviceUpgrade.upgrading.value) {
|
||||||
|
return ClickableContainer(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
highlightColor: Colors.transparent,
|
||||||
|
padding: EdgeInsets.all(0),
|
||||||
|
onTap: () {
|
||||||
|
//取消升级
|
||||||
|
MultiDeviceFirmwareUpdater().cancelUpgrade(widget.bleDevice.mac);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"${widget.bleDevice.process}%",
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc9,
|
||||||
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"取消".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: themeController.currentColor.sc9,
|
||||||
|
fontSize: AppConstants().normal_text_fontSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
height: 33.rpx,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
height: h,
|
||||||
|
width: h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(h / 2),
|
||||||
|
border: Border.all(
|
||||||
|
width: check ? 1 : 0.5,
|
||||||
|
color: Color(0xFFC8CBD2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: check
|
||||||
|
? Center(
|
||||||
|
child: ClipOval(
|
||||||
|
child: Container(
|
||||||
|
width: h * 0.6,
|
||||||
|
height: h * 0.6,
|
||||||
|
color: const Color(0xFF6BFDAC),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(width: 10.rpx)),
|
||||||
|
));
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.sizeOf(context).width * 0.14,
|
||||||
|
constraints: BoxConstraints(minWidth: 106.rpx),
|
||||||
|
child: Text(
|
||||||
|
"MAC".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: stringToColor("#929699"),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${device['mac']}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: stringToColor("#333333")),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(width: 33.rpx)),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.sizeOf(context).width * 0.14,
|
||||||
|
constraints: BoxConstraints(minWidth: 106.rpx),
|
||||||
|
child: Text(
|
||||||
|
"信号强度".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: stringToColor("#929699"),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${widget.bleDevice.rssi}" + "dBm".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: stringToColor("#333333")),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(width: 33.rpx)),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.sizeOf(context).width * 0.14,
|
||||||
|
constraints: BoxConstraints(minWidth: 106.rpx),
|
||||||
|
child: Text(
|
||||||
|
"当前版本".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: stringToColor("#929699"),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${device['version']}",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(width: 33.rpx)),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(height: 20.rpx)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
].divide(SizedBox(height: 20.rpx)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//更新设备绑定状态
|
||||||
|
void updateDeviceBindStatus(BleDeviceData device) {
|
||||||
|
String serviceAddress = ServiceConstant.service_address;
|
||||||
|
String serviceName = ServiceConstant.server_service;
|
||||||
|
String serviceApi = ServiceConstant.user_setting;
|
||||||
|
String mac = device.mac!;
|
||||||
|
String type = "device_bind_status_${mac}";
|
||||||
|
String queryUrl = "${serviceAddress}${serviceName}${serviceApi}";
|
||||||
|
Map<String, dynamic> data = {
|
||||||
|
"type": type,
|
||||||
|
"mac".tr: mac,
|
||||||
|
"wifi": false,
|
||||||
|
"celibration": false,
|
||||||
|
"person_info": false,
|
||||||
|
"time": DateTime.now().millisecondsSinceEpoch,
|
||||||
|
};
|
||||||
|
requestWithLog(
|
||||||
|
logTitle: "更新用户绑定流程".tr,
|
||||||
|
method: MyHttpMethod.put,
|
||||||
|
queryUrl: queryUrl,
|
||||||
|
data: data,
|
||||||
|
onSuccess: (res) {},
|
||||||
|
onFailure: (res) {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDeviceInfoSection(device, BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
device.name ?? '蓝牙绑定.默认设备名称'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFF6FAFD),
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
if (blueteethBindController.currentDeviceMac.value ==
|
||||||
|
device.mac) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 24.rpx,
|
||||||
|
height: 24.rpx,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
themeController.currentColor.sc1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"蓝牙绑定.信号强度".tr + ':${device.rssi ?? '-'}dBm',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFEBF2F8),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 40.rpx),
|
||||||
|
Text(
|
||||||
|
"蓝牙绑定.SN".tr + ':${device.sn ?? '-'}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFF5F9FD),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"蓝牙绑定.蓝牙地址".tr + ':${device.deviceId ?? '-'}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFF6FAFD),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"蓝牙绑定.mac".tr + ':${device.mac ?? '-'}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFF6FAFD),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"蓝牙绑定.网络".tr +
|
||||||
|
':${device.isOnline == true ? '蓝牙绑定.在线'.tr : '蓝牙绑定.离线'.tr}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFEBF2F8),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 145.rpx),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"蓝牙绑定.传感器".tr + ":",
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFEBF2F8),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
device.bind == false ? '蓝牙绑定.可绑定'.tr : '蓝牙绑定.已被绑定'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: device.bind == false
|
||||||
|
? const Color(0xFF1AD2B5)
|
||||||
|
: themeController.currentColor.sc9,
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"版本".tr + '${device.version ?? '-'}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: const Color(0xFFF6FAFD),
|
||||||
|
fontSize: 26.rpx,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(
|
||||||
|
height: 37.rpx,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取智能床/床垫mac
|
||||||
|
Future<String> getBindTHMAC(
|
||||||
|
BuildContext context, BlueToothDataModel device, Map deviceType) async {
|
||||||
|
const int maxRetries = 2;
|
||||||
|
const Duration timeout = Duration(seconds: 5);
|
||||||
|
String? macAddress;
|
||||||
|
try {
|
||||||
|
// 连接设备
|
||||||
|
THapp bledevice = THapp(device: device.scanResult.device);
|
||||||
|
await bledevice.connect();
|
||||||
|
var res2 = bledevice.isConnected;
|
||||||
|
if (!res2) {
|
||||||
|
edm.EasyDartModule.logger.error("蓝牙连接失败".tr);
|
||||||
|
DailyLogUtils.printLog("蓝牙连接失败".tr);
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "蓝牙连接失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
throw Exception("蓝牙连接失败".tr);
|
||||||
|
}
|
||||||
|
blueteethBindController.blueConnectFlag.value = 2;
|
||||||
|
blueteethBindController.currentDevice = bledevice;
|
||||||
|
await Future.delayed(Duration(seconds: 2));
|
||||||
|
|
||||||
|
if (deviceType['type'] == 3) {
|
||||||
|
//智能床垫
|
||||||
|
macAddress = await getMacFromType3(bledevice, timeout);
|
||||||
|
} else if (deviceType['type'] == 2) {
|
||||||
|
//智能床
|
||||||
|
macAddress = await getMacFromType2(bledevice, timeout);
|
||||||
|
} else {
|
||||||
|
throw Exception("不支持的设备类型".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (macAddress == null) {
|
||||||
|
throw Exception("未能获取到MAC地址".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// device.macA = macAddress;
|
||||||
|
print('MAC地址: $macAddress');
|
||||||
|
return macAddress;
|
||||||
|
} catch (e) {
|
||||||
|
blueteethBindController.currentDeviceMac.value = "";
|
||||||
|
edm.EasyDartModule.logger.error("蓝牙获取MAC失败:$e");
|
||||||
|
DailyLogUtils.printLog("蓝牙获取MAC失败:$e");
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
// text: e.message ?? "设备连接失败,请重试".tr,
|
||||||
|
text: "获取不到传感器mac,请重试".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fillTHMac(
|
||||||
|
String mac, BlueToothDataModel bleDevice, BuildContext context) async {
|
||||||
|
bool flag = false;
|
||||||
|
String serviceAddress = ServiceConstant.service_address;
|
||||||
|
String serviceName = ServiceConstant.server_service;
|
||||||
|
String serviceApi = ServiceConstant.get_bluetooth_device_status;
|
||||||
|
String queryUrl = "$serviceAddress$serviceName$serviceApi";
|
||||||
|
String macParam =
|
||||||
|
"mac=${Uri.encodeQueryComponent(mac.replaceAll(':', ''))}";
|
||||||
|
if (queryUrl.contains('?')) {
|
||||||
|
queryUrl += '&$macParam';
|
||||||
|
} else {
|
||||||
|
queryUrl += '?$macParam';
|
||||||
|
}
|
||||||
|
|
||||||
|
await requestWithLog(
|
||||||
|
logTitle: "获取设备状态".tr,
|
||||||
|
method: MyHttpMethod.get,
|
||||||
|
queryUrl: queryUrl,
|
||||||
|
onSuccess: (res) {
|
||||||
|
flag = true;
|
||||||
|
if (res.code != HttpStatusCodes.ok) return;
|
||||||
|
|
||||||
|
if (res.data != null && res.data is List) {
|
||||||
|
List<dynamic> responseList = res.data;
|
||||||
|
|
||||||
|
// 查找当前MAC对应的数据
|
||||||
|
String macKey = mac.replaceAll(':', '').toUpperCase();
|
||||||
|
for (var item in responseList) {
|
||||||
|
if (item['mac'.tr].toString().toUpperCase() == macKey) {
|
||||||
|
// 更新 bleDevice 的状态
|
||||||
|
//如果传感器已经绑定 暂时不处理
|
||||||
|
// bleDevice.bind = item['bind'] ?? bleDevice.bind;
|
||||||
|
bleDevice.macA = item['mac'.tr];
|
||||||
|
if (item['bindMac'] != null &&
|
||||||
|
item['bindMac'].toString().isNotEmpty) {
|
||||||
|
bleDevice.macB = item['bindMac'];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure: (res) {
|
||||||
|
flag = false;
|
||||||
|
TopSlideNotification.show(context,
|
||||||
|
text: "获取设备状态失败".tr, textColor: themeController.currentColor.sc9);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getMacFromType3(THapp bledevice, Duration timeout) async {
|
||||||
|
final read = bledevice.getresource('fff0/fff1');
|
||||||
|
await read!.characteristic.setNotifyValue(true);
|
||||||
|
final write = bledevice.getresource('fff0/fff2');
|
||||||
|
|
||||||
|
const int maxRetries = 2;
|
||||||
|
for (int attempt = 0; attempt < maxRetries; attempt++) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final subscription = read.characteristic.onValueReceived.listen((data) {
|
||||||
|
if (data.length >= 14) {
|
||||||
|
completer.complete(parseMacFromBleResponse(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final order = [
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0x00,
|
||||||
|
0x03,
|
||||||
|
0x40,
|
||||||
|
0x01,
|
||||||
|
0x01,
|
||||||
|
0x00,
|
||||||
|
0x45,
|
||||||
|
0xFD
|
||||||
|
];
|
||||||
|
await write!.characteristic.write(order);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final mac = await completer.future.timeout(timeout);
|
||||||
|
if (mac == null || mac.isEmpty) {
|
||||||
|
throw Exception("获取MAC失败".tr);
|
||||||
|
}
|
||||||
|
if (mac == "000000000000") {
|
||||||
|
throw Exception("获取MAC失败".tr);
|
||||||
|
}
|
||||||
|
await subscription.cancel();
|
||||||
|
return mac;
|
||||||
|
} catch (_) {
|
||||||
|
await subscription.cancel();
|
||||||
|
if (attempt == maxRetries - 1) rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw Exception("获取MAC超时".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String> getMacFromType2(THapp bledevice, Duration timeout) async {
|
||||||
|
try {
|
||||||
|
final read = bledevice.getresource('ffe0/ffe1');
|
||||||
|
await read!.characteristic.setNotifyValue(true);
|
||||||
|
|
||||||
|
final write =
|
||||||
|
bledevice.getresource('ffe0/ffe1'); // 与 read 同 characteristic
|
||||||
|
const int maxRetries = 2;
|
||||||
|
for (int attempt = 0; attempt < maxRetries; attempt++) {
|
||||||
|
final completer = Completer<String>();
|
||||||
|
final subscription = read.characteristic.onValueReceived.listen((data) {
|
||||||
|
if (data.length >= 17) {
|
||||||
|
completer.complete(parseMacFromTH2Response(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final order = [0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x0C, 0x0B, 0x0A];
|
||||||
|
int checksum = order.reduce((a, b) => a + b) & 0xFFFF;
|
||||||
|
order.add(checksum & 0xFF); // 低位
|
||||||
|
order.add((checksum >> 8) & 0xFF); // 高位
|
||||||
|
|
||||||
|
await write!.characteristic.write(order);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final mac = await completer.future.timeout(timeout);
|
||||||
|
await subscription.cancel();
|
||||||
|
return mac;
|
||||||
|
} catch (_) {
|
||||||
|
await subscription.cancel();
|
||||||
|
if (attempt == maxRetries - 1) rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ef.log("[获取设备 MAC]:失败:$e");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("获取MAC超时".tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String parseMacFromBleResponse(List<int> data) {
|
||||||
|
// 先做简单的合法性判断
|
||||||
|
if (data.length >= 17 &&
|
||||||
|
data[0] == 0xFF &&
|
||||||
|
data[1] == 0xFF &&
|
||||||
|
data[2] == 0xFF &&
|
||||||
|
data[3] == 0xFF &&
|
||||||
|
data[6] == 0x40 &&
|
||||||
|
data[7] == 0x01) {
|
||||||
|
// 取出 Byte8 ~ Byte13
|
||||||
|
List<int> macBytes = data.sublist(8, 14);
|
||||||
|
String macAddress = macBytes
|
||||||
|
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
||||||
|
.join('');
|
||||||
|
return macAddress;
|
||||||
|
} else {
|
||||||
|
throw Exception("BLE返回数据格式不正确".tr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String parseMacFromTH2Response(List<int> data) {
|
||||||
|
if (data.length < 17) {
|
||||||
|
throw Exception("数据长度不足,无法解析MAC".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = data[8];
|
||||||
|
if (status != 0x03 && status != 0x04) {
|
||||||
|
throw Exception("未连接心率带".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取9~14字节的MAC地址
|
||||||
|
List<int> macBytes = data.sublist(9, 15);
|
||||||
|
return macBytes
|
||||||
|
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||||
|
.join(":")
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import 'package:json_annotation/json_annotation.dart';
|
|||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FirmwareVersionService.dart';
|
||||||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
import 'package:vbvs_app/common/util/requestWithLog.dart';
|
||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
@@ -24,7 +25,7 @@ class MHTBlueToothModel {
|
|||||||
double? singal = -100;
|
double? singal = -100;
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
List<BlueToothDataModel>? blueRawData; //蓝牙原始数据
|
List<BlueToothDataModel>? blueRawData = []; //蓝牙原始数据
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
List<BlueToothDataModel>? deviceDataStatus; //已经请求过状态的数据
|
List<BlueToothDataModel>? deviceDataStatus; //已经请求过状态的数据
|
||||||
|
|
||||||
@@ -76,7 +77,11 @@ class MHTBlueToothController extends GetControllerEx<MHTBlueToothModel> {
|
|||||||
RxMap<String, Map> localUpgradeMac = <String, Map>{}.obs; //mac 进度
|
RxMap<String, Map> localUpgradeMac = <String, Map>{}.obs; //mac 进度
|
||||||
String? currentUpgradeVersion; //最新版本号
|
String? currentUpgradeVersion; //最新版本号
|
||||||
String? currentUpgradeName; //最新固件名
|
String? currentUpgradeName; //最新固件名
|
||||||
String? currentUpgradeUrl; //最新固件名
|
String? currentUpgradeUrl; //最新固件下载地址
|
||||||
|
List<FirmwareVersionInfo> firmwareList = []; //固件版本列表
|
||||||
|
RxBool allSelect = false.obs; //升级是否全选
|
||||||
|
RxBool autoUpgrade = false.obs; //是否自动升级
|
||||||
|
|
||||||
|
|
||||||
void startStatusPolling() {
|
void startStatusPolling() {
|
||||||
updateDeviceStatus().then((res) {
|
updateDeviceStatus().then((res) {
|
||||||
|
|||||||
176
lib/pages/mh_page/device/device_maintain.dart
Normal file
176
lib/pages/mh_page/device/device_maintain.dart
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||||
|
import 'package:vbvs_app/common/color/appConstants.dart';
|
||||||
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FitTool.dart';
|
||||||
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/CustomCard.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/controller/mh_controller/mh_language_controller.dart';
|
||||||
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
|
import 'package:vbvs_app/model/api_response.dart';
|
||||||
|
|
||||||
|
class DeviceMaintain extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_DeviceMaintainState createState() => _DeviceMaintainState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeviceMaintainState extends State<DeviceMaintain> {
|
||||||
|
MHLanguageController languageController = Get.find();
|
||||||
|
UserInfoController userInfoController = Get.find();
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(builder: (context, bodySize) {
|
||||||
|
return GestureDetector(
|
||||||
|
// onTap: () => FocusScope.of(context).unfocus(),,
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/images/new_background.png'), // 本地图片
|
||||||
|
fit: BoxFit.fill, // 填满整个 Container
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
iconTheme: const IconThemeData(color: Colors.white),
|
||||||
|
titleSpacing: 0,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
title: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 180.rpx,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// 中间居中的标题
|
||||||
|
Text(
|
||||||
|
'设备维护'.tr,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 左侧图标
|
||||||
|
Positioned(
|
||||||
|
left: 0.rpx,
|
||||||
|
child: returnIconButtomNew(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
top: true,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0.rpx, 0.rpx, 0.rpx, 0),
|
||||||
|
child: CustomCard(
|
||||||
|
borderRadius: 16.rpx,
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed("/deviceUpgrade");
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
Color(0xFF003058),
|
||||||
|
], // 渐变色是同一个色,也可以根据需要调整
|
||||||
|
child: Container(
|
||||||
|
width:
|
||||||
|
// MediaQuery.sizeOf(context).width * 0.66,
|
||||||
|
bodySize.maxWidth,
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.055,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 500.rpx,
|
||||||
|
minHeight: 90.rpx,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'批量升级'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize:
|
||||||
|
AppConstants().normal_text_fontSize,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(
|
||||||
|
width: 17.rpx,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0.rpx, 0.rpx, 0.rpx, 0),
|
||||||
|
child: CustomCard(
|
||||||
|
borderRadius: 16.rpx,
|
||||||
|
onTap: () {
|
||||||
|
// Get.toNamed("/deviceUpgrade");
|
||||||
|
NewTopSlideNotification.show(
|
||||||
|
text: "功能开发中...".tr,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
colors: [
|
||||||
|
Color(0xFF003058),
|
||||||
|
], // 渐变色是同一个色,也可以根据需要调整
|
||||||
|
child: Container(
|
||||||
|
width:
|
||||||
|
// MediaQuery.sizeOf(context).width * 0.66,
|
||||||
|
bodySize.maxWidth,
|
||||||
|
height: MediaQuery.sizeOf(context).height * 0.055,
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: 500.rpx,
|
||||||
|
minHeight: 90.rpx,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'远程诊断'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize:
|
||||||
|
AppConstants().normal_text_fontSize,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(
|
||||||
|
width: 17.rpx,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(SizedBox(
|
||||||
|
height: 20.rpx,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FirmwareVersionService.dart';
|
||||||
|
|
||||||
class BlueToothDataModel {
|
class BlueToothDataModel {
|
||||||
String name; // 设备型号
|
String name; // 设备型号
|
||||||
@@ -13,6 +14,12 @@ class BlueToothDataModel {
|
|||||||
DateTime lastSeen; // 最后可见时间
|
DateTime lastSeen; // 最后可见时间
|
||||||
String? deviceID; // 设备ID
|
String? deviceID; // 设备ID
|
||||||
int? version; // ✅ 新增版本号,可为空
|
int? version; // ✅ 新增版本号,可为空
|
||||||
|
int? rssi; //信号强度
|
||||||
|
bool? selected; //是否选中
|
||||||
|
int? process = 0; //升级进度
|
||||||
|
int? newVersion; //升级版本
|
||||||
|
FirmwareVersionInfo? upgradeInfo;//升级固件信息
|
||||||
|
int? upgradeStatus;//升级状态
|
||||||
|
|
||||||
BlueToothDataModel({
|
BlueToothDataModel({
|
||||||
this.name = '',
|
this.name = '',
|
||||||
@@ -25,6 +32,12 @@ class BlueToothDataModel {
|
|||||||
required this.lastSeen,
|
required this.lastSeen,
|
||||||
this.deviceID,
|
this.deviceID,
|
||||||
this.version, // ✅ 构造函数参数
|
this.version, // ✅ 构造函数参数
|
||||||
|
this.rssi, // ✅ 构造函数参数
|
||||||
|
this.selected, // ✅ 构造函数参数
|
||||||
|
this.process, // ✅ 构造函数参数
|
||||||
|
this.newVersion, // ✅ 构造函数参数
|
||||||
|
this.upgradeInfo, // ✅ 构造函数参数
|
||||||
|
this.upgradeStatus, // ✅ 构造函数参数
|
||||||
});
|
});
|
||||||
|
|
||||||
factory BlueToothDataModel.fromScanResult(
|
factory BlueToothDataModel.fromScanResult(
|
||||||
@@ -35,6 +48,12 @@ class BlueToothDataModel {
|
|||||||
String mac = '',
|
String mac = '',
|
||||||
String? deviceID,
|
String? deviceID,
|
||||||
int? version, // ✅ 工厂方法参数
|
int? version, // ✅ 工厂方法参数
|
||||||
|
int? rssi, // ✅ 工厂方法参数
|
||||||
|
bool? selected, // ✅ 工厂方法参数
|
||||||
|
int? process, // ✅ 工厂方法参数
|
||||||
|
int? newVersion, // ✅ 工厂方法参数
|
||||||
|
FirmwareVersionInfo? upgradeInfo, // ✅ 工厂方法参数
|
||||||
|
int? upgradeStatus, // ✅ 工厂方法参数
|
||||||
}) {
|
}) {
|
||||||
String finalName =
|
String finalName =
|
||||||
name.isNotEmpty ? name : (result.advertisementData.localName ?? '');
|
name.isNotEmpty ? name : (result.advertisementData.localName ?? '');
|
||||||
@@ -50,6 +69,12 @@ class BlueToothDataModel {
|
|||||||
lastSeen: DateTime.now(),
|
lastSeen: DateTime.now(),
|
||||||
deviceID: deviceID,
|
deviceID: deviceID,
|
||||||
version: version, // ✅ 赋值
|
version: version, // ✅ 赋值
|
||||||
|
rssi: rssi,
|
||||||
|
selected: selected,
|
||||||
|
process: process,
|
||||||
|
newVersion: newVersion,
|
||||||
|
upgradeInfo: upgradeInfo,
|
||||||
|
upgradeStatus: upgradeStatus,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
lib/pages/mh_page/device/upgrade/BatchUpgradeManager.dart
Normal file
165
lib/pages/mh_page/device/upgrade/BatchUpgradeManager.dart
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
// 批量升级封装类
|
||||||
|
import 'package:easydevice/easydevice.dart';
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart';
|
||||||
|
|
||||||
|
class BatchUpgradeManager {
|
||||||
|
static final BatchUpgradeManager _instance = BatchUpgradeManager._internal();
|
||||||
|
factory BatchUpgradeManager() => _instance;
|
||||||
|
BatchUpgradeManager._internal();
|
||||||
|
|
||||||
|
final MultiDeviceFirmwareUpdater _updater = MultiDeviceFirmwareUpdater();
|
||||||
|
final MHTDeviceUpgradeController _upgradeController = Get.find();
|
||||||
|
|
||||||
|
bool _isBatchUpgrading = false;
|
||||||
|
final List<BlueToothDataModel> _batchDevices = [];
|
||||||
|
|
||||||
|
// 开始批量升级
|
||||||
|
Future<void> startBatchUpgrade(List<BlueToothDataModel> devices) async {
|
||||||
|
if (_isBatchUpgrading) {
|
||||||
|
throw Exception("批量升级正在进行中".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isBatchUpgrading = true;
|
||||||
|
_batchDevices.clear();
|
||||||
|
_upgradeController.clearUpgradingList();
|
||||||
|
|
||||||
|
// 验证设备并添加到升级列表
|
||||||
|
for (var device in devices) {
|
||||||
|
if (device.selected == true) {
|
||||||
|
_batchDevices.add(device);
|
||||||
|
_upgradeController.addToUpgradingList(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_batchDevices.isEmpty) {
|
||||||
|
_isBatchUpgrading = false;
|
||||||
|
throw Exception("没有选择要升级的设备".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始批量升级
|
||||||
|
try {
|
||||||
|
await _startBatchUpgradeInternal();
|
||||||
|
} catch (e) {
|
||||||
|
_isBatchUpgrading = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部批量升级实现
|
||||||
|
Future<void> _startBatchUpgradeInternal() async {
|
||||||
|
final List<Future> upgradeFutures = [];
|
||||||
|
|
||||||
|
for (var device in _batchDevices) {
|
||||||
|
final future = _startSingleDeviceUpgrade(device);
|
||||||
|
upgradeFutures.add(future);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待所有升级完成
|
||||||
|
try {
|
||||||
|
await Future.wait(upgradeFutures, eagerError: false);
|
||||||
|
} finally {
|
||||||
|
_isBatchUpgrading = false;
|
||||||
|
_showBatchUpgradeCompleteNotification();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个设备升级
|
||||||
|
Future<void> _startSingleDeviceUpgrade(BlueToothDataModel device) async {
|
||||||
|
try {
|
||||||
|
// 创建 THapp 实例
|
||||||
|
final thapp = THapp(device: device.scanResult.device);
|
||||||
|
|
||||||
|
// 获取固件URL - 需要您提供实现
|
||||||
|
final firmwareUrl = await _getFirmwareUrl();
|
||||||
|
if (firmwareUrl == null) {
|
||||||
|
throw Exception("固件URL为空".tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用现有的升级器进行升级
|
||||||
|
await _updater.startUpgrade(
|
||||||
|
thapp: thapp,
|
||||||
|
mac: device.mac,
|
||||||
|
firmwareUrl: firmwareUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 升级成功,从列表中移除
|
||||||
|
_upgradeController.removeFromUpgradingList(device.mac);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
// 升级失败,也从列表中移除(或者您可以保留失败设备用于重试)
|
||||||
|
_upgradeController.removeFromUpgradingList(device.mac);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取固件URL - 需要您根据实际情况实现
|
||||||
|
Future<String?> _getFirmwareUrl() async {
|
||||||
|
// 这里可以从配置、用户选择或其他地方获取固件URL
|
||||||
|
// 例如:return _upgradeController.selectedFirmwareUrl;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消批量升级
|
||||||
|
void cancelBatchUpgrade() {
|
||||||
|
for (var device in _batchDevices) {
|
||||||
|
if (_updater.isDeviceUpgrading(device.mac)) {
|
||||||
|
_updater.cancelUpgrade(device.mac);
|
||||||
|
}
|
||||||
|
_upgradeController.removeFromUpgradingList(device.mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isBatchUpgrading = false;
|
||||||
|
_batchDevices.clear();
|
||||||
|
|
||||||
|
NewTopSlideNotification.show(
|
||||||
|
text: "批量升级已取消".tr,
|
||||||
|
textColor: Colors.orange,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示批量升级完成通知
|
||||||
|
void _showBatchUpgradeCompleteNotification() {
|
||||||
|
final allStatus = _updater.getAllUpgradeStatus();
|
||||||
|
final completedCount = allStatus.values.where((status) =>
|
||||||
|
status['status'] == 'completed').length;
|
||||||
|
final failedCount = allStatus.values.where((status) =>
|
||||||
|
status['status'] == 'failed').length;
|
||||||
|
|
||||||
|
String message;
|
||||||
|
if (failedCount == 0) {
|
||||||
|
message = "批量升级完成,所有 ${_batchDevices.length} 个设备升级成功".tr;
|
||||||
|
} else {
|
||||||
|
message = "批量升级完成,$completedCount 个成功,$failedCount 个失败".tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
NewTopSlideNotification.show(
|
||||||
|
text: message,
|
||||||
|
textColor: failedCount == 0 ? Colors.green : Colors.orange,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否正在批量升级
|
||||||
|
bool get isBatchUpgrading => _isBatchUpgrading;
|
||||||
|
|
||||||
|
// 获取批量升级统计
|
||||||
|
Map<String, int> getBatchUpgradeStatistics() {
|
||||||
|
final allStatus = _updater.getAllUpgradeStatus();
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total': _batchDevices.length,
|
||||||
|
'completed': allStatus.values.where((status) =>
|
||||||
|
status['status'] == 'completed').length,
|
||||||
|
'failed': allStatus.values.where((status) =>
|
||||||
|
status['status'] == 'failed').length,
|
||||||
|
'upgrading': allStatus.values.where((status) =>
|
||||||
|
status['status'] == 'upgrading' || status['status'] == 'downloading').length,
|
||||||
|
'waiting': allStatus.values.where((status) =>
|
||||||
|
status['status'] == 'waiting').length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
1165
lib/pages/mh_page/device/upgrade/device_upgrade.dart
Normal file
1165
lib/pages/mh_page/device/upgrade/device_upgrade.dart
Normal file
File diff suppressed because it is too large
Load Diff
244
lib/pages/mh_page/device/upgrade/device_upgrade_controller.dart
Normal file
244
lib/pages/mh_page/device/upgrade/device_upgrade_controller.dart
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
import 'package:easydevice/easydevice.dart';
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:vbvs_app/common/util/BluetoothFirmwareUpdater.dart';
|
||||||
|
import 'package:vbvs_app/common/util/FirmwareVersionService.dart';
|
||||||
|
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||||||
|
import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/enum/APPDeviceUpgrade.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
||||||
|
|
||||||
|
part 'device_upgrade_controller.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class MHTDeviceUpgradeModel {
|
||||||
|
bool? bluetooth = false; // 蓝牙开关
|
||||||
|
double? singal = -100;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
List<BlueToothDataModel>? blueRawData = []; //蓝牙原始数据
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
List<BlueToothDataModel>? upgradingDevices = []; // 正在升级的设备列表
|
||||||
|
|
||||||
|
MHTDeviceUpgradeModel();
|
||||||
|
|
||||||
|
static MHTDeviceUpgradeModel fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$MHTDeviceUpgradeModelFromJson(json);
|
||||||
|
Map<String, dynamic> toJson() => _$MHTDeviceUpgradeModelToJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MHTDeviceUpgradeController
|
||||||
|
extends GetControllerEx<MHTDeviceUpgradeModel> {
|
||||||
|
MHTDeviceUpgradeController() {
|
||||||
|
attr = GetModel(MHTDeviceUpgradeModel()).obs;
|
||||||
|
}
|
||||||
|
|
||||||
|
//选择的固件
|
||||||
|
FirmwareVersionInfo? selectVerison; //当前选中的固件信息
|
||||||
|
int maxLimit = 5;
|
||||||
|
MHTBlueToothController mhtBlueToothController = Get.find();
|
||||||
|
int? upgradeType = 1;
|
||||||
|
|
||||||
|
List<FirmwareVersionInfo>? firmwareList = [];
|
||||||
|
|
||||||
|
RxList? selectedDiseaseIds = [].obs; //已选中的列表
|
||||||
|
RxList diseaseList = [].obs; //升级类型列表
|
||||||
|
|
||||||
|
// 添加设备到升级列表
|
||||||
|
void addToUpgradingList(BlueToothDataModel device) {
|
||||||
|
if (!model.upgradingDevices!.any((d) => d.mac == device.mac)) {
|
||||||
|
model.upgradingDevices!.add(device);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从升级列表移除设备
|
||||||
|
void removeFromUpgradingList(String mac) {
|
||||||
|
//todo 移除蓝牙连接
|
||||||
|
model.upgradingDevices!.removeWhere((device) => device.mac == mac);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空升级列表
|
||||||
|
void clearUpgradingList() {
|
||||||
|
//todo 移除蓝牙连接
|
||||||
|
model.upgradingDevices!.clear();
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取正在升级的设备数量
|
||||||
|
int get upgradingDeviceCount => model.upgradingDevices?.length ?? 0;
|
||||||
|
|
||||||
|
// 检查设备是否正在升级
|
||||||
|
bool isDeviceUpgrading(String mac) {
|
||||||
|
return model.upgradingDevices!.any((device) => device.mac == mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
//开始更新
|
||||||
|
Future<void> startUpgrade() async {
|
||||||
|
// 获取全部设备数据
|
||||||
|
var allDevices = model.blueRawData;
|
||||||
|
|
||||||
|
// 获取正在升级的设备数据
|
||||||
|
var upgradingDevices = model.upgradingDevices;
|
||||||
|
|
||||||
|
// 筛选出所有选中的设备
|
||||||
|
var selectedDevices =
|
||||||
|
allDevices!.where((device) => device.selected == true).toList();
|
||||||
|
|
||||||
|
// 排除正在升级的设备(即将已经在升级中的设备从选中的设备列表中移除)
|
||||||
|
var devicesToUpgrade = selectedDevices.where((device) {
|
||||||
|
// 检查设备是否在正在升级的设备列表中
|
||||||
|
return !upgradingDevices!
|
||||||
|
.any((upgradingDevice) => upgradingDevice.mac == device.mac);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (devicesToUpgrade.isEmpty) {
|
||||||
|
NewTopSlideNotification.show(
|
||||||
|
text: "没有需要升级的设备".tr, textColor: themeController.currentColor.sc9);
|
||||||
|
ef.log("没有需要升级的设备");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查固件信息
|
||||||
|
if (selectVerison == null || selectVerison!.downloadUrl.isEmpty) {
|
||||||
|
NewTopSlideNotification.show(
|
||||||
|
text: "请先选择有效的固件版本".tr, textColor: themeController.currentColor.sc9);
|
||||||
|
throw Exception("请先选择有效的固件版本");
|
||||||
|
}
|
||||||
|
|
||||||
|
// mhtBlueToothController.currentUpgradeUrl = selectVerison!.downloadUrl;
|
||||||
|
// 打印最终需要升级的设备
|
||||||
|
ef.log("需要升级的设备:${devicesToUpgrade}");
|
||||||
|
|
||||||
|
for (var device in devicesToUpgrade) {
|
||||||
|
// await _startSingleDeviceUpgrade(device);
|
||||||
|
_startSingleDeviceUpgrade(device);
|
||||||
|
}
|
||||||
|
if (devicesToUpgrade.isNotEmpty) {
|
||||||
|
devicesToUpgrade.forEach((device) {
|
||||||
|
// 添加设备到正在升级列表
|
||||||
|
device.process = 0;
|
||||||
|
device.upgradeInfo = selectVerison;
|
||||||
|
addToUpgradingList(device);
|
||||||
|
// 发送升级指令
|
||||||
|
// mhtBlueToothController.sendUpgradeCommand(device);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
print(model.upgradingDevices!);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 启动单个设备升级
|
||||||
|
Future<void> _startSingleDeviceUpgrade(BlueToothDataModel device) async {
|
||||||
|
try {
|
||||||
|
// 更新设备状态
|
||||||
|
device.process = 0;
|
||||||
|
device.upgradeInfo = selectVerison;
|
||||||
|
device.selected = false; // 升级开始后取消选中状态
|
||||||
|
device.upgradeStatus = APPDeviceUpgrade.upgrading.value;
|
||||||
|
|
||||||
|
// 添加到升级列表
|
||||||
|
addToUpgradingList(device);
|
||||||
|
|
||||||
|
// 创建 THapp 实例
|
||||||
|
final thapp = THapp(device: device.scanResult.device);
|
||||||
|
|
||||||
|
// 确保设备连接
|
||||||
|
if (!thapp.isConnected) {
|
||||||
|
await thapp.device.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 MultiDeviceFirmwareUpdater 开始升级
|
||||||
|
await MultiDeviceFirmwareUpdater().startUpgrade(
|
||||||
|
thapp: thapp,
|
||||||
|
mac: device.mac,
|
||||||
|
firmwareUrl: selectVerison!.downloadUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
ef.log("设备 ${device.mac} 升级任务已启动");
|
||||||
|
} catch (e, stack) {
|
||||||
|
ef.log("设备 ${device.mac} 升级启动失败: $e\n$stack");
|
||||||
|
|
||||||
|
// 更新设备状态为失败
|
||||||
|
device.process = -1;
|
||||||
|
device.upgradeStatus = APPDeviceUpgrade.fail.value;
|
||||||
|
update();
|
||||||
|
|
||||||
|
// 从升级列表中移除失败设备(可选,取决于业务需求)
|
||||||
|
// removeFromUpgradingList(device.mac);
|
||||||
|
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消单个设备升级
|
||||||
|
void cancelUpgrade(String mac) {
|
||||||
|
try {
|
||||||
|
MultiDeviceFirmwareUpdater().cancelUpgrade(mac);
|
||||||
|
removeFromUpgradingList(mac);
|
||||||
|
ef.log("设备 $mac 升级已取消");
|
||||||
|
|
||||||
|
} catch (e, stack) {
|
||||||
|
ef.log("取消设备 $mac 升级失败: $e\n$stack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消所有设备升级
|
||||||
|
void cancelAllUpgrades() {
|
||||||
|
try {
|
||||||
|
MultiDeviceFirmwareUpdater().cancelAllUpgrades();
|
||||||
|
clearUpgradingList();
|
||||||
|
ef.log("所有设备升级已取消");
|
||||||
|
} catch (e, stack) {
|
||||||
|
ef.log("取消所有设备升级失败: $e\n$stack");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备升级状态
|
||||||
|
Map<String, dynamic>? getUpgradeStatus(String mac) {
|
||||||
|
return MultiDeviceFirmwareUpdater().getUpgradeStatus(mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有设备升级状态
|
||||||
|
Map<String, Map<String, dynamic>> getAllUpgradeStatus() {
|
||||||
|
return MultiDeviceFirmwareUpdater().getAllUpgradeStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查设备是否正在升级(包括底层状态)
|
||||||
|
bool isDeviceInUpgrading(String mac) {
|
||||||
|
return MultiDeviceFirmwareUpdater().isDeviceUpgrading(mac) ||
|
||||||
|
isDeviceUpgrading(mac);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理已完成的任务
|
||||||
|
void cleanupCompletedTasks() {
|
||||||
|
MultiDeviceFirmwareUpdater().cleanupCompletedTasks();
|
||||||
|
|
||||||
|
// 同时清理本地升级列表中已完成的任务
|
||||||
|
model.upgradingDevices!.removeWhere((device) {
|
||||||
|
final status = getUpgradeStatus(device.mac);
|
||||||
|
return status == null ||
|
||||||
|
['completed', 'failed', 'cancelled'].contains(status['status']);
|
||||||
|
});
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新设备进度(供 MultiDeviceFirmwareUpdater 回调使用)
|
||||||
|
void updateDeviceProgress(String mac, int progress, String status) {
|
||||||
|
final device = model.upgradingDevices!.firstWhere((d) => d.mac == mac);
|
||||||
|
|
||||||
|
if (device != null) {
|
||||||
|
device.process = progress;
|
||||||
|
|
||||||
|
// 如果升级完成或失败,更新相关状态
|
||||||
|
if (status == 'completed' && progress == 100) {
|
||||||
|
device.newVersion = int.parse(selectVerison?.version ?? '0');
|
||||||
|
// 可以在这里添加其他完成后的逻辑
|
||||||
|
} else if (status == 'failed') {
|
||||||
|
device.process = -1;
|
||||||
|
}
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'device_upgrade_controller.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
MHTDeviceUpgradeModel _$MHTDeviceUpgradeModelFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
MHTDeviceUpgradeModel()
|
||||||
|
..bluetooth = json['bluetooth'] as bool?
|
||||||
|
..singal = (json['singal'] as num?)?.toDouble();
|
||||||
|
|
||||||
|
Map<String, dynamic> _$MHTDeviceUpgradeModelToJson(
|
||||||
|
MHTDeviceUpgradeModel instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'bluetooth': instance.bluetooth,
|
||||||
|
'singal': instance.singal,
|
||||||
|
};
|
||||||
568
lib/pages/mh_page/device/upgrade/device_upgrade_list.dart
Normal file
568
lib/pages/mh_page/device/upgrade/device_upgrade_list.dart
Normal file
@@ -0,0 +1,568 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_blue_plus/flutter_blue_plus.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/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/tool/TopSlideNotification.dart';
|
||||||
|
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||||||
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
|
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/component/UpgradeDevice.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';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart';
|
||||||
|
|
||||||
|
class DeviceUpgradeList extends StatefulWidget {
|
||||||
|
DeviceUpgradeList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DeviceUpgradeList> createState() => _DeviceUpgradeState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeviceUpgradeState extends State<DeviceUpgradeList> {
|
||||||
|
MHTBlueToothController mhtBlueToothController = Get.find();
|
||||||
|
GlobalController globalController = Get.find();
|
||||||
|
UserInfoController userInfoController = Get.find();
|
||||||
|
ThemeController themeController = Get.find();
|
||||||
|
late FlutterBluePlus flutterBlue;
|
||||||
|
List<ScanResult> scanResults = [];
|
||||||
|
bool isScanning = false;
|
||||||
|
Timer? _timer;
|
||||||
|
bool _isDialogShowing = false;
|
||||||
|
|
||||||
|
var currentConnectedDeviceProp;
|
||||||
|
var connectDeviceCurrent = null;
|
||||||
|
List bleDevice = [];
|
||||||
|
String currentMsg = "寻找设备中...";
|
||||||
|
Timer? connectTimer;
|
||||||
|
bool isFind = false;
|
||||||
|
StreamSubscription<List<ScanResult>>? _scanSubscription;
|
||||||
|
var formFieldController = FormFieldController<String>(null);
|
||||||
|
MHTDeviceUpgradeController mhtDeviceUpgradeController = Get.find();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
mhtBlueToothController.allSelect.value = false;
|
||||||
|
mhtBlueToothController.model.blueRawData = [];
|
||||||
|
mhtBlueToothController.model.deviceDataStatus = [];
|
||||||
|
flutterBlue = FlutterBluePlus();
|
||||||
|
_checkBluetoothPermission();
|
||||||
|
mhtBlueToothController.search.value = "";
|
||||||
|
mhtBlueToothController.currentDeviceMac?.value = "";
|
||||||
|
BluetoothHelper.listenBluetoothState((isOn) {
|
||||||
|
mhtBlueToothController.model.bluetooth = isOn;
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
|
if (!isOn && !_isDialogShowing) {
|
||||||
|
_isDialogShowing = true;
|
||||||
|
mhtBlueToothController.model.blueRawData = [];
|
||||||
|
mhtBlueToothController.model.deviceDataStatus = [];
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
|
_showBluetoothNotEnabledDialog().then((_) {
|
||||||
|
_isDialogShowing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
_initFirmwareVersions();
|
||||||
|
}
|
||||||
|
|
||||||
|
//扫描设备
|
||||||
|
Future<void> _checkBluetoothPermission() async {
|
||||||
|
PermissionStatus bluetoothStatus = await Permission.bluetooth.status;
|
||||||
|
PermissionStatus locationStatus = await Permission.location.status;
|
||||||
|
|
||||||
|
if (bluetoothStatus.isGranted && locationStatus.isGranted) {
|
||||||
|
_startScanning();
|
||||||
|
_startPeriodicScan();
|
||||||
|
} else {
|
||||||
|
_requestBluetoothPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _requestBluetoothPermission() async {
|
||||||
|
Map<Permission, PermissionStatus> statuses = {};
|
||||||
|
bool dialogShown = false; // 标记是否弹过权限提示弹窗
|
||||||
|
if (Platform.isIOS) {
|
||||||
|
PermissionStatus isBleGranted = await Permission.bluetooth.request();
|
||||||
|
print('checkBlePermissions-ios, isBleGranted=$isBleGranted');
|
||||||
|
if (isBleGranted.isGranted) {
|
||||||
|
// startBluetoothScanning();
|
||||||
|
_startScanning();
|
||||||
|
_startPeriodicScan();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
_startScanning();
|
||||||
|
_startPeriodicScan();
|
||||||
|
} 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();
|
||||||
|
_startPeriodicScan();
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startScanning() async {
|
||||||
|
try {
|
||||||
|
if (!mounted || isScanning || !mhtBlueToothController.shouldScan.value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_scanSubscription?.cancel();
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
|
|
||||||
|
if (!mhtBlueToothController.model.bluetooth!) return;
|
||||||
|
|
||||||
|
if (!isScanning) {
|
||||||
|
setState(() {
|
||||||
|
isScanning = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
||||||
|
|
||||||
|
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
final signalThreshold = mhtBlueToothController.model.singal!;
|
||||||
|
final searchKey =
|
||||||
|
mhtBlueToothController.search.value.trim().toLowerCase();
|
||||||
|
|
||||||
|
final filteredResults = results.map((r) {
|
||||||
|
Map<String, dynamic> 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<int, List<int>> m_d = r.advertisementData.manufacturerData;
|
||||||
|
String? deviceId;
|
||||||
|
|
||||||
|
m_d.forEach((key, value) {
|
||||||
|
if (value == null || value.isEmpty) return;
|
||||||
|
|
||||||
|
if (key == 65517) {
|
||||||
|
List<int> a = [0, 0, ...value];
|
||||||
|
advertisDataFormatter(a, d); // 你原来的处理
|
||||||
|
if (d['adData']?['deviceId'] != null)
|
||||||
|
deviceId = d['adData']['deviceId'];
|
||||||
|
} else if (key == 11125 && value.length == 8) {
|
||||||
|
deviceId = ab2str(value.sublist(2, 8)).toUpperCase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
d['id'] = deviceId ?? r.device.remoteId.str; // fallback UUID
|
||||||
|
return BlueToothDataModel.fromScanResult(
|
||||||
|
r,
|
||||||
|
1,
|
||||||
|
bind: false,
|
||||||
|
name: d['name'],
|
||||||
|
mac: d['id'],
|
||||||
|
version: d['adData']?['version'],
|
||||||
|
rssi: d['rssi'],
|
||||||
|
selected: mhtBlueToothController.allSelect.value,
|
||||||
|
);
|
||||||
|
}).where((d) {
|
||||||
|
// 信号强度过滤(用 d.scanResult.rssi 或 d.rssi 都可以)
|
||||||
|
if (d.scanResult.rssi <= signalThreshold) return false;
|
||||||
|
|
||||||
|
// 搜索关键字过滤
|
||||||
|
if (searchKey.isNotEmpty) {
|
||||||
|
String name = d.name.toLowerCase();
|
||||||
|
String mac = d.mac.toLowerCase();
|
||||||
|
if (!name.contains(searchKey) && !mac.contains(searchKey))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 名称过滤规则,必须包含 deviceType['reg'] 列表中的某个字符串
|
||||||
|
List<String> regList = [];
|
||||||
|
if (regList.isNotEmpty) {
|
||||||
|
String lowerName = d.name.toLowerCase();
|
||||||
|
bool match =
|
||||||
|
regList.any((reg) => lowerName.contains(reg.toLowerCase()));
|
||||||
|
if (!match) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修正:不要使用外部的 r,改从 d.scanResult 读取广告数据
|
||||||
|
final adv = d.scanResult.advertisementData;
|
||||||
|
final isTarget = (d.rssi ?? d.scanResult.rssi) > signalThreshold &&
|
||||||
|
(adv.localName == "AITH-V2" || adv.localName == "SLAVE") &&
|
||||||
|
adv.manufacturerData.containsKey(0xFFED);
|
||||||
|
|
||||||
|
if (!isTarget) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
final currentDevices = mhtBlueToothController.model.blueRawData ?? [];
|
||||||
|
final newDevices = <BlueToothDataModel>[];
|
||||||
|
|
||||||
|
// for (var newDevice in filteredResults) {
|
||||||
|
// final existingIndex =
|
||||||
|
// currentDevices.indexWhere((d) => d.mac == newDevice.mac);
|
||||||
|
// if (existingIndex >= 0) {
|
||||||
|
// currentDevices[existingIndex] = newDevice;
|
||||||
|
// } else {
|
||||||
|
// newDevices.add(newDevice);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (var newDevice in filteredResults) {
|
||||||
|
final existingIndex =
|
||||||
|
currentDevices.indexWhere((d) => d.mac == newDevice.mac);
|
||||||
|
|
||||||
|
if (existingIndex >= 0) {
|
||||||
|
// 如果设备已存在,更新信号强度rssi
|
||||||
|
currentDevices[existingIndex].rssi = newDevice.rssi;
|
||||||
|
} else {
|
||||||
|
// 如果设备不存在,添加到新设备列表
|
||||||
|
newDevices.add(newDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
mhtBlueToothController.model.blueRawData = [
|
||||||
|
...currentDevices,
|
||||||
|
...newDevices
|
||||||
|
];
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await Future.delayed(Duration(seconds: 10));
|
||||||
|
await FlutterBluePlus.stopScan();
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
isScanning = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
ef.log("$e");
|
||||||
|
} finally {
|
||||||
|
setState(() {
|
||||||
|
isScanning = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startPeriodicScan() {
|
||||||
|
_timer = Timer.periodic(Duration(seconds: 3), (timer) {
|
||||||
|
if (mhtBlueToothController.shouldScan.value && !isScanning) {
|
||||||
|
_removeOldDevices(); // 先清理老旧设备
|
||||||
|
_startScanning();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopScanning() {
|
||||||
|
if (isScanning) {
|
||||||
|
FlutterBluePlus.stopScan();
|
||||||
|
_scanSubscription?.cancel();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
isScanning = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopPeriodicScan() {
|
||||||
|
_timer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_stopPeriodicScan();
|
||||||
|
_stopScanning();
|
||||||
|
_scanSubscription?.cancel();
|
||||||
|
connectTimer?.cancel();
|
||||||
|
mhtBlueToothController.stopStatusPolling();
|
||||||
|
mhtBlueToothController.model.blueRawData = [];
|
||||||
|
mhtBlueToothController.model.deviceDataStatus = [];
|
||||||
|
BluetoothHelper.cancelListener();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTargetDevice(String? name, List<String> keywords) {
|
||||||
|
if (name == null) return false;
|
||||||
|
return keywords.any((k) => name.contains(k));
|
||||||
|
}
|
||||||
|
|
||||||
|
_showBluetoothNotEnabledDialog() async {
|
||||||
|
await showTipDialog(
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, boxConstraints) => GestureDetector(
|
||||||
|
// onTap: () => FocusScope.of(context).unfocus(),,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
image: DecorationImage(
|
||||||
|
image: AssetImage('assets/images/new_background.png'),
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Scaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
appBar: AppBar(
|
||||||
|
iconTheme: IconThemeData(color: themeController.currentColor.sc3),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
titleSpacing: 0,
|
||||||
|
title: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 180.rpx,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'升级列表'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Readex Pro',
|
||||||
|
color: themeController.currentColor.sc3,
|
||||||
|
letterSpacing: 0,
|
||||||
|
fontSize: 30.rpx,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
child: returnIconButtom,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [],
|
||||||
|
centerTitle: false,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
top: true,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Obx(() {
|
||||||
|
if (mhtDeviceUpgradeController.model
|
||||||
|
.upgradingDevices!.isNotEmpty) {
|
||||||
|
final sortedList = mhtDeviceUpgradeController.model
|
||||||
|
.upgradingDevices!
|
||||||
|
.toList()
|
||||||
|
..sort((a, b) => b.rssi!.compareTo(a.rssi!));
|
||||||
|
// if (sortedList.length > 0) {
|
||||||
|
// ef.log("[rssi变化]--》${sortedList[0].rssi}");
|
||||||
|
// }
|
||||||
|
return Expanded(
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
...sortedList
|
||||||
|
.map((device) {
|
||||||
|
return UpgradeDevice(
|
||||||
|
bleDevice: device,
|
||||||
|
deviceType: 1,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.toList()
|
||||||
|
.divide(SizedBox(height: 30.rpx))
|
||||||
|
.addToEnd(SizedBox(height: 30.rpx)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
|
}),
|
||||||
|
].divide(SizedBox(
|
||||||
|
height: 30.rpx,
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
].divide(SizedBox(height: 30.rpx)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _removeOldDevices() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final currentDevices = mhtBlueToothController.model.blueRawData ?? [];
|
||||||
|
|
||||||
|
// 移除30秒内未出现的设备
|
||||||
|
final updatedDevices = currentDevices.where((device) {
|
||||||
|
return now.difference(device.lastSeen) < Duration(seconds: 30);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (updatedDevices.length != currentDevices.length) {
|
||||||
|
setState(() {
|
||||||
|
mhtBlueToothController.model.blueRawData = updatedDevices;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDeviceId(ScanResult result) {
|
||||||
|
// Android:remoteId 就是 MAC
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return result.device.remoteId.str.replaceAll(':', '').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS:尝试从 manufacturerData 里解析
|
||||||
|
Map<int, List<int>> mData = result.advertisementData.manufacturerData;
|
||||||
|
for (var key in mData.keys) {
|
||||||
|
List<int>? bytes = mData[key];
|
||||||
|
if (bytes != null && bytes.length == 6) {
|
||||||
|
// 假设这 6 个字节就是 MAC
|
||||||
|
return bytes
|
||||||
|
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||||
|
.join('')
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.device.remoteId.str.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initFirmwareVersions() async {
|
||||||
|
try {
|
||||||
|
const baseUrl = "https://share.file.he-info.cn";
|
||||||
|
const firmwarePath = "/ota/aithv2/";
|
||||||
|
|
||||||
|
final versions = await FirmwareVersionService.getAllFirmwareVersions(
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
firmwarePath: firmwarePath,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (versions.isNotEmpty) {
|
||||||
|
// 这里你可以存到控制器里
|
||||||
|
mhtBlueToothController.firmwareList = versions;
|
||||||
|
print("✅ 获取到 ${versions.length} 个固件版本:");
|
||||||
|
for (final v in versions) {
|
||||||
|
print("➡ ${v.fileName} (${v.version})");
|
||||||
|
}
|
||||||
|
if (mhtDeviceUpgradeController.selectVerison == null) {
|
||||||
|
mhtDeviceUpgradeController.selectVerison = versions.first;
|
||||||
|
formFieldController.value =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.version!;
|
||||||
|
mhtBlueToothController.updateAll();
|
||||||
|
} else {
|
||||||
|
formFieldController.value =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.version!;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("⚠ 没有获取到固件版本文件");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("❌ 初始化获取固件版本失败: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
328
lib/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart
Normal file
328
lib/pages/mh_page/device/upgrade/tool/device_upgrade_tool.dart
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||||
|
import 'package:vbvs_app/common/color/appConstants.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/tool/SelectableTagButton.dart';
|
||||||
|
import 'package:vbvs_app/enum/APPDeviceUpgrade.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/model/BlueToothDataModel.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_controller.dart';
|
||||||
|
|
||||||
|
MHTDeviceUpgradeController mhtDeviceUpgradeController = Get.find();
|
||||||
|
|
||||||
|
Future<void> initDeviceUpgradeType() async {
|
||||||
|
try {
|
||||||
|
// 模拟异步加载
|
||||||
|
// await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
final dataList = APPDeviceUpgradeExtension.list;
|
||||||
|
|
||||||
|
ef.log("✅ 初始化固件升级类型成功,共 ${dataList.length} 项");
|
||||||
|
for (var item in dataList) {
|
||||||
|
ef.log(" ${item['id']} - ${item['name']}");
|
||||||
|
}
|
||||||
|
// 你可以在这里存入 controller 变量中,例如:
|
||||||
|
mhtDeviceUpgradeController.diseaseList.value = dataList;
|
||||||
|
} catch (e) {
|
||||||
|
ef.log("❌ 初始化获取固件版本失败: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initFirmwareVersions(formFieldController) async {
|
||||||
|
try {
|
||||||
|
const baseUrl = "https://share.file.he-info.cn";
|
||||||
|
const firmwarePath = "/ota/aithv2/";
|
||||||
|
|
||||||
|
final versions = await FirmwareVersionService.getAllFirmwareVersions(
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
firmwarePath: firmwarePath,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (versions.isNotEmpty) {
|
||||||
|
// 这里你可以存到控制器里
|
||||||
|
mhtDeviceUpgradeController.firmwareList = versions;
|
||||||
|
print("✅ 获取到 ${versions.length} 个固件版本:");
|
||||||
|
for (final v in versions) {
|
||||||
|
print("➡ ${v.fileName} (${v.version})");
|
||||||
|
}
|
||||||
|
if (mhtDeviceUpgradeController.selectVerison == null) {
|
||||||
|
mhtDeviceUpgradeController.selectVerison = versions.first;
|
||||||
|
formFieldController.value =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.version!;
|
||||||
|
|
||||||
|
final MHTBlueToothController _btController = Get.find();
|
||||||
|
_btController.currentUpgradeVersion =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.version!;
|
||||||
|
_btController.currentUpgradeName =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.fileName!;
|
||||||
|
_btController.currentUpgradeUrl =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.downloadUrl!;
|
||||||
|
|
||||||
|
mhtDeviceUpgradeController.updateAll();
|
||||||
|
} else {
|
||||||
|
formFieldController.value =
|
||||||
|
mhtDeviceUpgradeController.selectVerison!.version!;
|
||||||
|
}
|
||||||
|
mhtDeviceUpgradeController.updateAll();
|
||||||
|
} else {
|
||||||
|
print("⚠ 没有获取到固件版本文件");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("❌ 初始化获取固件版本失败: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getQueryList() {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.all(AppConstants().content_left_right_padding),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"筛选条件".tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: AppConstants().title_text_fontSize,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(() {
|
||||||
|
final selectedIds = mhtDeviceUpgradeController.selectedDiseaseIds;
|
||||||
|
final diseases = mhtDeviceUpgradeController.diseaseList;
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 40.rpx, 0.rpx, 0),
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 20.rpx,
|
||||||
|
runSpacing: 20.rpx,
|
||||||
|
children: diseases.map<Widget>((disease) {
|
||||||
|
final id = disease['id'];
|
||||||
|
final name = disease['name'];
|
||||||
|
final isSelected = selectedIds!.contains(id);
|
||||||
|
return SelectableTagButton(
|
||||||
|
selectedTextColor: Colors.black,
|
||||||
|
unselectedTextColor: Colors.white,
|
||||||
|
colors: [stringToColor("#84F5FF")],
|
||||||
|
borderRadius: AppConstants().button_container_radius,
|
||||||
|
label: name,
|
||||||
|
selected: isSelected,
|
||||||
|
onTap: () {
|
||||||
|
final allId = APPDeviceUpgrade.ALL.value; // 全部的id(一般是1)
|
||||||
|
|
||||||
|
if (id == allId) {
|
||||||
|
// 👉 点击的是“全部”
|
||||||
|
if (isSelected) {
|
||||||
|
// 当前是选中状态 → 取消“全部”选择(清空所有)
|
||||||
|
selectedIds.clear();
|
||||||
|
} else {
|
||||||
|
// 当前未选中 → 全部选中(添加所有状态的id)
|
||||||
|
selectedIds
|
||||||
|
..clear()
|
||||||
|
..addAll(APPDeviceUpgradeExtension.valueList);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 👉 点击的不是“全部”
|
||||||
|
if (isSelected) {
|
||||||
|
// 已选中 → 取消当前id
|
||||||
|
selectedIds.remove(id);
|
||||||
|
|
||||||
|
// 若取消后,“全部”还在选中 → 去掉“全部”
|
||||||
|
if (selectedIds.contains(allId)) {
|
||||||
|
selectedIds.remove(allId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 未选中 → 选中当前id
|
||||||
|
selectedIds.add(id);
|
||||||
|
|
||||||
|
// 若选中的数量 == 除“全部”外的所有类型数 → 自动勾选“全部”
|
||||||
|
final allOtherIds = APPDeviceUpgradeExtension
|
||||||
|
.valueList
|
||||||
|
.where((v) => v != allId);
|
||||||
|
if (selectedIds.toSet().containsAll(allOtherIds)) {
|
||||||
|
selectedIds
|
||||||
|
..clear()
|
||||||
|
..addAll(APPDeviceUpgradeExtension.valueList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mhtDeviceUpgradeController.updateAll();
|
||||||
|
});
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// int getRemainingDeviceCount() {
|
||||||
|
// // 获取所有设备数据
|
||||||
|
// var allDevices = mhtDeviceUpgradeController.model.blueRawData!;
|
||||||
|
|
||||||
|
// // 获取正在升级的设备数据
|
||||||
|
// var upgradingDevices = mhtDeviceUpgradeController.model.upgradingDevices;
|
||||||
|
|
||||||
|
// // 获取正在升级的设备 mac 地址列表
|
||||||
|
// var upgradingMacs = upgradingDevices!.map((device) => device.mac).toList();
|
||||||
|
|
||||||
|
// // 计算所有设备中不在升级设备列表中的设备数量
|
||||||
|
// var remainingDevices = allDevices
|
||||||
|
// .where((device) => !upgradingMacs.contains(device.mac))
|
||||||
|
// .toList();
|
||||||
|
|
||||||
|
// // 返回剩余设备的数量,最小为 0
|
||||||
|
|
||||||
|
// mhtDeviceUpgradeController.selectVerison;
|
||||||
|
// return remainingDevices.length;
|
||||||
|
// }
|
||||||
|
int getRemainingDeviceCount() {
|
||||||
|
// 获取所有设备数据
|
||||||
|
var allDevices = mhtDeviceUpgradeController.model.blueRawData ?? [];
|
||||||
|
|
||||||
|
// 获取正在升级的设备数据
|
||||||
|
var upgradingDevices =
|
||||||
|
mhtDeviceUpgradeController.model.upgradingDevices ?? [];
|
||||||
|
|
||||||
|
// 获取当前选择的升级版本
|
||||||
|
var selectedVersion = mhtDeviceUpgradeController.selectVerison;
|
||||||
|
|
||||||
|
// 获取正在升级的设备 mac 地址列表
|
||||||
|
var upgradingMacs = upgradingDevices.map((device) => device.mac).toList();
|
||||||
|
|
||||||
|
// 过滤出:
|
||||||
|
// 1. 不在正在升级的设备列表中;
|
||||||
|
// 2. 且版本号不等于当前选中的版本(表示无需升级的排除掉)
|
||||||
|
var remainingDevices = allDevices.where((device) {
|
||||||
|
// bool notUpgrading = !upgradingMacs.contains(device.mac);
|
||||||
|
bool notUpgrading = true;
|
||||||
|
//todo wyf 恢复
|
||||||
|
// bool needUpgrade = device.version.toString() != selectedVersion!.version;
|
||||||
|
bool needUpgrade = true;
|
||||||
|
return notUpgrading && needUpgrade;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
// 返回剩余需要升级的设备数量
|
||||||
|
|
||||||
|
List<BlueToothDataModel> displayDevices = [
|
||||||
|
// 用 upgrading 覆盖 raw 中的相同 mac
|
||||||
|
...allDevices.map((d) {
|
||||||
|
final u = upgradingDevices.firstWhereOrNull((x) => x.mac == d.mac);
|
||||||
|
return u ?? d;
|
||||||
|
}),
|
||||||
|
// 加入那些只在 upgradingDevices 中但不在 raw 的
|
||||||
|
...upgradingDevices.where((u) => !allDevices.any((d) => d.mac == u.mac)),
|
||||||
|
];
|
||||||
|
return displayDevices.length;
|
||||||
|
return remainingDevices.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
String judgeUpgradeDeviceCount() {
|
||||||
|
List selectedDevices = mhtDeviceUpgradeController.model.blueRawData!
|
||||||
|
.where((device) => device.selected == true)
|
||||||
|
.toList();
|
||||||
|
if (selectedDevices.length == 0) {
|
||||||
|
return "请至少选择一项设备";
|
||||||
|
}
|
||||||
|
if (selectedDevices.length > mhtDeviceUpgradeController.maxLimit) {
|
||||||
|
return "超出数量限制,请等待";
|
||||||
|
}
|
||||||
|
final devices = mhtDeviceUpgradeController.model.upgradingDevices;
|
||||||
|
if (devices == null || devices.isEmpty) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
if (devices.length >= mhtDeviceUpgradeController.maxLimit) {
|
||||||
|
return "超出数量限制,请等待";
|
||||||
|
}
|
||||||
|
if (devices.length + selectedDevices.length >
|
||||||
|
mhtDeviceUpgradeController.maxLimit) {
|
||||||
|
return "超出数量限制,请等待";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDeviceId(ScanResult result) {
|
||||||
|
// Android:remoteId 就是 MAC
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return result.device.remoteId.str.replaceAll(':', '').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// iOS:尝试从 manufacturerData 里解析
|
||||||
|
Map<int, List<int>> mData = result.advertisementData.manufacturerData;
|
||||||
|
for (var key in mData.keys) {
|
||||||
|
List<int>? bytes = mData[key];
|
||||||
|
if (bytes != null && bytes.length == 6) {
|
||||||
|
// 假设这 6 个字节就是 MAC
|
||||||
|
return bytes
|
||||||
|
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||||
|
.join('')
|
||||||
|
.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.device.remoteId.str.toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeOldDevices() {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final currentDevices = mhtDeviceUpgradeController.model.blueRawData! ?? [];
|
||||||
|
|
||||||
|
// 移除30秒内未出现的设备
|
||||||
|
final updatedDevices = currentDevices.where((device) {
|
||||||
|
return now.difference(device.lastSeen) < Duration(seconds: 30);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
if (updatedDevices.length != currentDevices.length) {
|
||||||
|
mhtDeviceUpgradeController.model.blueRawData = updatedDevices;
|
||||||
|
mhtDeviceUpgradeController.updateAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showBluetoothNotEnabledDialog(BuildContext context) async {
|
||||||
|
await showTipDialog(
|
||||||
|
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"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<BlueToothDataModel> filterDeviceType(List<BlueToothDataModel> devices) {
|
||||||
|
final selectedIds = mhtDeviceUpgradeController.selectedDiseaseIds;
|
||||||
|
|
||||||
|
// 如果未选择或包含“全部”,直接返回全部设备
|
||||||
|
if (selectedIds!.isEmpty ||
|
||||||
|
selectedIds.contains(APPDeviceUpgrade.ALL.value)) {
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则按类型过滤
|
||||||
|
return devices
|
||||||
|
.where((device) => selectedIds.contains(device.upgradeStatus))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
@@ -236,6 +236,10 @@ class _NewHomePageState extends State<NewHomePage> {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
const Spacer(), // 左右分隔
|
const Spacer(), // 左右分隔
|
||||||
|
if (userInfoController.model.login != null &&
|
||||||
|
userInfoController.model.login != 0 &&
|
||||||
|
userInfoController.model.user!.phone != null &&
|
||||||
|
userInfoController.model.user!.phone != "17649984946")
|
||||||
FloatingSvgIcon(
|
FloatingSvgIcon(
|
||||||
assetPath: 'assets/img/icon/xiaoyi.svg',
|
assetPath: 'assets/img/icon/xiaoyi.svg',
|
||||||
width: 60.rpx,
|
width: 60.rpx,
|
||||||
@@ -563,6 +567,7 @@ class _NewHomePageState extends State<NewHomePage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (formFieldController
|
if (formFieldController
|
||||||
|
|||||||
@@ -490,6 +490,65 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (userInfoController.model.user != null &&
|
||||||
|
userInfoController.model.user!.fac !=
|
||||||
|
null &&
|
||||||
|
userInfoController.model.user!.fac ==
|
||||||
|
true)
|
||||||
|
ClickableContainer(
|
||||||
|
backgroundColor:
|
||||||
|
Colors.transparent, // 容器背景色
|
||||||
|
highlightColor: themeController
|
||||||
|
.currentColor.sc21, // 点击时的背景色
|
||||||
|
padding: EdgeInsetsDirectional.fromSTEB(
|
||||||
|
0.rpx, 0.rpx, 0.rpx, 0.rpx),
|
||||||
|
onTap: () {
|
||||||
|
// Get.toNamed("/privacyPolicyPage");
|
||||||
|
Get.toNamed("/deviceMaintain");
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsetsDirectional.fromSTEB(
|
||||||
|
40.rpx,
|
||||||
|
30.rpx,
|
||||||
|
40.rpx,
|
||||||
|
30.rpx),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'设备维护'.tr,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: AppConstants()
|
||||||
|
.title_text_fontSize,
|
||||||
|
letterSpacing: 0.0,
|
||||||
|
// height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
].divide(
|
||||||
|
SizedBox(width: 22.rpx)),
|
||||||
|
),
|
||||||
|
SvgPicture.asset(
|
||||||
|
'assets/img/icon/arrow_right.svg',
|
||||||
|
width: 8.rpx,
|
||||||
|
height: 14
|
||||||
|
.rpx, // 如果 SVG 中没有固定颜色,可以这样设置
|
||||||
|
color: themeController
|
||||||
|
.currentColor.sc3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.divide(SizedBox(height: 0.rpx))
|
.divide(SizedBox(height: 0.rpx))
|
||||||
.addToStart(SizedBox(height: 30.rpx))
|
.addToStart(SizedBox(height: 30.rpx))
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import 'package:vbvs_app/pages/mh_page/apply_repair_page.dart';
|
|||||||
import 'package:vbvs_app/pages/mh_page/book_info_page.dart';
|
import 'package:vbvs_app/pages/mh_page/book_info_page.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/book_success_page.dart';
|
import 'package:vbvs_app/pages/mh_page/book_success_page.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/delete_account.dart';
|
import 'package:vbvs_app/pages/mh_page/delete_account.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/device_maintain.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/upgrade/device_upgrade_list.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/mht_bind_device_success.dart';
|
import 'package:vbvs_app/pages/mh_page/device/mht_bind_device_success.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/mht_bind_device_type.dart';
|
import 'package:vbvs_app/pages/mh_page/device/mht_bind_device_type.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/mht_blueteeth_device_page.dart';
|
import 'package:vbvs_app/pages/mh_page/device/mht_blueteeth_device_page.dart';
|
||||||
@@ -140,6 +143,9 @@ var mhroutes = {
|
|||||||
"/privacyPolicyPageNew": (contxt, {arguments}) =>
|
"/privacyPolicyPageNew": (contxt, {arguments}) =>
|
||||||
PrivacyPolicyNewPage(sleepUri: arguments),
|
PrivacyPolicyNewPage(sleepUri: arguments),
|
||||||
"/commonMessageSettingPage": (contxt) => MHTCommonMessageSettingPage(),
|
"/commonMessageSettingPage": (contxt) => MHTCommonMessageSettingPage(),
|
||||||
|
"/deviceMaintain": (contxt) => DeviceMaintain(),
|
||||||
|
"/deviceUpgrade": (contxt) => DeviceUpgrade(),
|
||||||
|
"/deviceUpgradeList": (contxt) => DeviceUpgradeList(),
|
||||||
};
|
};
|
||||||
var mhonGenerateRoute = (RouteSettings settings) {
|
var mhonGenerateRoute = (RouteSettings settings) {
|
||||||
final String? name = settings.name; // 获取路由名称,如 /news 或 /search
|
final String? name = settings.name; // 获取路由名称,如 /news 或 /search
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ environment:
|
|||||||
sdk: ^3.5.4
|
sdk: ^3.5.4
|
||||||
|
|
||||||
fluwx:
|
fluwx:
|
||||||
app_id: 'wx929c548fea6af9c7' #填写自己的 WeChat app id.眠花糖
|
# app_id: 'wx929c548fea6af9c7' #填写自己的 WeChat app id.眠花糖
|
||||||
# app_id: 'wxeb2688220799e2c5' #填写自己的 app id.太和e护
|
# app_id: 'wxeb2688220799e2c5' #填写自己的 app id.太和e护
|
||||||
debug_logging: false # Logging in debug mode.
|
debug_logging: false # Logging in debug mode.
|
||||||
android:
|
android:
|
||||||
|
|||||||
Reference in New Issue
Block a user