Browse Source

修复不更新电量设备信息

newdev_shunjiawei
fdp 1 year ago
parent
commit
dd2f871836
  1. 142
      lib/data/services/ble_manager.dart
  2. 156
      lib/modules/home/controllers/home_controller.dart
  3. 182
      lib/modules/home/views/home_view.dart
  4. 170
      lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart
  5. 0
      lib/modules/ota_upgrade/model/ota_upgrade_model.dart
  6. 200
      lib/modules/ota_upgrade/views/ota_upgrade_view.dart
  7. 60
      local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt
  8. 10
      local_plugins/ble_service/ios/Classes/BleService.swift
  9. 8
      local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift
  10. 11
      local_plugins/ble_service/lib/ble_service.dart

142
lib/data/services/ble_manager.dart

@ -9,13 +9,15 @@ import 'package:deep_voice/data/models/appconfig_model.dart';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/services.dart' show rootBundle;
import 'package:agent_service/agent_service.dart'; import 'package:agent_service/agent_service.dart';
import 'package:classic_bluetooth/classic_bluetooth.dart';
/// 命令类型常量(与原生层保持一致) /// 命令类型常量(与原生层保持一致)
class BleCommands { class BleCommands {
static const int GET_VERSION = 0x01; static const int GET_VERSION = 0x01;
static const int GET_BATTERY_INFO = 0x02; static const int GET_PRODUCT_ID = 0x02;
static const int CONTROL_CODEC = 0x03; static const int GET_PRODUCT_COLOR = 0x03;
static const int PLAY_A2DP = 0x04; static const int GET_BATTERY_INFO = 0x04;
static const int CONTROL_CODEC = 0x05;
} }
/// 蓝牙设备信息缓存模型 /// 蓝牙设备信息缓存模型
@ -37,6 +39,8 @@ class DeviceInfo {
final bool caseCharging; final bool caseCharging;
final String firmwareVersion; final String firmwareVersion;
final String firmwareProductId;
final String firmwareProductColor;
final DateTime lastUpdated; final DateTime lastUpdated;
DeviceInfo({ DeviceInfo({
@ -50,6 +54,8 @@ class DeviceInfo {
this.rightEarCharging = false, this.rightEarCharging = false,
this.caseCharging = false, this.caseCharging = false,
this.firmwareVersion = '', this.firmwareVersion = '',
this.firmwareProductId = '',
this.firmwareProductColor = '',
DateTime? lastUpdated, DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now(); }) : lastUpdated = lastUpdated ?? DateTime.now();
@ -65,6 +71,8 @@ class DeviceInfo {
bool? rightEarCharging, bool? rightEarCharging,
bool? caseCharging, bool? caseCharging,
String? firmwareVersion, String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
}) { }) {
return DeviceInfo( return DeviceInfo(
address: address ?? this.address, address: address ?? this.address,
@ -77,6 +85,8 @@ class DeviceInfo {
rightEarCharging: rightEarCharging ?? this.rightEarCharging, rightEarCharging: rightEarCharging ?? this.rightEarCharging,
caseCharging: caseCharging ?? this.caseCharging, caseCharging: caseCharging ?? this.caseCharging,
firmwareVersion: firmwareVersion ?? this.firmwareVersion, firmwareVersion: firmwareVersion ?? this.firmwareVersion,
firmwareProductId: firmwareProductId ?? this.firmwareProductId,
firmwareProductColor: firmwareProductColor ?? this.firmwareProductColor,
lastUpdated: DateTime.now(), lastUpdated: DateTime.now(),
); );
} }
@ -96,6 +106,7 @@ class BleManager extends GetxService {
// BLE服务实例 // BLE服务实例
final BleService _bleService = BleService(); final BleService _bleService = BleService();
// 经典蓝牙实例
// BLE连接状态 // BLE连接状态
final _connectionState = BleConnectionState.disconnected.obs; final _connectionState = BleConnectionState.disconnected.obs;
@ -106,8 +117,7 @@ class BleManager extends GetxService {
bool get isConnected => _isConnected.value; bool get isConnected => _isConnected.value;
// 设备信息缓存 // 设备信息缓存
final Rx<DeviceInfo?> _deviceInfo = Rx<DeviceInfo?>(null); final deviceInfo = Rx<DeviceInfo?>(null); // 使用 Rx 包装
DeviceInfo? get deviceInfo => _deviceInfo.value;
// 订阅 // 订阅
StreamSubscription<dynamic>? _statusSubscription; StreamSubscription<dynamic>? _statusSubscription;
@ -145,8 +155,8 @@ class BleManager extends GetxService {
if (event is Map) { if (event is Map) {
// 处理连接状态更新 // 处理连接状态更新
if (event.containsKey('connectionState')) { if (event['type'] == 'connectionStateChanged') {
final state = event['connectionState'] as int; final state = event['state'] as int;
_connectionState.value = state; _connectionState.value = state;
// 连接状态转换 // 连接状态转换
@ -154,8 +164,13 @@ class BleManager extends GetxService {
Logger.info('BLE设备已连接'); Logger.info('BLE设备已连接');
_isConnected.value = true; _isConnected.value = true;
// 在连接成功后获取设备信息 // 在连接成功后延迟 300 毫秒获取设备信息(可根据需要调整时间)
_fetchDeviceInfo(); Future.delayed(const Duration(milliseconds: 300), () {
// 可选:检查是否仍处于连接状态
if (_isConnected.value) {
fetchDeviceInfo();
}
});
} else if (state == BleConnectionState.disconnected || } else if (state == BleConnectionState.disconnected ||
state == BleConnectionState.error) { state == BleConnectionState.error) {
Logger.info('BLE设备已断开或发生错误'); Logger.info('BLE设备已断开或发生错误');
@ -218,12 +233,18 @@ class BleManager extends GetxService {
void _processDeviceInfoByCommandId( void _processDeviceInfoByCommandId(
int commandId, Map<dynamic, dynamic> data) { int commandId, Map<dynamic, dynamic> data) {
switch (commandId) { switch (commandId) {
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
case BleCommands.GET_VERSION: case BleCommands.GET_VERSION:
_updateVersionInfoFromData(data); _updateVersionInfoFromData(data);
break; break;
case BleCommands.GET_PRODUCT_ID:
_updateProductIdFromData(data);
break;
case BleCommands.GET_PRODUCT_COLOR:
break;
case BleCommands.GET_BATTERY_INFO:
_updateBatteryInfoFromData(data);
break;
default: default:
Logger.info('未处理的命令ID: $commandId'); Logger.info('未处理的命令ID: $commandId');
} }
@ -239,6 +260,11 @@ class BleManager extends GetxService {
case 'versionInfo': case 'versionInfo':
_updateVersionInfoFromData(data); _updateVersionInfoFromData(data);
break; break;
case 'productId':
_updateProductIdFromData(data);
break;
case 'productColor':
break;
default: default:
Logger.info('未处理的命令名称: $commandName'); Logger.info('未处理的命令名称: $commandName');
} }
@ -307,6 +333,29 @@ class BleManager extends GetxService {
} }
} }
/// 更新设备id
void _updateProductIdFromData(Map<dynamic, dynamic> data) {
try {
// 确保数据中包含版本信息
if (!data.containsKey('productId')) {
Logger.warning('版本信息数据不完整');
return;
}
// 提取版本信息
final String productId = data['productId'] as String? ?? '';
// 更新设备信息
_updateDeviceInfoIfNeeded(
firmwareProductId: productId,
);
Logger.info('设备设备id已更新: 设备id=$productId');
} catch (e) {
Logger.error('更新设备id失败: $e');
}
}
/// 更新设备信息(如果已存在设备) /// 更新设备信息(如果已存在设备)
void _updateDeviceInfoIfNeeded({ void _updateDeviceInfoIfNeeded({
int? batteryLevel, int? batteryLevel,
@ -317,31 +366,53 @@ class BleManager extends GetxService {
bool? rightEarCharging, bool? rightEarCharging,
bool? caseCharging, bool? caseCharging,
String? firmwareVersion, String? firmwareVersion,
String? firmwareProductId,
String? firmwareProductColor,
}) { }) {
// 如果设备信息已存在,则更新 // 如果设备信息已存在,则更新
if (_deviceInfo.value != null) { if (deviceInfo.value != null) {
_deviceInfo.value = _deviceInfo.value!.copyWith( deviceInfo.value = deviceInfo.value!.copyWith(
batteryLevel: batteryLevel, batteryLevel: batteryLevel ?? deviceInfo.value?.batteryLevel,
leftEarBattery: leftEarBattery, leftEarBattery: leftEarBattery ?? deviceInfo.value?.leftEarBattery,
rightEarBattery: rightEarBattery, rightEarBattery: rightEarBattery ?? deviceInfo.value?.rightEarBattery,
caseBattery: caseBattery, caseBattery: caseBattery ?? deviceInfo.value?.caseBattery,
leftEarCharging: leftEarCharging, leftEarCharging: leftEarCharging ?? deviceInfo.value!.leftEarCharging,
rightEarCharging: rightEarCharging, rightEarCharging:
caseCharging: caseCharging, rightEarCharging ?? deviceInfo.value!.rightEarCharging,
firmwareVersion: firmwareVersion, caseCharging: caseCharging ?? deviceInfo.value!.caseCharging,
firmwareVersion: firmwareVersion ?? deviceInfo.value!.firmwareVersion,
firmwareProductId:
firmwareProductId ?? deviceInfo.value!.firmwareProductId,
firmwareProductColor:
firmwareProductColor ?? deviceInfo.value!.firmwareProductColor,
); );
} }
// 创建设备信息
// 如果设备信息不存在,但有地址和名称,则创建新的设备信息 // 如果设备信息不存在,但有地址和名称,则创建新的设备信息
else if (_deviceInfo.value == null && else if (deviceInfo.value == null &&
_isConnected.value && _isConnected.value &&
(firmwareVersion != null || batteryLevel != null)) { (firmwareVersion != null || batteryLevel != null)) {
// 提示尝试创建设备信息但缺少必要信息 // 提示尝试创建设备信息但缺少必要信息
Logger.warning('尝试更新设备信息,但设备基本信息不存在'); Logger.warning('尝试更新设备信息,但设备基本信息不存在');
deviceInfo.value = DeviceInfo(
address: "", // 应确保从连接信息中获取真实值
name: "",
batteryLevel: batteryLevel ?? 0,
leftEarBattery: leftEarBattery ?? 0,
rightEarBattery: rightEarBattery ?? 0,
caseBattery: caseBattery ?? 0,
leftEarCharging: leftEarCharging ?? false,
rightEarCharging: rightEarCharging ?? false,
caseCharging: caseCharging ?? false,
firmwareVersion: firmwareVersion ?? '',
firmwareProductId: firmwareProductId ?? '',
firmwareProductColor: firmwareProductColor ?? '',
);
} }
} }
/// 连接成功后获取设备信息 /// 连接成功后获取设备信息
Future<void> _fetchDeviceInfo() async { Future<void> fetchDeviceInfo() async {
Logger.info('开始获取BLE设备详细信息'); Logger.info('开始获取BLE设备详细信息');
try { try {
@ -360,6 +431,14 @@ class BleManager extends GetxService {
} else { } else {
Logger.warning('版本信息获取指令发送失败'); Logger.warning('版本信息获取指令发送失败');
} }
// 获取产品id
final productIdResult = await _bleService.getProductId();
if (productIdResult) {
Logger.info('产品id获取指令已发送');
} else {
Logger.warning('产品id获取指令发送失败');
}
} catch (e) { } catch (e) {
Logger.error('获取BLE设备信息失败: $e'); Logger.error('获取BLE设备信息失败: $e');
} }
@ -450,9 +529,9 @@ class BleManager extends GetxService {
_connectionState.value = BleConnectionState.connected; _connectionState.value = BleConnectionState.connected;
// 如果设备信息存在,则获取详细信息 // 如果设备信息存在,则获取详细信息
if (_deviceInfo.value != null) { if (deviceInfo.value != null) {
// 获取详细设备信息 // 获取详细设备信息
_fetchDeviceInfo(); fetchDeviceInfo();
} }
} }
} else { } else {
@ -486,7 +565,6 @@ class BleManager extends GetxService {
} }
} }
/// 获取电池电量 /// 获取电池电量
Future<int> getBatteryLevel() async { Future<int> getBatteryLevel() async {
try { try {
@ -507,6 +585,16 @@ class BleManager extends GetxService {
} }
} }
/// 主动产品id
Future<bool> getProductId() async {
try {
return await _bleService.getProductId();
} catch (e) {
Logger.error('获取设备版本失败: $e');
return false;
}
}
/// 主动获取设备版本信息 /// 主动获取设备版本信息
Future<bool> fetchDeviceVersion() async { Future<bool> fetchDeviceVersion() async {
try { try {

156
lib/modules/home/controllers/home_controller.dart

@ -19,7 +19,7 @@ enum ConnectionStatus {
/// 主页控制器,管理设备连接状态和信息展示 /// 主页控制器,管理设备连接状态和信息展示
class HomeController extends GetxController { class HomeController extends GetxController {
static HomeController get to => Get.find(); static HomeController get to => Get.find();
// 依赖的控制器 // 依赖的控制器
final ExploreController exploreController = Get.find<ExploreController>(); final ExploreController exploreController = Get.find<ExploreController>();
@ -27,31 +27,47 @@ class HomeController extends GetxController {
final _connectionStatus = ConnectionStatus.disconnected.obs; final _connectionStatus = ConnectionStatus.disconnected.obs;
final _connectionErrorMsg = ''.obs; final _connectionErrorMsg = ''.obs;
final _bluetoothState = 'unknown'.obs; final _bluetoothState = 'unknown'.obs;
// Getter方法 // Getter方法
ConnectionStatus get connectionStatus => _connectionStatus.value; ConnectionStatus get connectionStatus => _connectionStatus.value;
String get connectionErrorMsg => _connectionErrorMsg.value; String get connectionErrorMsg => _connectionErrorMsg.value;
String get bluetoothState => _bluetoothState.value; String get bluetoothState => _bluetoothState.value;
BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice; BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice;
DeviceInfo? get deviceInfo => _bleManager.deviceInfo; Rx<DeviceInfo?> get deviceInfo => _bleManager.deviceInfo;
bool get isConnected => currentDevice != null; bool get isConnected => currentDevice != null;
// 设备详细信息的getter,从deviceInfo获取数据 // 设备详细信息的getter,从deviceInfo获取数据
int get leftEarBattery => deviceInfo?.leftEarBattery ?? 0; final leftEarBattery = 0.obs;
int get rightEarBattery => deviceInfo?.rightEarBattery ?? 0; final rightEarBattery = 0.obs;
int get caseBattery => deviceInfo?.caseBattery ?? 0; final caseBattery = 0.obs;
bool get leftEarCharging => deviceInfo?.leftEarCharging ?? false; final leftEarCharging = false.obs;
bool get rightEarCharging => deviceInfo?.rightEarCharging ?? false; final rightEarCharging = false.obs;
bool get caseCharging => deviceInfo?.caseCharging ?? false; final caseCharging = false.obs;
String get firmwareVersion => deviceInfo?.firmwareVersion ?? ''; final firmwareVersion = ''.obs;
// int get leftEarBattery => deviceInfo?.leftEarBattery ?? 0;
// int get rightEarBattery => deviceInfo?.rightEarBattery ?? 0;
// int get caseBattery => deviceInfo?.caseBattery ?? 0;
// bool get leftEarCharging => deviceInfo?.leftEarCharging ?? false;
// bool get rightEarCharging => deviceInfo?.rightEarCharging ?? false;
// bool get caseCharging => deviceInfo?.caseCharging ?? false;
// String get firmwareVersion => deviceInfo?.firmwareVersion ?? '';
/// 当有新的deviceInfo时更新所有Observable变量
void updateDeviceInfo(DeviceInfo? newDeviceInfo) {
leftEarBattery.value = newDeviceInfo?.leftEarBattery ?? 0;
rightEarBattery.value = newDeviceInfo?.rightEarBattery ?? 0;
caseBattery.value = newDeviceInfo?.caseBattery ?? 0;
leftEarCharging.value = newDeviceInfo?.leftEarCharging ?? false;
rightEarCharging.value = newDeviceInfo?.rightEarCharging ?? false;
caseCharging.value = newDeviceInfo?.caseCharging ?? false;
firmwareVersion.value = newDeviceInfo?.firmwareVersion ?? '';
}
// 监听和订阅 // 监听和订阅
Worker? _deviceInfoWorker; Worker? _deviceInfoWorker;
Worker? _bluetoothStateWorker; Worker? _bluetoothStateWorker;
Worker? _deviceWorker; Worker? _deviceWorker;
// 蓝牙服务 // 蓝牙服务
final _bluetoothManager = Get.find<BluetoothManager>(); final _bluetoothManager = Get.find<BluetoothManager>();
final _bleManager = Get.find<BleManager>(); final _bleManager = Get.find<BleManager>();
@ -60,7 +76,7 @@ class HomeController extends GetxController {
super.onInit(); super.onInit();
_initWithCachedInfo(); _initWithCachedInfo();
} }
@override @override
void onClose() { void onClose() {
_deviceInfoWorker?.dispose(); _deviceInfoWorker?.dispose();
@ -73,11 +89,12 @@ class HomeController extends GetxController {
void _initWithCachedInfo() { void _initWithCachedInfo() {
// 使用蓝牙服务中的当前状态 // 使用蓝牙服务中的当前状态
_bluetoothState.value = _bluetoothManager.bluetoothState; _bluetoothState.value = _bluetoothManager.bluetoothState;
// 根据当前设备状态设置连接状态 // 根据当前设备状态设置连接状态
if (currentDevice != null) { if (currentDevice != null) {
_connectionStatus.value = ConnectionStatus.connected; _connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = ''; _connectionErrorMsg.value = '';
updateDeviceInfo(_bleManager.deviceInfo.value);
Logger.info('主页加载缓存设备信息: ${currentDevice!.name}'); Logger.info('主页加载缓存设备信息: ${currentDevice!.name}');
} else { } else {
_connectionStatus.value = ConnectionStatus.disconnected; _connectionStatus.value = ConnectionStatus.disconnected;
@ -87,67 +104,73 @@ class HomeController extends GetxController {
_connectionErrorMsg.value = '未连接到设备'; _connectionErrorMsg.value = '未连接到设备';
} }
} }
// 设置监听器监听蓝牙服务状态变化 // 设置监听器监听蓝牙服务状态变化
_setupStateListeners(); _setupStateListeners();
} }
/// 设置状态监听器 /// 设置状态监听器
void _setupStateListeners() { void _setupStateListeners() {
// 设备信息监听 - 设备信息更新时自动触发UI更新 // 设备信息监听 - 设备信息更新时自动触发UI更新
_deviceInfoWorker = ever(_bleManager.deviceInfo.obs, (DeviceInfo? info) { _deviceInfoWorker = ever(_bleManager.deviceInfo, (DeviceInfo? info) {
Logger.info('设备信息监听');
if (info != null) { if (info != null) {
Logger.info('主页更新设备信息: ${info.toString()}'); Logger.info('主页更新设备信息: ${info.toString()}');
updateDeviceInfo(info);
} else { } else {
Logger.info('主页清空设备信息'); Logger.info('主页清空设备信息');
} }
update(); update();
}); });
// 蓝牙状态监听 // 蓝牙状态监听
_bluetoothStateWorker = ever(_bluetoothManager.bluetoothState.obs, (String state) { _bluetoothStateWorker =
ever(_bluetoothManager.bluetoothState.obs, (String state) {
_bluetoothState.value = state; _bluetoothState.value = state;
// 如果蓝牙关闭,更新错误信息 // 如果蓝牙关闭,更新错误信息
if (state == 'off') { if (state == 'off') {
_connectionErrorMsg.value = '蓝牙未开启'; _connectionErrorMsg.value = '蓝牙未开启';
_connectionStatus.value = ConnectionStatus.disconnected; _connectionStatus.value = ConnectionStatus.disconnected;
} }
update(); update();
}); });
// 设备连接状态监听 - 直接监听设备变化 // 设备连接状态监听 - 直接监听设备变化
_deviceWorker = ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) { _deviceWorker =
ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) {
if (device != null) { if (device != null) {
// 设备已连接 // 设备已连接
if (_connectionStatus.value != ConnectionStatus.connected) { if (_connectionStatus.value != ConnectionStatus.connected) {
_connectionStatus.value = ConnectionStatus.connected; _connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = ''; _connectionErrorMsg.value = '';
Logger.info('主页检测到设备已连接: ${device.name}'); Logger.info('主页检测到设备已连接: ${device.name}');
} }
} else if (_connectionStatus.value == ConnectionStatus.connected) { } else if (_connectionStatus.value == ConnectionStatus.connected) {
// 设备已断开 // 设备已断开
_connectionStatus.value = ConnectionStatus.disconnected; _connectionStatus.value = ConnectionStatus.disconnected;
_connectionErrorMsg.value = '设备已断开连接'; _connectionErrorMsg.value = '设备已断开连接';
Logger.info('主页检测到设备已断开连接'); Logger.info('主页检测到设备已断开连接');
} }
update(); update();
}); });
} }
/// 刷新设备连接状态 /// 刷新设备连接状态
/// 注意:此方法不会主动刷新设备,只更新UI状态 /// 注意:此方法不会主动刷新设备,只更新UI状态
Future<void> refreshDeviceConnection() async { Future<void> refreshDeviceConnection() async {
// 更新当前状态以反映UI变化 // 更新当前状态以反映UI变化
bool hasDevice = currentDevice != null; bool hasDevice = currentDevice != null;
if (hasDevice) { if (hasDevice) {
_connectionStatus.value = ConnectionStatus.connected; _connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = ''; _connectionErrorMsg.value = '';
await _bleManager.fetchDeviceInfo();
Logger.info('使用缓存设备信息: ${currentDevice!.name}'); Logger.info('使用缓存设备信息: ${currentDevice!.name}');
} else { } else {
if (_bluetoothState.value == 'off') { if (_bluetoothState.value == 'off') {
@ -157,13 +180,13 @@ class HomeController extends GetxController {
} }
_connectionStatus.value = ConnectionStatus.disconnected; _connectionStatus.value = ConnectionStatus.disconnected;
} }
// 刷新UI // 刷新UI
update(); update();
} }
// 导航和功能方法 // 导航和功能方法
/// 重新扫描设备 /// 重新扫描设备
void rescanDevices() { void rescanDevices() {
Get.toNamed(Routes.pairing); Get.toNamed(Routes.pairing);
@ -185,7 +208,7 @@ class HomeController extends GetxController {
} }
// UI辅助方法 // UI辅助方法
/// 获取连接状态对应的颜色 /// 获取连接状态对应的颜色
Color getConnectionStatusColor() { Color getConnectionStatusColor() {
switch (_connectionStatus.value) { switch (_connectionStatus.value) {
@ -244,62 +267,71 @@ class HomeController extends GetxController {
/// 获取设备ID /// 获取设备ID
String getDeviceId() { String getDeviceId() {
return deviceInfo?.address ?? currentDevice?.address ?? 'unknown'.tr; return deviceInfo.value?.address ?? currentDevice?.address ?? 'unknown'.tr;
} }
/// 获取设备电量文本 /// 获取设备电量文本
String getBatteryText() { String getBatteryText() {
return deviceInfo?.batteryLevel != null && deviceInfo!.batteryLevel > 0 return deviceInfo.value?.batteryLevel != null &&
? '${deviceInfo!.batteryLevel}%' deviceInfo.value!.batteryLevel > 0
: 'unknown'.tr; ? '${deviceInfo.value?.batteryLevel}%'
: 'unknown'.tr;
} }
/// 获取耳机左右电量文本 /// 获取耳机左右电量文本
String getEarbudsText() { String getEarbudsText() {
if (deviceInfo == null || (deviceInfo!.leftEarBattery <= 0 && deviceInfo!.rightEarBattery <= 0)) { if (deviceInfo == null ||
(deviceInfo.value!.leftEarBattery <= 0 &&
deviceInfo.value!.rightEarBattery <= 0)) {
return 'unknown'.tr; return 'unknown'.tr;
} }
String leftStatus = deviceInfo!.leftEarBattery > 0 ? '${deviceInfo!.leftEarBattery}%' : 'N/A'; String leftStatus = deviceInfo.value!.leftEarBattery > 0
String rightStatus = deviceInfo!.rightEarBattery > 0 ? '${deviceInfo!.rightEarBattery}%' : 'N/A'; ? '${deviceInfo.value!.leftEarBattery}%'
: 'N/A';
leftStatus = deviceInfo!.leftEarCharging ? '$leftStatus ⚡' : leftStatus; String rightStatus = deviceInfo.value!.rightEarBattery > 0
rightStatus = deviceInfo!.rightEarCharging ? '$rightStatus ⚡' : rightStatus; ? '${deviceInfo.value!.rightEarBattery}%'
: 'N/A';
leftStatus =
deviceInfo.value!.leftEarCharging ? '$leftStatus ⚡' : leftStatus;
rightStatus =
deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus;
return '左耳: $leftStatus / 右耳: $rightStatus'; return '左耳: $leftStatus / 右耳: $rightStatus';
} }
/// 获取充电仓电量文本 /// 获取充电仓电量文本
String getCaseText() { String getCaseText() {
if (deviceInfo == null || deviceInfo!.caseBattery <= 0) { if (deviceInfo.value == null || deviceInfo.value!.caseBattery <= 0) {
return 'unknown'.tr; return 'unknown'.tr;
} }
String caseStatus = '${deviceInfo!.caseBattery}%'; String caseStatus = '${deviceInfo.value!.caseBattery}%';
if (deviceInfo!.caseCharging) { if (deviceInfo.value!.caseCharging) {
caseStatus = '$caseStatus ⚡'; caseStatus = '$caseStatus ⚡';
} }
return '充电仓: $caseStatus'; return '充电仓: $caseStatus';
} }
/// 获取充电状态文本 /// 获取充电状态文本
String getChargingText() { String getChargingText() {
if (deviceInfo == null) { if (deviceInfo == null) {
return 'unknown'.tr; return 'unknown'.tr;
} }
bool isCharging = deviceInfo!.leftEarCharging || bool isCharging = deviceInfo.value!.leftEarCharging ||
deviceInfo!.rightEarCharging || deviceInfo.value!.rightEarCharging ||
deviceInfo!.caseCharging; deviceInfo.value!.caseCharging;
return isCharging ? 'charging'.tr : 'notCharging'.tr; return isCharging ? 'charging'.tr : 'notCharging'.tr;
} }
/// 获取设备版本文本 /// 获取设备版本文本
String getVersionText() { String getVersionText() {
return deviceInfo?.firmwareVersion.isNotEmpty == true ? return deviceInfo.value?.firmwareVersion.isNotEmpty == true
deviceInfo!.firmwareVersion : 'unknown'.tr; ? deviceInfo.value!.firmwareVersion
: 'unknown'.tr;
} }
} }

182
lib/modules/home/views/home_view.dart

@ -160,43 +160,43 @@ class HomeView extends GetView<HomeController> {
child: ClipRRect( child: ClipRRect(
// 添加 ClipRRect 来实现圆角 // 添加 ClipRRect 来实现圆角
borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小 borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
height: 200.h, height: 200.h,
child: Image.asset( child: Image.asset(
isDarkMode isDarkMode
? 'assets/images/robot_light.png' ? 'assets/images/robot_light.png'
: 'assets/images/robot_light.png', : 'assets/images/robot_light.png',
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
),
), ),
), ),
), ),
),
Center( Center(
child: ClipRRect( child: ClipRRect(
// 添加 ClipRRect // 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
child: Image.asset( child: Image.asset(
isDarkMode isDarkMode
? 'assets/images/headphone_dark.png' ? 'assets/images/headphone_dark.png'
: 'assets/images/headphone_light.png', : 'assets/images/headphone_light.png',
height: 180.h, height: 180.h,
fit: BoxFit.contain, fit: BoxFit.contain,
),
), ),
), ),
),
Center( Center(
child: ClipRRect( child: ClipRRect(
// 添加 ClipRRect // 添加 ClipRRect
borderRadius: BorderRadius.circular(16.r), borderRadius: BorderRadius.circular(16.r),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
height: 200.h, height: 200.h,
child: Image.asset( child: Image.asset(
isDarkMode isDarkMode
? 'assets/images/glasses_light.png' ? 'assets/images/glasses_light.png'
: 'assets/images/glasses_light.png', : 'assets/images/glasses_light.png',
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
), ),
), ),
), ),
@ -283,50 +283,52 @@ class HomeView extends GetView<HomeController> {
], ],
), ),
Obx(() => Row( Obx(() => Row(
children: [ children: [
Container( Container(
width: 8.w, width: 8.w,
height: 8.w, height: 8.w,
decoration: BoxDecoration( decoration: BoxDecoration(
color: controller.getConnectionStatusColor(), color: controller.getConnectionStatusColor(),
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: controller.getConnectionStatusColor().withOpacity(0.3), color: controller
blurRadius: 4, .getConnectionStatusColor()
spreadRadius: 1, .withOpacity(0.3),
blurRadius: 4,
spreadRadius: 1,
),
],
), ),
],
),
),
SizedBox(width: 4.w),
Text(
controller.getConnectionStatusText(),
style: TextStyle(
fontSize: 12.sp,
color: controller.getConnectionStatusColor(),
),
),
// 添加刷新按钮
SizedBox(width: 8.w),
InkWell(
onTap: () => controller.refreshDeviceConnection(),
borderRadius: BorderRadius.circular(12.r),
child: Container(
padding: EdgeInsets.all(4.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
shape: BoxShape.circle,
), ),
child: Icon( SizedBox(width: 4.w),
Icons.refresh, Text(
size: 14.sp, controller.getConnectionStatusText(),
color: isDarkMode ? Colors.grey[400] : Colors.grey[600], style: TextStyle(
fontSize: 12.sp,
color: controller.getConnectionStatusColor(),
),
), ),
), // 添加刷新按钮
), SizedBox(width: 8.w),
], InkWell(
)), onTap: () => controller.refreshDeviceConnection(),
borderRadius: BorderRadius.circular(12.r),
child: Container(
padding: EdgeInsets.all(4.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
shape: BoxShape.circle,
),
child: Icon(
Icons.refresh,
size: 14.sp,
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
),
),
),
],
)),
], ],
); );
} }
@ -338,9 +340,8 @@ class HomeView extends GetView<HomeController> {
Container( Container(
padding: EdgeInsets.all(12.w), padding: EdgeInsets.all(12.w),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color:
? Colors.red[900]!.withOpacity(0.2) isDarkMode ? Colors.red[900]!.withOpacity(0.2) : Colors.red[50],
: Colors.red[50],
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
border: Border.all( border: Border.all(
color: isDarkMode ? Colors.red[800]! : Colors.red[200]!), color: isDarkMode ? Colors.red[800]! : Colors.red[200]!),
@ -440,12 +441,12 @@ class HomeView extends GetView<HomeController> {
final leftBattery = controller.leftEarBattery; final leftBattery = controller.leftEarBattery;
final rightBattery = controller.rightEarBattery; final rightBattery = controller.rightEarBattery;
final caseBattery = controller.caseBattery; final caseBattery = controller.caseBattery;
// 获取充电状态 // 获取充电状态
final leftEarCharging = controller.leftEarCharging; final leftEarCharging = controller.leftEarCharging;
final rightEarCharging = controller.rightEarCharging; final rightEarCharging = controller.rightEarCharging;
final caseCharging = controller.caseCharging; final caseCharging = controller.caseCharging;
return GridView.count( return GridView.count(
crossAxisCount: 3, crossAxisCount: 3,
shrinkWrap: true, shrinkWrap: true,
@ -457,33 +458,33 @@ class HomeView extends GetView<HomeController> {
_buildBatteryGridItem( _buildBatteryGridItem(
icon: Icons.headphones, icon: Icons.headphones,
label: '左耳机', label: '左耳机',
batteryLevel: leftBattery, batteryLevel: leftBattery.value,
isCharging: leftEarCharging, isCharging: leftEarCharging.value,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
// 右耳机 // 右耳机
_buildBatteryGridItem( _buildBatteryGridItem(
icon: Icons.headphones, icon: Icons.headphones,
label: '右耳机', label: '右耳机',
batteryLevel: rightBattery, batteryLevel: rightBattery.value,
isCharging: rightEarCharging, isCharging: rightEarCharging.value,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
// 充电盒 // 充电盒
_buildBatteryGridItem( _buildBatteryGridItem(
icon: Icons.charging_station, icon: Icons.charging_station,
label: '充电盒', label: '充电盒',
batteryLevel: caseBattery, batteryLevel: caseBattery.value,
isCharging: caseCharging, isCharging: caseCharging.value,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
], ],
); );
}); });
} }
// 单个电池网格项 // 单个电池网格项
Widget _buildBatteryGridItem({ Widget _buildBatteryGridItem({
required IconData icon, required IconData icon,
@ -495,7 +496,7 @@ class HomeView extends GetView<HomeController> {
// 获取电池图标和颜色 // 获取电池图标和颜色
IconData batteryIcon; IconData batteryIcon;
Color batteryColor; Color batteryColor;
if (batteryLevel > 75) { if (batteryLevel > 75) {
batteryIcon = Icons.battery_full; batteryIcon = Icons.battery_full;
batteryColor = Colors.green[600]!; batteryColor = Colors.green[600]!;
@ -512,16 +513,16 @@ class HomeView extends GetView<HomeController> {
batteryIcon = Icons.battery_alert; batteryIcon = Icons.battery_alert;
batteryColor = Colors.red[600]!; batteryColor = Colors.red[600]!;
} }
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100], color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r), borderRadius: BorderRadius.circular(12.r),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: isDarkMode ? color: isDarkMode
Colors.black.withOpacity(0.1) : ? Colors.black.withOpacity(0.1)
Colors.grey.withOpacity(0.1), : Colors.grey.withOpacity(0.1),
blurRadius: 4, blurRadius: 4,
spreadRadius: 0, spreadRadius: 0,
offset: Offset(0, 2), offset: Offset(0, 2),
@ -545,7 +546,7 @@ class HomeView extends GetView<HomeController> {
), ),
), ),
SizedBox(height: 6.h), SizedBox(height: 6.h),
// 标签 // 标签
Text( Text(
label, label,
@ -557,7 +558,7 @@ class HomeView extends GetView<HomeController> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 6.h), SizedBox(height: 6.h),
// 电池指示器 // 电池指示器
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -614,16 +615,15 @@ class HomeView extends GetView<HomeController> {
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
SizedBox(height: 8.h), SizedBox(height: 8.h),
// 固件版本 // 固件版本
_buildInfoRow( _buildInfoRow(
icon: Icons.info_outline, icon: Icons.info_outline,
label: 'firmwareVersion'.tr, label: 'firmwareVersion'.tr,
value: controller.firmwareVersion, value: controller.firmwareVersion.value,
isDarkMode: isDarkMode, isDarkMode: isDarkMode,
), ),
// 连接状态标签 // 连接状态标签
SizedBox(height: 10.h), SizedBox(height: 10.h),
Container( Container(
@ -763,7 +763,7 @@ class HomeView extends GetView<HomeController> {
), ),
); );
} }
// 热门AI体验标题 // 热门AI体验标题
Widget _buildSectionTitle(bool isDarkMode) { Widget _buildSectionTitle(bool isDarkMode) {
return Row( return Row(

170
lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart

@ -1,19 +1,17 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:path/path.dart';
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'package:get_storage/get_storage.dart';
import '../../../data/services/network/api.dart'; import '../../../data/services/network/api.dart';
import '../ota.dart'; import '../model/ota_upgrade_model.dart';
import 'dart:io'; import 'dart:io';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:json_annotation/json_annotation.dart'; import '../../../data/services/ble_manager.dart';
class OtaUpgradeController extends GetxController { class OtaUpgradeController extends GetxController {
final RxString errorMessage = ''.obs;
final RxBool isFetchingDeviceInfo = false.obs;
final Dio _dio = Dio(); final Dio _dio = Dio();
final RxString latestVersion = ''.obs; final RxString latestVersion = ''.obs;
final RxString currentVersion = ''.obs; final RxString currentVersion = ''.obs;
@ -24,41 +22,93 @@ class OtaUpgradeController extends GetxController {
final RxDouble progress = 0.0.obs; final RxDouble progress = 0.0.obs;
// 蓝牙服务
final _bleManager = Get.find<BleManager>();
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
_getCurrentVersion(); _getCurrentVersion();
} }
// 获取当前版本信息 // 添加重试方法
void retryConnection() {
errorMessage.value = '';
_getCurrentVersion();
}
// 添加更新方法
Future<void> startUpdate() async {
try {
isUpdating.value = true;
// 实现更新逻辑...
} catch (e) {
errorMessage.value = e.toString();
Get.snackbar('更新失败', e.toString());
} finally {
isUpdating.value = false;
}
}
Future<void> _getCurrentVersion() async { Future<void> _getCurrentVersion() async {
//发送ble获取固件版本 try {
var firmwareVersion = "1.1.0"; // 添加获取设备信息的重试机制
currentVersion.value = firmwareVersion; DeviceInfo? deviceInfo = _bleManager.deviceInfo.value;
//发送ble获取产品id
var id = "a2"; // 第一次检查设备信息
//发送api请求 if (deviceInfo == null) {
final response = await Api.getequipmentinfo({ // 显示加载提示
"id": id, isFetchingDeviceInfo.value = true;
});
if (response['info'] != '') { // 尝试重新获取设备信息
print('getLoginToken:进来了'); await _bleManager.fetchDeviceInfo();
// 根据API设计调整
final resp = OtaConfigResp.fromJson(response); // 等待后再次获取设备信息
deviceInfo = _bleManager.deviceInfo.value;
isFetchingDeviceInfo.value = false;
// 仍然为空则抛出异常
if (deviceInfo == null) {
throw const FormatException('无法获取设备信息,请检查设备连接');
}
}
// 检查固件信息有效性
if (deviceInfo.firmwareVersion.isEmpty
//||
// deviceInfo.firmwareProductId.isEmpty
) {
throw const FormatException('设备固件信息不完整');
}
// 正常流程
currentVersion.value = deviceInfo.firmwareVersion;
print('成功解析: ${resp.id}, ${resp.factory}'); // API请求
final response = await Api.getequipmentinfo({
"id": "a2",
});
// 处理API响应
if (response['info'] == null) {
throw const FormatException('API 响应格式错误');
}
final resp = OtaConfigResp.fromJson(response);
latestVersion.value = resp.systemversion!; latestVersion.value = resp.systemversion!;
updatepackageaddress = resp.updatepackageaddress!; updatepackageaddress = resp.updatepackageaddress!;
if (_isNewVersionAvailable(resp.systemversion!)) {
//需要升级 // 版本检查
print('需要更新'); isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!);
isUpdateAvailable.value = false; } catch (e) {
} else { // 统一错误处理
isUpdateAvailable.value = true; errorMessage.value = e.toString();
//已经是最新版本更新ui,按钮隐藏 Get.snackbar('错误', e.toString(),
} snackPosition: SnackPosition.BOTTOM,
} else { backgroundColor: Colors.red,
throw const FormatException('API 响应缺少 "info" 字段'); colorText: Colors.white);
// 重要:重新抛出异常以便上层处理(可选)
} finally {
isFetchingDeviceInfo.value = false;
} }
} }
@ -120,13 +170,10 @@ class OtaUpgradeController extends GetxController {
); );
} }
// 开始下载 // 修改后的startDownload方法
Future<void> startDownload() async { Future<void> startDownload() async {
isUpdating.value = true; isUpdating.value = true;
// 查找最后一个 '/' 的位置
int lastIndex = updatepackageaddress.lastIndexOf('/'); int lastIndex = updatepackageaddress.lastIndexOf('/');
// 截取最后一个 '/' 后面的字符串
String fileName = updatepackageaddress.substring(lastIndex + 1); String fileName = updatepackageaddress.substring(lastIndex + 1);
String directoryName = "ota_upgrades"; String directoryName = "ota_upgrades";
@ -145,14 +192,54 @@ class OtaUpgradeController extends GetxController {
throw FileSystemException('无法访问下载目录'); throw FileSystemException('无法访问下载目录');
} }
// 创建子目录:Download/APP
final targetDir = Directory('${downloadsDir.path}/$directoryName'); final targetDir = Directory('${downloadsDir.path}/$directoryName');
if (!await targetDir.exists()) { if (!await targetDir.exists()) {
await targetDir.create(recursive: true); await targetDir.create(recursive: true);
} }
final savePath = '${targetDir.path}/$fileName'; final savePath = '${targetDir.path}/$fileName';
print('文件保存路径: $savePath'); // 关键日志
// 新增:检查同名文件逻辑
bool existingSameFile = false;
try {
var files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
String name = basename(entity.path); // 使用path包中的basename
if (name == fileName) {
existingSameFile = true;
break;
}
}
}
} catch (e) {
Get.snackbar('错误', '检查本地文件失败: $e');
isUpdating.value = false;
return;
}
if (existingSameFile) {
Get.snackbar('文件已存在', '跳过下载,直接更新');
isUpdating.value = false;
_installApp(savePath);
return;
}
// 新增:删除其他文件逻辑
try {
var files = await targetDir.list().toList();
for (var entity in files) {
if (await FileSystemEntity.isFile(entity.path)) {
await entity.delete();
}
}
} catch (e) {
Get.snackbar('错误', '删除旧文件失败: $e');
isUpdating.value = false;
return;
}
print('文件保存路径: $savePath');
final options = Options( final options = Options(
receiveTimeout: Duration.zero, receiveTimeout: Duration.zero,
@ -181,22 +268,23 @@ class OtaUpgradeController extends GetxController {
}, },
); );
// 下载完成后验证文件存在性
File file = File(savePath); File file = File(savePath);
if (await file.exists()) { if (await file.exists()) {
Get.snackbar('下载完成', '文件已保存至: $savePath'); // 明确提示路径 Get.snackbar('下载完成', '文件已保存至: $savePath');
} else { } else {
Get.snackbar('错误', '文件未找到,请检查写入权限'); Get.snackbar('错误', '文件未找到,请检查写入权限');
} }
isUpdating.value = false; isUpdating.value = false;
//下载完成,开始升级
_installApp(savePath); _installApp(savePath);
} on DioException catch (e) { } on DioException catch (e) {
Get.snackbar('下载失败', '错误: ${e.message}'); Get.snackbar('下载失败', '错误: ${e.message}');
isUpdating.value = false;
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
Get.snackbar('存储失败', '无法写入文件: ${e.message}'); Get.snackbar('存储失败', '无法写入文件: ${e.message}');
isUpdating.value = false;
} catch (e) { } catch (e) {
Get.snackbar('未知错误', e.toString()); Get.snackbar('未知错误', e.toString());
isUpdating.value = false;
} }
} }

0
lib/modules/ota_upgrade/ota.dart → lib/modules/ota_upgrade/model/ota_upgrade_model.dart

200
lib/modules/ota_upgrade/views/ota_upgrade_view.dart

@ -4,106 +4,126 @@ import '../controllers/ota_upgrade_controller.dart';
class OtaUpgradeView extends GetView<OtaUpgradeController> { class OtaUpgradeView extends GetView<OtaUpgradeController> {
const OtaUpgradeView({Key? key}) : super(key: key); const OtaUpgradeView({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
body: Center( body: Center(
// 添加外层Center child: ConstrainedBox(
child: SingleChildScrollView( constraints: BoxConstraints(maxWidth: 600), // 限制最大宽度
// 添加滚动防止小屏溢出 child: SingleChildScrollView(
child: Padding( child: Padding(
padding: EdgeInsets.all(20), padding: EdgeInsets.all(20),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center, // 水平居中 children: [
mainAxisSize: MainAxisSize.min, // 紧凑布局 // 设备图标
children: [ _buildDeviceIcon(),
// 设备图标 SizedBox(height: 30),
CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(
Icons.phone_iphone,
size: 60,
color: Colors.blue,
),
),
SizedBox(height: 30),
// 版本信息 // 版本信息
Column( _buildVersionInfo(),
children: [ SizedBox(height: 40),
Text(
'当前版本',
style: TextStyle(
fontSize: 18,
color: Colors.grey,
),
),
SizedBox(height: 8),
Text(
'v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
],
),
SizedBox(height: 40),
// 更新按钮/进度条 // 状态显示区域
Obx(() { Obx(() {
if (controller.isUpdating.value) { if (controller.isFetchingDeviceInfo.value) {
return Column( return _buildLoadingIndicator();
children: [ }
SizedBox( if (controller.errorMessage.value.isNotEmpty) {
width: 200, // 限制进度条宽度 return _buildErrorInfo();
child: LinearProgressIndicator( }
value: controller.downloadProgress.value, return _buildUpdateButton();
backgroundColor: Colors.grey[200], }),
valueColor: ],
AlwaysStoppedAnimation<Color>(Colors.blue), ),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey),
)
],
);
} else {
return ElevatedButton(
onPressed: controller.isUpdateAvailable.value
? null
: controller.checkUpdate,
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.grey
: Colors.blue,
foregroundColor: Colors.white,
padding:
EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
child: Text(
controller.isUpdateAvailable.value ? '已是最新版本' : '立即更新',
style: TextStyle(fontSize: 16),
),
);
}
}),
],
), ),
), ),
), ),
), ),
); );
} }
// 组件化构建方法
Widget _buildDeviceIcon() => CircleAvatar(
radius: 60,
backgroundColor: Colors.blue[100],
child: Icon(Icons.phone_iphone, size: 60, color: Colors.blue),
);
Widget _buildVersionInfo() => Column(
children: [
Text('当前版本', style: TextStyle(fontSize: 18, color: Colors.grey)),
SizedBox(height: 8),
Text('v${controller.currentVersion}',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue)),
],
);
Widget _buildLoadingIndicator() => Column(
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text('正在连接设备...', style: TextStyle(color: Colors.grey))
],
);
Widget _buildErrorInfo() => Column(
children: [
Icon(Icons.error_outline, color: Colors.red, size: 40),
SizedBox(height: 10),
Text(controller.errorMessage.value,
style: TextStyle(color: Colors.red)),
SizedBox(height: 20),
ElevatedButton(
onPressed: controller.retryConnection,
child: Text('重试连接'),
)
],
);
Widget _buildUpdateButton() {
return controller.isUpdating.value
? _buildProgressIndicator()
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: controller.isUpdateAvailable.value
? Colors.blue
: Colors.grey,
padding: EdgeInsets.symmetric(horizontal: 40, vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
),
onPressed: controller.isUpdateAvailable.value
? controller.startUpdate
: null,
child: Text(
controller.isUpdateAvailable.value ? '立即更新' : '已是最新版本',
style: TextStyle(fontSize: 16, color: Colors.white),
),
);
}
Widget _buildProgressIndicator() => Column(
children: [
SizedBox(
width: 200,
child: LinearProgressIndicator(
value: controller.downloadProgress.value,
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation(Colors.blue),
minHeight: 12,
borderRadius: BorderRadius.circular(10),
),
),
SizedBox(height: 20),
Text(
'${(controller.downloadProgress.value * 100).toStringAsFixed(1)}%',
style: TextStyle(color: Colors.grey))
],
);
} }

60
local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt

@ -473,7 +473,7 @@ object BleService {
} }
override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) { override fun onCharacteristicChanged(g: BluetoothGatt, c: BluetoothGattCharacteristic) {
val data = c.value ?: return val data = c.value ?: return
// 根据特征UUID区分处理 // 根据特征UUID区分处理
@ -1017,9 +1017,11 @@ object BleService {
stopOpusStreamDecoding() // 停止Opus解码流 stopOpusStreamDecoding() // 停止Opus解码流
return sendCommand( return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), BleConst.CMD_CONTROL_CODEC.toByte(),
byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte(), byteArrayOf(
BleConst.CODEC_CONTROL_CLOSE.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()) // 或其他默认声道
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
) // 或其他默认声道
) )
} }
@ -1027,45 +1029,54 @@ object BleService {
* 控制编解码 - 打开解码 * 控制编解码 - 打开解码
*/ */
fun openDecoder(): Boolean { fun openDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...") FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding( val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头 hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道 channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高 sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM) packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60. // packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it. // The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
) )
FileLogger.i(TAG, "打开解码...") FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte(), return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.AUDIO_CHANNEL_RIGHT.toByte())) BleConst.CODEC_CONTROL_DECODE_ON.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
} }
/** /**
* 控制编解码 - 打开解码 * 控制编解码 - 打开解码
*/ */
fun openA2DPDecoder(): Boolean { fun openA2DPDecoder(): Boolean {
FileLogger.i(TAG, "打开编码 (设备侧)...") FileLogger.i(TAG, "打开编码 (设备侧)...")
val opusStarted = startOpusStreamDecoding( val opusStarted = startOpusStreamDecoding(
hasHeader = false, // BLE传输的Opus数据通常无文件头 hasHeader = false, // BLE传输的Opus数据通常无文件头
channel = 1, // 单声道 channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高 sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM) packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60. // packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it. // The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
) )
FileLogger.i(TAG, "打开解码...") FileLogger.i(TAG, "打开解码...")
return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(), return sendCommand(
BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(
BleConst.AUDIO_CHANNEL_RIGHT.toByte())) BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(),
BleConst.AUDIO_CHANNEL_RIGHT.toByte()
)
)
} }
/** /**
@ -1087,11 +1098,11 @@ object BleService {
channel = 1, // 单声道 channel = 1, // 单声道
sampleRate = 16000,// Opus推荐16kHz或更高 sampleRate = 16000,// Opus推荐16kHz或更高
packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM) packetSize = 40 // Opus常用帧长,如20ms, 40ms, 60ms。 (16000Hz * 1channel * 2bytes/sample * 0.04s = 1280 bytes/frame if PCM)
// packetSize for Opus is typically in ms, e.g. 20, 40, 60. // packetSize for Opus is typically in ms, e.g. 20, 40, 60.
// The library might expect this in a different unit or derive it. // The library might expect this in a different unit or derive it.
// Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms.
) )
// 注意:此方法通常是控制设备端开始编码并发送数据。 // 注意:此方法通常是控制设备端开始编码并发送数据。
// App侧通常是打开解码器(openDecoder)来接收和处理这些数据。 // App侧通常是打开解码器(openDecoder)来接收和处理这些数据。
@ -1178,7 +1189,6 @@ object BleService {
return bluetoothAdapter return bluetoothAdapter
} }
// ====================================================================================================== // ======================================================================================================
// 资源清理 // 资源清理

10
local_plugins/ble_service/ios/Classes/BleService.swift

@ -353,6 +353,11 @@ actor BleService {
func getDeviceVersion() -> Bool { func getDeviceVersion() -> Bool {
return sendCommand(BleConst.CMD_GET_VERSION) return sendCommand(BleConst.CMD_GET_VERSION)
} }
///获取产品ID
/// - Returns: 是否成功发送请求
func getProductId() -> Bool {
return sendCommand(BleConst.CMD_GET_PRODUCT_ID)
}
/// 获取电池信息 /// 获取电池信息
/// - Returns: 是否成功发送请求 /// - Returns: 是否成功发送请求
@ -371,6 +376,11 @@ actor BleService {
func openDecoder() -> Bool { func openDecoder() -> Bool {
return sendCodecControl(BleConst.CODEC_CONTROL_DECODE_ON) return sendCodecControl(BleConst.CODEC_CONTROL_DECODE_ON)
} }
/// 打开解码器
/// - Returns: 是否成功发送请求
func openA2DPDecoder() -> Bool {
return sendCodecControl(BleConst.CODEC_CONTROL_A2DP_PLAY)
}
/// 关闭编解码器 /// 关闭编解码器
/// - Returns: 是否成功发送请求 /// - Returns: 是否成功发送请求

8
local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift

@ -73,12 +73,18 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin, BleDelegate {
case "getDeviceVersion": case "getDeviceVersion":
result(bleService.getDeviceVersion()) result(bleService.getDeviceVersion())
case "getProductId":
result(bleService.getProductId())
case "openEncoder": case "openEncoder":
result(bleService.openEncoder()) result(bleService.openEncoder())
case "openDecoder": case "openDecoder":
result(bleService.openDecoder()) result(bleService.openDecoder())
case "openA2DPDecoder":
result(bleService.openA2DPDecoder())
case "closeCodec": case "closeCodec":
result(bleService.closeCodec()) result(bleService.closeCodec())

11
local_plugins/ble_service/lib/ble_service.dart

@ -83,6 +83,17 @@ class BleService {
} }
} }
/// 获取产品ID
Future<bool> getProductId() async {
try {
final result = await _methodChannel.invokeMethod<bool>('getProductId');
return result ?? false;
} catch (e) {
print('获取设备版本失败: $e');
return false;
}
}
/// 获取电池信息 /// 获取电池信息
Future<bool> getBatteryInfo() async { Future<bool> getBatteryInfo() async {
try { try {

Loading…
Cancel
Save