From dd434bc1464e0932a1a5b6b76ffe6dc33e203cc5 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Mon, 12 May 2025 00:51:45 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8D=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=94=B5=E9=87=8F=E8=AE=BE=E5=A4=87=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/ble_manager.dart | 142 ++++++++++--- .../home/controllers/home_controller.dart | 156 ++++++++------ lib/modules/home/views/home_view.dart | 182 ++++++++-------- .../controllers/ota_upgrade_controller.dart | 170 +++++++++++---- .../ota_upgrade_model.dart} | 0 .../ota_upgrade/views/ota_upgrade_view.dart | 200 ++++++++++-------- .../yunqiinnovation/ble_service/BleService.kt | 60 +++--- .../ble_service/ios/Classes/BleService.swift | 10 + .../ios/Classes/SwiftBleServicePlugin.swift | 8 +- .../ble_service/lib/ble_service.dart | 11 + 10 files changed, 602 insertions(+), 337 deletions(-) rename lib/modules/ota_upgrade/{ota.dart => model/ota_upgrade_model.dart} (100%) diff --git a/lib/data/services/ble_manager.dart b/lib/data/services/ble_manager.dart index cc72b46c6..0f826a7c4 100644 --- a/lib/data/services/ble_manager.dart +++ b/lib/data/services/ble_manager.dart @@ -9,13 +9,15 @@ import 'package:deep_voice/data/models/appconfig_model.dart'; import 'dart:convert'; import 'package:flutter/services.dart' show rootBundle; import 'package:agent_service/agent_service.dart'; +import 'package:classic_bluetooth/classic_bluetooth.dart'; /// 命令类型常量(与原生层保持一致) class BleCommands { static const int GET_VERSION = 0x01; - static const int GET_BATTERY_INFO = 0x02; - static const int CONTROL_CODEC = 0x03; - static const int PLAY_A2DP = 0x04; + static const int GET_PRODUCT_ID = 0x02; + static const int GET_PRODUCT_COLOR = 0x03; + static const int GET_BATTERY_INFO = 0x04; + static const int CONTROL_CODEC = 0x05; } /// 蓝牙设备信息缓存模型 @@ -37,6 +39,8 @@ class DeviceInfo { final bool caseCharging; final String firmwareVersion; + final String firmwareProductId; + final String firmwareProductColor; final DateTime lastUpdated; DeviceInfo({ @@ -50,6 +54,8 @@ class DeviceInfo { this.rightEarCharging = false, this.caseCharging = false, this.firmwareVersion = '', + this.firmwareProductId = '', + this.firmwareProductColor = '', DateTime? lastUpdated, }) : lastUpdated = lastUpdated ?? DateTime.now(); @@ -65,6 +71,8 @@ class DeviceInfo { bool? rightEarCharging, bool? caseCharging, String? firmwareVersion, + String? firmwareProductId, + String? firmwareProductColor, }) { return DeviceInfo( address: address ?? this.address, @@ -77,6 +85,8 @@ class DeviceInfo { rightEarCharging: rightEarCharging ?? this.rightEarCharging, caseCharging: caseCharging ?? this.caseCharging, firmwareVersion: firmwareVersion ?? this.firmwareVersion, + firmwareProductId: firmwareProductId ?? this.firmwareProductId, + firmwareProductColor: firmwareProductColor ?? this.firmwareProductColor, lastUpdated: DateTime.now(), ); } @@ -96,6 +106,7 @@ class BleManager extends GetxService { // BLE服务实例 final BleService _bleService = BleService(); +// 经典蓝牙实例 // BLE连接状态 final _connectionState = BleConnectionState.disconnected.obs; @@ -106,8 +117,7 @@ class BleManager extends GetxService { bool get isConnected => _isConnected.value; // 设备信息缓存 - final Rx _deviceInfo = Rx(null); - DeviceInfo? get deviceInfo => _deviceInfo.value; + final deviceInfo = Rx(null); // 使用 Rx 包装 // 订阅 StreamSubscription? _statusSubscription; @@ -145,8 +155,8 @@ class BleManager extends GetxService { if (event is Map) { // 处理连接状态更新 - if (event.containsKey('connectionState')) { - final state = event['connectionState'] as int; + if (event['type'] == 'connectionStateChanged') { + final state = event['state'] as int; _connectionState.value = state; // 连接状态转换 @@ -154,8 +164,13 @@ class BleManager extends GetxService { Logger.info('BLE设备已连接'); _isConnected.value = true; - // 在连接成功后获取设备信息 - _fetchDeviceInfo(); + // 在连接成功后延迟 300 毫秒获取设备信息(可根据需要调整时间) + Future.delayed(const Duration(milliseconds: 300), () { + // 可选:检查是否仍处于连接状态 + if (_isConnected.value) { + fetchDeviceInfo(); + } + }); } else if (state == BleConnectionState.disconnected || state == BleConnectionState.error) { Logger.info('BLE设备已断开或发生错误'); @@ -218,12 +233,18 @@ class BleManager extends GetxService { void _processDeviceInfoByCommandId( int commandId, Map data) { switch (commandId) { - case BleCommands.GET_BATTERY_INFO: - _updateBatteryInfoFromData(data); - break; case BleCommands.GET_VERSION: _updateVersionInfoFromData(data); break; + case BleCommands.GET_PRODUCT_ID: + _updateProductIdFromData(data); + break; + case BleCommands.GET_PRODUCT_COLOR: + break; + case BleCommands.GET_BATTERY_INFO: + _updateBatteryInfoFromData(data); + break; + default: Logger.info('未处理的命令ID: $commandId'); } @@ -239,6 +260,11 @@ class BleManager extends GetxService { case 'versionInfo': _updateVersionInfoFromData(data); break; + case 'productId': + _updateProductIdFromData(data); + break; + case 'productColor': + break; default: Logger.info('未处理的命令名称: $commandName'); } @@ -307,6 +333,29 @@ class BleManager extends GetxService { } } + /// 更新设备id + void _updateProductIdFromData(Map 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({ int? batteryLevel, @@ -317,31 +366,53 @@ class BleManager extends GetxService { bool? rightEarCharging, bool? caseCharging, String? firmwareVersion, + String? firmwareProductId, + String? firmwareProductColor, }) { // 如果设备信息已存在,则更新 - if (_deviceInfo.value != null) { - _deviceInfo.value = _deviceInfo.value!.copyWith( - batteryLevel: batteryLevel, - leftEarBattery: leftEarBattery, - rightEarBattery: rightEarBattery, - caseBattery: caseBattery, - leftEarCharging: leftEarCharging, - rightEarCharging: rightEarCharging, - caseCharging: caseCharging, - firmwareVersion: firmwareVersion, + if (deviceInfo.value != null) { + deviceInfo.value = deviceInfo.value!.copyWith( + batteryLevel: batteryLevel ?? deviceInfo.value?.batteryLevel, + leftEarBattery: leftEarBattery ?? deviceInfo.value?.leftEarBattery, + rightEarBattery: rightEarBattery ?? deviceInfo.value?.rightEarBattery, + caseBattery: caseBattery ?? deviceInfo.value?.caseBattery, + leftEarCharging: leftEarCharging ?? deviceInfo.value!.leftEarCharging, + rightEarCharging: + rightEarCharging ?? deviceInfo.value!.rightEarCharging, + 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 && (firmwareVersion != null || batteryLevel != null)) { // 提示尝试创建设备信息但缺少必要信息 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 _fetchDeviceInfo() async { + Future fetchDeviceInfo() async { Logger.info('开始获取BLE设备详细信息'); try { @@ -360,6 +431,14 @@ class BleManager extends GetxService { } else { Logger.warning('版本信息获取指令发送失败'); } + + // 获取产品id + final productIdResult = await _bleService.getProductId(); + if (productIdResult) { + Logger.info('产品id获取指令已发送'); + } else { + Logger.warning('产品id获取指令发送失败'); + } } catch (e) { Logger.error('获取BLE设备信息失败: $e'); } @@ -450,9 +529,9 @@ class BleManager extends GetxService { _connectionState.value = BleConnectionState.connected; // 如果设备信息存在,则获取详细信息 - if (_deviceInfo.value != null) { + if (deviceInfo.value != null) { // 获取详细设备信息 - _fetchDeviceInfo(); + fetchDeviceInfo(); } } } else { @@ -486,7 +565,6 @@ class BleManager extends GetxService { } } - /// 获取电池电量 Future getBatteryLevel() async { try { @@ -507,6 +585,16 @@ class BleManager extends GetxService { } } + /// 主动产品id + Future getProductId() async { + try { + return await _bleService.getProductId(); + } catch (e) { + Logger.error('获取设备版本失败: $e'); + return false; + } + } + /// 主动获取设备版本信息 Future fetchDeviceVersion() async { try { diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index f1a5955e4..f389cc7d5 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -19,7 +19,7 @@ enum ConnectionStatus { /// 主页控制器,管理设备连接状态和信息展示 class HomeController extends GetxController { static HomeController get to => Get.find(); - + // 依赖的控制器 final ExploreController exploreController = Get.find(); @@ -27,31 +27,47 @@ class HomeController extends GetxController { final _connectionStatus = ConnectionStatus.disconnected.obs; final _connectionErrorMsg = ''.obs; final _bluetoothState = 'unknown'.obs; - + // Getter方法 ConnectionStatus get connectionStatus => _connectionStatus.value; String get connectionErrorMsg => _connectionErrorMsg.value; String get bluetoothState => _bluetoothState.value; BluetoothDevice? get currentDevice => _bluetoothManager.currentDevice; - DeviceInfo? get deviceInfo => _bleManager.deviceInfo; + Rx get deviceInfo => _bleManager.deviceInfo; bool get isConnected => currentDevice != null; - + // 设备详细信息的getter,从deviceInfo获取数据 - 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 ?? ''; + final leftEarBattery = 0.obs; + final rightEarBattery = 0.obs; + final caseBattery = 0.obs; + final leftEarCharging = false.obs; + final rightEarCharging = false.obs; + final caseCharging = false.obs; + 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? _bluetoothStateWorker; Worker? _deviceWorker; - + // 蓝牙服务 final _bluetoothManager = Get.find(); final _bleManager = Get.find(); @@ -60,7 +76,7 @@ class HomeController extends GetxController { super.onInit(); _initWithCachedInfo(); } - + @override void onClose() { _deviceInfoWorker?.dispose(); @@ -73,11 +89,12 @@ class HomeController extends GetxController { void _initWithCachedInfo() { // 使用蓝牙服务中的当前状态 _bluetoothState.value = _bluetoothManager.bluetoothState; - + // 根据当前设备状态设置连接状态 if (currentDevice != null) { _connectionStatus.value = ConnectionStatus.connected; _connectionErrorMsg.value = ''; + updateDeviceInfo(_bleManager.deviceInfo.value); Logger.info('主页加载缓存设备信息: ${currentDevice!.name}'); } else { _connectionStatus.value = ConnectionStatus.disconnected; @@ -87,67 +104,73 @@ class HomeController extends GetxController { _connectionErrorMsg.value = '未连接到设备'; } } - + // 设置监听器监听蓝牙服务状态变化 _setupStateListeners(); } - + /// 设置状态监听器 void _setupStateListeners() { // 设备信息监听 - 设备信息更新时自动触发UI更新 - _deviceInfoWorker = ever(_bleManager.deviceInfo.obs, (DeviceInfo? info) { + _deviceInfoWorker = ever(_bleManager.deviceInfo, (DeviceInfo? info) { + Logger.info('设备信息监听'); if (info != null) { Logger.info('主页更新设备信息: ${info.toString()}'); + updateDeviceInfo(info); } else { Logger.info('主页清空设备信息'); } + update(); }); - + // 蓝牙状态监听 - _bluetoothStateWorker = ever(_bluetoothManager.bluetoothState.obs, (String state) { + _bluetoothStateWorker = + ever(_bluetoothManager.bluetoothState.obs, (String state) { _bluetoothState.value = state; - + // 如果蓝牙关闭,更新错误信息 if (state == 'off') { _connectionErrorMsg.value = '蓝牙未开启'; _connectionStatus.value = ConnectionStatus.disconnected; } - + update(); }); - + // 设备连接状态监听 - 直接监听设备变化 - _deviceWorker = ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) { + _deviceWorker = + ever(_bluetoothManager.currentDeviceRx, (BluetoothDevice? device) { if (device != null) { // 设备已连接 if (_connectionStatus.value != ConnectionStatus.connected) { _connectionStatus.value = ConnectionStatus.connected; _connectionErrorMsg.value = ''; - Logger.info('主页检测到设备已连接: ${device.name}'); } } else if (_connectionStatus.value == ConnectionStatus.connected) { // 设备已断开 _connectionStatus.value = ConnectionStatus.disconnected; _connectionErrorMsg.value = '设备已断开连接'; - + Logger.info('主页检测到设备已断开连接'); } - + update(); }); } - + /// 刷新设备连接状态 /// 注意:此方法不会主动刷新设备,只更新UI状态 Future refreshDeviceConnection() async { // 更新当前状态以反映UI变化 bool hasDevice = currentDevice != null; - + if (hasDevice) { _connectionStatus.value = ConnectionStatus.connected; _connectionErrorMsg.value = ''; + await _bleManager.fetchDeviceInfo(); + Logger.info('使用缓存设备信息: ${currentDevice!.name}'); } else { if (_bluetoothState.value == 'off') { @@ -157,13 +180,13 @@ class HomeController extends GetxController { } _connectionStatus.value = ConnectionStatus.disconnected; } - + // 刷新UI update(); } // 导航和功能方法 - + /// 重新扫描设备 void rescanDevices() { Get.toNamed(Routes.pairing); @@ -185,7 +208,7 @@ class HomeController extends GetxController { } // UI辅助方法 - + /// 获取连接状态对应的颜色 Color getConnectionStatusColor() { switch (_connectionStatus.value) { @@ -244,62 +267,71 @@ class HomeController extends GetxController { /// 获取设备ID String getDeviceId() { - return deviceInfo?.address ?? currentDevice?.address ?? 'unknown'.tr; + return deviceInfo.value?.address ?? currentDevice?.address ?? 'unknown'.tr; } - + /// 获取设备电量文本 String getBatteryText() { - return deviceInfo?.batteryLevel != null && deviceInfo!.batteryLevel > 0 - ? '${deviceInfo!.batteryLevel}%' - : 'unknown'.tr; + return deviceInfo.value?.batteryLevel != null && + deviceInfo.value!.batteryLevel > 0 + ? '${deviceInfo.value?.batteryLevel}%' + : 'unknown'.tr; } - + /// 获取耳机左右电量文本 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; } - - String leftStatus = deviceInfo!.leftEarBattery > 0 ? '${deviceInfo!.leftEarBattery}%' : 'N/A'; - String rightStatus = deviceInfo!.rightEarBattery > 0 ? '${deviceInfo!.rightEarBattery}%' : 'N/A'; - - leftStatus = deviceInfo!.leftEarCharging ? '$leftStatus ⚡' : leftStatus; - rightStatus = deviceInfo!.rightEarCharging ? '$rightStatus ⚡' : rightStatus; - + + String leftStatus = deviceInfo.value!.leftEarBattery > 0 + ? '${deviceInfo.value!.leftEarBattery}%' + : 'N/A'; + String rightStatus = deviceInfo.value!.rightEarBattery > 0 + ? '${deviceInfo.value!.rightEarBattery}%' + : 'N/A'; + + leftStatus = + deviceInfo.value!.leftEarCharging ? '$leftStatus ⚡' : leftStatus; + rightStatus = + deviceInfo.value!.rightEarCharging ? '$rightStatus ⚡' : rightStatus; + return '左耳: $leftStatus / 右耳: $rightStatus'; } - + /// 获取充电仓电量文本 String getCaseText() { - if (deviceInfo == null || deviceInfo!.caseBattery <= 0) { + if (deviceInfo.value == null || deviceInfo.value!.caseBattery <= 0) { return 'unknown'.tr; } - - String caseStatus = '${deviceInfo!.caseBattery}%'; - if (deviceInfo!.caseCharging) { + + String caseStatus = '${deviceInfo.value!.caseBattery}%'; + if (deviceInfo.value!.caseCharging) { caseStatus = '$caseStatus ⚡'; } - + return '充电仓: $caseStatus'; } - + /// 获取充电状态文本 String getChargingText() { if (deviceInfo == null) { return 'unknown'.tr; } - - bool isCharging = deviceInfo!.leftEarCharging || - deviceInfo!.rightEarCharging || - deviceInfo!.caseCharging; - + + bool isCharging = deviceInfo.value!.leftEarCharging || + deviceInfo.value!.rightEarCharging || + deviceInfo.value!.caseCharging; + return isCharging ? 'charging'.tr : 'notCharging'.tr; } - + /// 获取设备版本文本 String getVersionText() { - return deviceInfo?.firmwareVersion.isNotEmpty == true ? - deviceInfo!.firmwareVersion : 'unknown'.tr; + return deviceInfo.value?.firmwareVersion.isNotEmpty == true + ? deviceInfo.value!.firmwareVersion + : 'unknown'.tr; } - } diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index abdb2f920..dae65616a 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -160,43 +160,43 @@ class HomeView extends GetView { child: ClipRRect( // 添加 ClipRRect 来实现圆角 borderRadius: BorderRadius.circular(16.r), // 使用与其他部件一致的圆角大小 - child: SizedBox( - width: double.infinity, - height: 200.h, - child: Image.asset( - isDarkMode - ? 'assets/images/robot_light.png' - : 'assets/images/robot_light.png', - fit: BoxFit.fitWidth, + child: SizedBox( + width: double.infinity, + height: 200.h, + child: Image.asset( + isDarkMode + ? 'assets/images/robot_light.png' + : 'assets/images/robot_light.png', + fit: BoxFit.fitWidth, + ), ), ), ), - ), Center( child: ClipRRect( // 添加 ClipRRect borderRadius: BorderRadius.circular(16.r), - child: Image.asset( - isDarkMode - ? 'assets/images/headphone_dark.png' - : 'assets/images/headphone_light.png', - height: 180.h, - fit: BoxFit.contain, + child: Image.asset( + isDarkMode + ? 'assets/images/headphone_dark.png' + : 'assets/images/headphone_light.png', + height: 180.h, + fit: BoxFit.contain, + ), ), ), - ), Center( child: ClipRRect( // 添加 ClipRRect borderRadius: BorderRadius.circular(16.r), - child: SizedBox( - width: double.infinity, - height: 200.h, - child: Image.asset( - isDarkMode - ? 'assets/images/glasses_light.png' - : 'assets/images/glasses_light.png', - fit: BoxFit.fitWidth, + child: SizedBox( + width: double.infinity, + height: 200.h, + child: Image.asset( + isDarkMode + ? 'assets/images/glasses_light.png' + : 'assets/images/glasses_light.png', + fit: BoxFit.fitWidth, ), ), ), @@ -283,50 +283,52 @@ class HomeView extends GetView { ], ), Obx(() => Row( - children: [ - Container( - width: 8.w, - height: 8.w, - decoration: BoxDecoration( - color: controller.getConnectionStatusColor(), - shape: BoxShape.circle, - boxShadow: [ - BoxShadow( - color: controller.getConnectionStatusColor().withOpacity(0.3), - blurRadius: 4, - spreadRadius: 1, + children: [ + Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + color: controller.getConnectionStatusColor(), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: controller + .getConnectionStatusColor() + .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( - Icons.refresh, - size: 14.sp, - color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + 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( + Icons.refresh, + size: 14.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + ], + )), ], ); } @@ -338,9 +340,8 @@ class HomeView extends GetView { Container( padding: EdgeInsets.all(12.w), decoration: BoxDecoration( - color: isDarkMode - ? Colors.red[900]!.withOpacity(0.2) - : Colors.red[50], + color: + isDarkMode ? Colors.red[900]!.withOpacity(0.2) : Colors.red[50], borderRadius: BorderRadius.circular(12.r), border: Border.all( color: isDarkMode ? Colors.red[800]! : Colors.red[200]!), @@ -440,12 +441,12 @@ class HomeView extends GetView { final leftBattery = controller.leftEarBattery; final rightBattery = controller.rightEarBattery; final caseBattery = controller.caseBattery; - + // 获取充电状态 final leftEarCharging = controller.leftEarCharging; final rightEarCharging = controller.rightEarCharging; final caseCharging = controller.caseCharging; - + return GridView.count( crossAxisCount: 3, shrinkWrap: true, @@ -457,33 +458,33 @@ class HomeView extends GetView { _buildBatteryGridItem( icon: Icons.headphones, label: '左耳机', - batteryLevel: leftBattery, - isCharging: leftEarCharging, + batteryLevel: leftBattery.value, + isCharging: leftEarCharging.value, isDarkMode: isDarkMode, ), - + // 右耳机 _buildBatteryGridItem( icon: Icons.headphones, label: '右耳机', - batteryLevel: rightBattery, - isCharging: rightEarCharging, + batteryLevel: rightBattery.value, + isCharging: rightEarCharging.value, isDarkMode: isDarkMode, ), - + // 充电盒 _buildBatteryGridItem( icon: Icons.charging_station, label: '充电盒', - batteryLevel: caseBattery, - isCharging: caseCharging, + batteryLevel: caseBattery.value, + isCharging: caseCharging.value, isDarkMode: isDarkMode, ), ], ); }); } - + // 单个电池网格项 Widget _buildBatteryGridItem({ required IconData icon, @@ -495,7 +496,7 @@ class HomeView extends GetView { // 获取电池图标和颜色 IconData batteryIcon; Color batteryColor; - + if (batteryLevel > 75) { batteryIcon = Icons.battery_full; batteryColor = Colors.green[600]!; @@ -512,16 +513,16 @@ class HomeView extends GetView { batteryIcon = Icons.battery_alert; batteryColor = Colors.red[600]!; } - + return Container( decoration: BoxDecoration( color: isDarkMode ? Colors.grey[900] : Colors.grey[100], borderRadius: BorderRadius.circular(12.r), boxShadow: [ BoxShadow( - color: isDarkMode ? - Colors.black.withOpacity(0.1) : - Colors.grey.withOpacity(0.1), + color: isDarkMode + ? Colors.black.withOpacity(0.1) + : Colors.grey.withOpacity(0.1), blurRadius: 4, spreadRadius: 0, offset: Offset(0, 2), @@ -545,7 +546,7 @@ class HomeView extends GetView { ), ), SizedBox(height: 6.h), - + // 标签 Text( label, @@ -557,7 +558,7 @@ class HomeView extends GetView { textAlign: TextAlign.center, ), SizedBox(height: 6.h), - + // 电池指示器 Row( mainAxisAlignment: MainAxisAlignment.center, @@ -614,16 +615,15 @@ class HomeView extends GetView { isDarkMode: isDarkMode, ), SizedBox(height: 8.h), - + // 固件版本 _buildInfoRow( icon: Icons.info_outline, label: 'firmwareVersion'.tr, - value: controller.firmwareVersion, + value: controller.firmwareVersion.value, isDarkMode: isDarkMode, ), - - + // 连接状态标签 SizedBox(height: 10.h), Container( @@ -763,7 +763,7 @@ class HomeView extends GetView { ), ); } - + // 热门AI体验标题 Widget _buildSectionTitle(bool isDarkMode) { return Row( diff --git a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart index 878a97be6..a59a16289 100644 --- a/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart +++ b/lib/modules/ota_upgrade/controllers/ota_upgrade_controller.dart @@ -1,19 +1,17 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:path/path.dart'; import 'dart:async'; -import 'dart:convert'; -import 'package:get_storage/get_storage.dart'; import '../../../data/services/network/api.dart'; -import '../ota.dart'; +import '../model/ota_upgrade_model.dart'; import 'dart:io'; 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:json_annotation/json_annotation.dart'; +import '../../../data/services/ble_manager.dart'; class OtaUpgradeController extends GetxController { + final RxString errorMessage = ''.obs; + final RxBool isFetchingDeviceInfo = false.obs; final Dio _dio = Dio(); final RxString latestVersion = ''.obs; final RxString currentVersion = ''.obs; @@ -24,41 +22,93 @@ class OtaUpgradeController extends GetxController { final RxDouble progress = 0.0.obs; + // 蓝牙服务 + final _bleManager = Get.find(); @override void onInit() { super.onInit(); _getCurrentVersion(); } - // 获取当前版本信息 + // 添加重试方法 + void retryConnection() { + errorMessage.value = ''; + _getCurrentVersion(); + } + + // 添加更新方法 + Future startUpdate() async { + try { + isUpdating.value = true; + // 实现更新逻辑... + } catch (e) { + errorMessage.value = e.toString(); + Get.snackbar('更新失败', e.toString()); + } finally { + isUpdating.value = false; + } + } + Future _getCurrentVersion() async { - //发送ble获取固件版本 - var firmwareVersion = "1.1.0"; - currentVersion.value = firmwareVersion; - //发送ble获取产品id - var id = "a2"; - //发送api请求 - final response = await Api.getequipmentinfo({ - "id": id, - }); - if (response['info'] != '') { - print('getLoginToken:进来了'); - // 根据API设计调整 - final resp = OtaConfigResp.fromJson(response); + try { + // 添加获取设备信息的重试机制 + DeviceInfo? deviceInfo = _bleManager.deviceInfo.value; + + // 第一次检查设备信息 + if (deviceInfo == null) { + // 显示加载提示 + isFetchingDeviceInfo.value = true; + + // 尝试重新获取设备信息 + await _bleManager.fetchDeviceInfo(); + + // 等待后再次获取设备信息 + 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!; updatepackageaddress = resp.updatepackageaddress!; - if (_isNewVersionAvailable(resp.systemversion!)) { - //需要升级 - print('需要更新'); - isUpdateAvailable.value = false; - } else { - isUpdateAvailable.value = true; - //已经是最新版本更新ui,按钮隐藏 - } - } else { - throw const FormatException('API 响应缺少 "info" 字段'); + + // 版本检查 + isUpdateAvailable.value = _isNewVersionAvailable(resp.systemversion!); + } catch (e) { + // 统一错误处理 + errorMessage.value = e.toString(); + Get.snackbar('错误', e.toString(), + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white); + // 重要:重新抛出异常以便上层处理(可选) + } finally { + isFetchingDeviceInfo.value = false; } } @@ -120,13 +170,10 @@ class OtaUpgradeController extends GetxController { ); } - // 开始下载 +// 修改后的startDownload方法 Future startDownload() async { isUpdating.value = true; - // 查找最后一个 '/' 的位置 int lastIndex = updatepackageaddress.lastIndexOf('/'); - - // 截取最后一个 '/' 后面的字符串 String fileName = updatepackageaddress.substring(lastIndex + 1); String directoryName = "ota_upgrades"; @@ -145,14 +192,54 @@ class OtaUpgradeController extends GetxController { throw FileSystemException('无法访问下载目录'); } - // 创建子目录:Download/APP final targetDir = Directory('${downloadsDir.path}/$directoryName'); if (!await targetDir.exists()) { await targetDir.create(recursive: true); } 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( receiveTimeout: Duration.zero, @@ -181,22 +268,23 @@ class OtaUpgradeController extends GetxController { }, ); - // 下载完成后验证文件存在性 File file = File(savePath); if (await file.exists()) { - Get.snackbar('下载完成', '文件已保存至: $savePath'); // 明确提示路径 + Get.snackbar('下载完成', '文件已保存至: $savePath'); } else { Get.snackbar('错误', '文件未找到,请检查写入权限'); } isUpdating.value = false; - //下载完成,开始升级 _installApp(savePath); } on DioException catch (e) { Get.snackbar('下载失败', '错误: ${e.message}'); + isUpdating.value = false; } on FileSystemException catch (e) { Get.snackbar('存储失败', '无法写入文件: ${e.message}'); + isUpdating.value = false; } catch (e) { Get.snackbar('未知错误', e.toString()); + isUpdating.value = false; } } diff --git a/lib/modules/ota_upgrade/ota.dart b/lib/modules/ota_upgrade/model/ota_upgrade_model.dart similarity index 100% rename from lib/modules/ota_upgrade/ota.dart rename to lib/modules/ota_upgrade/model/ota_upgrade_model.dart diff --git a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart index bc507d1e9..a33120845 100644 --- a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart +++ b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart @@ -4,106 +4,126 @@ import '../controllers/ota_upgrade_controller.dart'; class OtaUpgradeView extends GetView { const OtaUpgradeView({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return Scaffold( body: Center( - // 添加外层Center - child: SingleChildScrollView( - // 添加滚动防止小屏溢出 - child: Padding( - padding: EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, // 水平居中 - mainAxisSize: MainAxisSize.min, // 紧凑布局 - children: [ - // 设备图标 - CircleAvatar( - radius: 60, - backgroundColor: Colors.blue[100], - child: Icon( - Icons.phone_iphone, - size: 60, - color: Colors.blue, - ), - ), - SizedBox(height: 30), + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 600), // 限制最大宽度 + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 设备图标 + _buildDeviceIcon(), + SizedBox(height: 30), - // 版本信息 - 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, - ), - ), - ], - ), - SizedBox(height: 40), + // 版本信息 + _buildVersionInfo(), + SizedBox(height: 40), - // 更新按钮/进度条 - Obx(() { - if (controller.isUpdating.value) { - return 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), - ) - ], - ); - } 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), - ), - ); - } - }), - ], + // 状态显示区域 + Obx(() { + if (controller.isFetchingDeviceInfo.value) { + return _buildLoadingIndicator(); + } + if (controller.errorMessage.value.isNotEmpty) { + return _buildErrorInfo(); + } + return _buildUpdateButton(); + }), + ], + ), ), ), ), ), ); } + + // 组件化构建方法 + 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)) + ], + ); } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index ef5a6553d..dd6d24917 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/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) { - + val data = c.value ?: return // 根据特征UUID区分处理 @@ -1017,9 +1017,11 @@ object BleService { stopOpusStreamDecoding() // 停止Opus解码流 return sendCommand( BleConst.CMD_CONTROL_CODEC.toByte(), - byteArrayOf(BleConst.CODEC_CONTROL_CLOSE.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte()) // 或其他默认声道 + byteArrayOf( + BleConst.CODEC_CONTROL_CLOSE.toByte(), + + BleConst.AUDIO_CHANNEL_RIGHT.toByte() + ) // 或其他默认声道 ) } @@ -1027,45 +1029,54 @@ object BleService { * 控制编解码 - 打开解码 */ fun openDecoder(): Boolean { - FileLogger.i(TAG, "打开编码 (设备侧)...") + FileLogger.i(TAG, "打开编码 (设备侧)...") val opusStarted = startOpusStreamDecoding( hasHeader = false, // BLE传输的Opus数据通常无文件头 channel = 1, // 单声道 sampleRate = 16000,// Opus推荐16kHz或更高 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. - // The library might expect this in a different unit or derive it. - // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. + // packetSize for Opus is typically in ms, e.g. 20, 40, 60. + // The library might expect this in a different unit or derive it. + // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. ) - + FileLogger.i(TAG, "打开解码...") - return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_DECODE_ON.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte())) + return sendCommand( + BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( + BleConst.CODEC_CONTROL_DECODE_ON.toByte(), + + BleConst.AUDIO_CHANNEL_RIGHT.toByte() + ) + ) } + /** * 控制编解码 - 打开解码 */ fun openA2DPDecoder(): Boolean { - FileLogger.i(TAG, "打开编码 (设备侧)...") + FileLogger.i(TAG, "打开编码 (设备侧)...") val opusStarted = startOpusStreamDecoding( hasHeader = false, // BLE传输的Opus数据通常无文件头 channel = 1, // 单声道 sampleRate = 16000,// Opus推荐16kHz或更高 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. - // The library might expect this in a different unit or derive it. - // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. + // packetSize for Opus is typically in ms, e.g. 20, 40, 60. + // The library might expect this in a different unit or derive it. + // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. ) - + FileLogger.i(TAG, "打开解码...") - return sendCommand(BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf(BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(), - - BleConst.AUDIO_CHANNEL_RIGHT.toByte())) + return sendCommand( + BleConst.CMD_CONTROL_CODEC.toByte(), byteArrayOf( + BleConst.CODEC_CONTROL_A2DP_PLAY.toByte(), + + BleConst.AUDIO_CHANNEL_RIGHT.toByte() + ) + ) } /** @@ -1087,11 +1098,11 @@ object BleService { channel = 1, // 单声道 sampleRate = 16000,// Opus推荐16kHz或更高 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. - // The library might expect this in a different unit or derive it. - // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. + // packetSize for Opus is typically in ms, e.g. 20, 40, 60. + // The library might expect this in a different unit or derive it. + // Given JlOpusPlugin.kt, packetSize refers to Opus encoded frame duration in ms. ) - + // 注意:此方法通常是控制设备端开始编码并发送数据。 // App侧通常是打开解码器(openDecoder)来接收和处理这些数据。 @@ -1178,7 +1189,6 @@ object BleService { return bluetoothAdapter } - // ====================================================================================================== // 资源清理 diff --git a/local_plugins/ble_service/ios/Classes/BleService.swift b/local_plugins/ble_service/ios/Classes/BleService.swift index b4c2ae456..92f129d9a 100644 --- a/local_plugins/ble_service/ios/Classes/BleService.swift +++ b/local_plugins/ble_service/ios/Classes/BleService.swift @@ -353,6 +353,11 @@ actor BleService { func getDeviceVersion() -> Bool { return sendCommand(BleConst.CMD_GET_VERSION) } + ///获取产品ID + /// - Returns: 是否成功发送请求 + func getProductId() -> Bool { + return sendCommand(BleConst.CMD_GET_PRODUCT_ID) + } /// 获取电池信息 /// - Returns: 是否成功发送请求 @@ -371,6 +376,11 @@ actor BleService { func openDecoder() -> Bool { return sendCodecControl(BleConst.CODEC_CONTROL_DECODE_ON) } + /// 打开解码器 + /// - Returns: 是否成功发送请求 + func openA2DPDecoder() -> Bool { + return sendCodecControl(BleConst.CODEC_CONTROL_A2DP_PLAY) + } /// 关闭编解码器 /// - Returns: 是否成功发送请求 diff --git a/local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift b/local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift index 140c0ca7a..e3004c43c 100644 --- a/local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift +++ b/local_plugins/ble_service/ios/Classes/SwiftBleServicePlugin.swift @@ -73,12 +73,18 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin, BleDelegate { case "getDeviceVersion": result(bleService.getDeviceVersion()) + case "getProductId": + result(bleService.getProductId()) + case "openEncoder": result(bleService.openEncoder()) case "openDecoder": result(bleService.openDecoder()) - + + case "openA2DPDecoder": + result(bleService.openA2DPDecoder()) + case "closeCodec": result(bleService.closeCodec()) diff --git a/local_plugins/ble_service/lib/ble_service.dart b/local_plugins/ble_service/lib/ble_service.dart index 3b791b437..9b28d7cfc 100644 --- a/local_plugins/ble_service/lib/ble_service.dart +++ b/local_plugins/ble_service/lib/ble_service.dart @@ -83,6 +83,17 @@ class BleService { } } + /// 获取产品ID + Future getProductId() async { + try { + final result = await _methodChannel.invokeMethod('getProductId'); + return result ?? false; + } catch (e) { + print('获取设备版本失败: $e'); + return false; + } + } + /// 获取电池信息 Future getBatteryInfo() async { try { From 7663ca57b6979192165d2df036f268500721aa94 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 12 May 2025 10:30:07 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E4=BB=A3=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controllers/agent_controller.dart | 2 +- .../music_service/MusicServicePlugin.kt | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 07f5532a9..ddb80a1d4 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -621,7 +621,7 @@ class AgentController extends GetxController { // 播放音乐 void musicStatus() async { var status = await _musiceManager.getStatus(); - logger.i('获取当前音乐播放状态 $status'); + // logger.i('获取当前音乐播放状态 $status'); isPlaying.value = status['isPlaying']; currentUrl.value = status['url']; } diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt index f7686faf2..60bd63f40 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt @@ -24,22 +24,33 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { FileLogger.d(TAG, "Method call: ${call.method}") - val intent = Intent(context, MusicService::class.java) when (call.method) { "play" -> { val url = call.argument("url") MusicServiceStarter.startServiceWithCommand(context, "play", url) + result.success(null) } "playlist" -> { val urls = call.argument>("urls") MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls) + result.success(null) + } + "pause" -> { + MusicServiceStarter.startServiceWithCommand(context, "pause") + result.success(null) + } + "resume" -> { + MusicServiceStarter.startServiceWithCommand(context, "resume") + result.success(null) + } + "stop" -> { + MusicServiceStarter.startServiceWithCommand(context, "stop") + result.success(null) } - "pause" -> MusicServiceStarter.startServiceWithCommand(context, "pause") - "resume" -> MusicServiceStarter.startServiceWithCommand(context, "resume") - "stop" -> MusicServiceStarter.startServiceWithCommand(context, "stop") "setVolume" -> { val volume = call.argument("volume")?.toFloat() MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume) + result.success(null) } "getStatus" -> { val isPlaying = MusicService.isPlaying() @@ -52,7 +63,6 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { } else -> result.notImplemented() } - result.success(null) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {} From 158760ad2d908227aee3e29363ac7f41cea6076a Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 12 May 2025 12:17:56 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E8=BF=98=E5=8E=9F=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/pairing_controller.dart | 129 ++++++++++-------- .../agent_service/android/build.gradle.kts | 1 + .../agent_service/AgentService.kt | 70 +++++++++- .../music_service/MusicServicePlugin.kt | 11 +- 4 files changed, 146 insertions(+), 65 deletions(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 4dedaefcb..1a0e0e55c 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -10,13 +10,14 @@ import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice; import '../../../data/services/ble_manager.dart'; import 'package:agent_service/agent_service.dart'; import 'package:ble_service/ble_service.dart'; + /// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程 class PairingController extends GetxController { static PairingController get to => Get.find(); // 状态标志 final isLoading = false.obs; - + // 权限是否已授予 final _hasPermissions = false.obs; bool get hasPermissions => _hasPermissions.value; @@ -25,22 +26,22 @@ class PairingController extends GetxController { final _bluetoothState = 'unknown'.obs; String get bluetoothState => _bluetoothState.value; bool get isBluetoothOn => _bluetoothState.value == 'on'; - + // 状态描述(用于UI显示) final statusDescription = 'initializing'.tr.obs; // 是否正在跳转到主页 bool _isNavigatingToHome = false; - + // 是否正在执行Companion关联流程 bool _isAssociating = false; // 当前连接设备信息 BluetoothDevice? get currentDevice => _bluetoothService.currentDevice; - + // 是否已连接设备 bool get isDeviceConnected => currentDevice != null; - + // 是否已完成Companion关联 final _companionAssociated = false.obs; bool get isCompanionAssociated => _companionAssociated.value; @@ -48,10 +49,10 @@ class PairingController extends GetxController { // 服务状态监听 Worker? _bluetoothStateWorker; Worker? _deviceWorker; - + // 蓝牙服务 final _bluetoothService = Get.find(); - + // BLE服务 final _bleService = BleService(); @@ -96,7 +97,7 @@ class PairingController extends GetxController { // 刷新已连接设备 statusDescription.value = 'searchingDevice'.tr; await refreshConnectedDevices(); - + // 检查是否已存在Companion设备关联,但不要重复调用关联流程 await _checkExistingCompanionAssociation(); } catch (e) { @@ -107,16 +108,16 @@ class PairingController extends GetxController { _updateStatusDescription(); } } - + /// 检查是否已存在Companion设备关联 Future _checkExistingCompanionAssociation() async { // 只在Android平台执行 if (!Platform.isAndroid) return; - + try { final isSupported = await _bleService.isCompanionSupported(); if (!isSupported) return; - + final pairedMac = await _bleService.getPairedMacAddress(); if (pairedMac != null) { Logger.info('发现已关联的Companion设备: $pairedMac'); @@ -156,18 +157,18 @@ class PairingController extends GetxController { Logger.warning('权限 ${permission.toString()} 未被授予: $status'); } }); - + _hasPermissions.value = allGranted; return allGranted; } - + // 辅助方法获取Android SDK版本 Future _getAndroidSdkVersion() async { if (Platform.isAndroid) { - // 这里需要原生代码支持来获取确切的SDK版本, - // 或者使用 device_info_plus 插件 - // 暂时返回一个较高值以触发新权限请求 - return 31; // 假设运行在需要新权限的设备上 + // 这里需要原生代码支持来获取确切的SDK版本, + // 或者使用 device_info_plus 插件 + // 暂时返回一个较高值以触发新权限请求 + return 31; // 假设运行在需要新权限的设备上 } return 0; } @@ -175,38 +176,41 @@ class PairingController extends GetxController { /// 设置服务状态监听 void _setupServiceStateListeners() { // 监听蓝牙服务的蓝牙状态变化 - _bluetoothStateWorker = ever(_bluetoothService.bluetoothState.obs, (String state) { + _bluetoothStateWorker = + ever(_bluetoothService.bluetoothState.obs, (String state) { _bluetoothState.value = state; _updateStatusDescription(); - + // 如果蓝牙关闭,可能需要更新UI或提示用户 if (state == 'off') { Logger.info('蓝牙已关闭'); } }); - + Logger.info('liwei----------------- _setupServiceStateListeners 1'); // 监听当前设备变化 - 使用公开的currentDeviceRx字段 - _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { + _deviceWorker = + ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { if (device != null && !_isNavigatingToHome) { Logger.info('配对页面发现设备已连接: ${device.name}'); - + Logger.info('liwei----------------- _setupServiceStateListeners 2'); // 当经典蓝牙设备连接成功,尝试进行Companion关联 _handleDeviceConnected(); } _updateStatusDescription(); }); } - + /// 处理设备连接成功事件 Future _handleDeviceConnected() async { + Logger.info('liwei----------------- _handleDeviceConnected 1'); if (!isDeviceConnected || _isNavigatingToHome) return; - + Logger.info('liwei----------------- _handleDeviceConnected 2'); // 如果已经完成关联,直接跳转 if (_companionAssociated.value) { _autoNavigateToHome(); return; } - + Logger.info('liwei----------------- _handleDeviceConnected 3'); // 启动关联流程 _startCompanionAssociation(); } @@ -217,7 +221,7 @@ class PairingController extends GetxController { statusDescription.value = 'permissionDenied'.tr; return; } - + if (bluetoothState == 'off') { statusDescription.value = 'bluetoothTurnedOff'.tr; return; @@ -225,11 +229,14 @@ class PairingController extends GetxController { if (isDeviceConnected) { if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) { - statusDescription.value = '${'associatingDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'associatingDevice'.tr} ${currentDevice!.name}'; } else if (isCompanionAssociated) { - statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'connectedDevice'.tr} ${currentDevice!.name}'; } else { - statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; + statusDescription.value = + '${'connectedDevice'.tr} ${currentDevice!.name}'; } return; } @@ -248,18 +255,18 @@ class PairingController extends GetxController { statusDescription.value = 'permissionDenied'.tr; return; } - + try { isLoading.value = true; statusDescription.value = 'searchingDevice'.tr; - + // 使用蓝牙服务刷新连接 await _bluetoothService.refreshDeviceConnection(); - + // 检查连接状态 if (isDeviceConnected) { Logger.info('刷新发现设备已连接: ${currentDevice!.name}'); - + // 当发现设备已连接,统一由_handleDeviceConnected处理 _handleDeviceConnected(); } else { @@ -273,18 +280,22 @@ class PairingController extends GetxController { _updateStatusDescription(); } } - + /// 启动Companion设备关联流程 Future _startCompanionAssociation() async { + Logger.info('liwei----------------- _startCompanionAssociation 1'); // 如果不是Android平台,已经跳转到首页,已经完成关联或正在进行关联,直接返回 - if (!Platform.isAndroid || _isNavigatingToHome || _companionAssociated.value || _isAssociating) { + if (!Platform.isAndroid || + _isNavigatingToHome || + _companionAssociated.value || + _isAssociating) { return; } - + Logger.info('liwei----------------- _startCompanionAssociation 2'); // 设置关联状态标志 _isAssociating = true; _updateStatusDescription(); - + try { // 检查是否支持Companion Device功能 final isSupported = await _bleService.isCompanionSupported(); @@ -294,7 +305,7 @@ class PairingController extends GetxController { _autoNavigateToHome(); return; } - + Logger.info('liwei----------------- _startCompanionAssociation 3'); // 检查是否已有配对的MAC地址 final pairedMac = await _bleService.getPairedMacAddress(); if (pairedMac != null) { @@ -303,34 +314,35 @@ class PairingController extends GetxController { Logger.info('已关联的Companion设备与当前连接的设备一致'); _companionAssociated.value = true; await _bleService.registerBackgroundScan(); - _autoNavigateToHome(); - return; + // _autoNavigateToHome(); + // return; } else { // 不一致,清除旧关联 Logger.info('已关联的Companion设备与当前连接的设备不一致,清除旧关联'); await _bleService.clearAssociations(); } } - + Logger.info('liwei----------------- _startCompanionAssociation 4'); // 开始关联流程 if (currentDevice != null) { Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}'); - - final success = await _bleService.startDeviceAssociation( + + var success = await _bleService.startDeviceAssociation( deviceNamePattern: currentDevice!.name, macAddress: currentDevice!.address, ); - + success = true; if (success) { Logger.info('Companion设备关联成功'); _companionAssociated.value = true; - + // 注册后台扫描,确保应用被杀死后仍能被唤醒 - await _bleService.registerBackgroundScan(macAddress: currentDevice!.address); - + await _bleService.registerBackgroundScan( + macAddress: currentDevice!.address); + Logger.info('liwei----------------- _startCompanionAssociation 5'); // 启动BLE后台服务(可选) await _startAgentService(); - + // 关联和后台扫描都成功后,再跳转 _autoNavigateToHome(); } else { @@ -353,10 +365,10 @@ class PairingController extends GetxController { /// 自动跳转到主页 Future _autoNavigateToHome() async { if (_isNavigatingToHome) return; - + // 如果没有连接,不进行跳转 if (!isDeviceConnected) return; - + // 在Android平台上,如果支持Companion且仍在关联中,不跳转 if (Platform.isAndroid) { final isSupported = await _bleService.isCompanionSupported(); @@ -365,25 +377,24 @@ class PairingController extends GetxController { return; } } - + _isNavigatingToHome = true; - + Logger.info('已找到连接的设备 ${currentDevice!.name},自动跳转到主页'); - + Future.delayed(const Duration(milliseconds: 500), () { Get.offAllNamed(Routes.home); }); } - + /// 启动BLE后台服务 Future _startAgentService() async { try { if (!isDeviceConnected) return; - // 启动BLE服务,无需传递配置参数,将从.env读取 final result = await BleManager.to.startAgentService(); - + if (result) { Logger.info('BLE后台服务启动成功'); } else { @@ -393,12 +404,12 @@ class PairingController extends GetxController { Logger.error('启动BLE后台服务发生错误: $e'); } } - + /// 停止BLE后台服务 Future _stopAgentService() async { try { final result = await BleManager.to.stopAgentService(); - + if (result) { Logger.info('BLE后台服务停止成功'); } else { @@ -425,7 +436,7 @@ class PairingController extends GetxController { Logger.error('打开设置失败: $e'); } } - + /// 打开应用设置 Future openAppSettings() async { await AppSettings.openAppSettings(); diff --git a/local_plugins/agent_service/android/build.gradle.kts b/local_plugins/agent_service/android/build.gradle.kts index 5a0a26504..b6c1b4d21 100644 --- a/local_plugins/agent_service/android/build.gradle.kts +++ b/local_plugins/agent_service/android/build.gradle.kts @@ -48,4 +48,5 @@ dependencies { add("compileOnly", project(":deepvoice_utils")) add("compileOnly", project(":chat_storage")) add("compileOnly", project(":ble_service")) + add("compileOnly", project(":music_service")) } \ No newline at end of file diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 3bea170d1..a00625399 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -690,6 +690,42 @@ object AgentService : CoroutineScope { "result" to functionCallResult.toString(), )) FileLogger.d(TAG, "mcp调用结果: $functionCallResult") + val metaStr = functionCallResult.optString("meta") + if (metaStr.isNotEmpty()) { + metadata = metaStr; + val meta = JSONObject(metaStr) + FileLogger.d(TAG, "mcp调用结果: $metaStr") + if (meta.has("card_music")) { //音乐卡片 + val cardMusic = meta.getJSONObject("card_music") + FileLogger.d(TAG, "检查到音乐卡片: $cardMusic") + val musicUrl = cardMusic.optString("url", "") + val name = cardMusic.optString("name", "") + val sgener = cardMusic.optString("sgener", "") + processMusicPlay(musicUrl, name, sgener) + }else if (meta.has("card_musiclist")) { //音乐列表 + val cardMusiclist = meta.getJSONObject("card_musiclist") + val musics = cardMusiclist.optJSONArray("musics") + if (musics != null) { + val playlist = mutableListOf>() + for (i in 0 until musics.length()) { + val item = musics.optJSONObject(i) ?: continue + val url = item.optString("url", "") + val name = item.optString("name", "") + val sgener = item.optString("sgener", "") + if (url.isNotEmpty()) { + playlist.add(mapOf("url" to url, "name" to name, "sgener" to sgener)) + } + } + if (playlist.isNotEmpty()) { + processMusicPlayList(playlist) + } else { + FileLogger.w(TAG, "card_musiclist 中没有有效的音乐条目") + } + } else { + FileLogger.w(TAG, "card_musiclist.musics 不是一个数组") + } + } + } } } ) @@ -1040,4 +1076,36 @@ object AgentService : CoroutineScope { } } } -} \ No newline at end of file + + /** + * 处理音乐播放 + * 将ai返回的音乐连接 在后台启动播放 + * + * @param musicUrl 图片文件路径 + * @param name 歌曲名称,默认为空 + * @param singer 歌手,默认为false + * @return 是否成功开始处理 + */ + fun processMusicPlay(musicUrl:String,name: String = "",singer:String = ""){ + // 在其他 Service、BroadcastReceiver 或 Application 中调用 + FileLogger.i(TAG, "启动音乐服务 播放音乐 $musicUrl") + MusicServiceStarter.startServiceWithCommand(context, "play", url = musicUrl) + } + + /** + * 处理音乐播放列表 + * 将ai返回的音乐连接 在后台启动播放 + * + * @param musicUrl 图片文件路径 + * @param name 歌曲名称,默认为空 + * @param singer 歌手,默认为false + * @return 是否成功开始处理 + */ + fun processMusicPlayList(playlist:List>){ + // 在其他 Service、BroadcastReceiver 或 Application 中调用 + + val urls = playlist.mapNotNull { it["url"] }.distinct() + FileLogger.i(TAG, "启动音乐服务 播放音乐列表 $urls") + MusicServiceStarter.startServiceWithPlaylist(context, "playlist", urls = urls) + } +} \ No newline at end of file diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt index 60bd63f40..e85c4be44 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicServicePlugin.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import androidx.core.content.ContextCompat import com.yunqiinnovation.deepvoice_utils.FileLogger +import com.yunqiinnovation.music_service.MusicServiceStarter.startServiceWithCommand import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel @@ -27,7 +28,7 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { when (call.method) { "play" -> { val url = call.argument("url") - MusicServiceStarter.startServiceWithCommand(context, "play", url) + startServiceWithCommand(context, "play", url) result.success(null) } "playlist" -> { @@ -36,20 +37,20 @@ class MusicServicePlugin: FlutterPlugin, MethodCallHandler { result.success(null) } "pause" -> { - MusicServiceStarter.startServiceWithCommand(context, "pause") + startServiceWithCommand(context, "pause") result.success(null) } "resume" -> { - MusicServiceStarter.startServiceWithCommand(context, "resume") + startServiceWithCommand(context, "resume") result.success(null) } "stop" -> { - MusicServiceStarter.startServiceWithCommand(context, "stop") + startServiceWithCommand(context, "stop") result.success(null) } "setVolume" -> { val volume = call.argument("volume")?.toFloat() - MusicServiceStarter.startServiceWithCommand(context, "setVolume", volume = volume) + startServiceWithCommand(context, "setVolume", volume = volume) result.success(null) } "getStatus" -> { From f2b035f4f8e24fa5581eb69a5cce12accadfc025 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 12 May 2025 12:21:46 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=93=9D=E7=89=99?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=20=E6=B5=81=E7=A8=8B=E8=B0=83=E6=95=B4=20?= =?UTF-8?q?=E6=96=B9=E4=BE=BF=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pairing/controllers/pairing_controller.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 1a0e0e55c..44a30fa2c 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -186,13 +186,11 @@ class PairingController extends GetxController { Logger.info('蓝牙已关闭'); } }); - Logger.info('liwei----------------- _setupServiceStateListeners 1'); // 监听当前设备变化 - 使用公开的currentDeviceRx字段 _deviceWorker = ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) { if (device != null && !_isNavigatingToHome) { Logger.info('配对页面发现设备已连接: ${device.name}'); - Logger.info('liwei----------------- _setupServiceStateListeners 2'); // 当经典蓝牙设备连接成功,尝试进行Companion关联 _handleDeviceConnected(); } @@ -202,15 +200,12 @@ class PairingController extends GetxController { /// 处理设备连接成功事件 Future _handleDeviceConnected() async { - Logger.info('liwei----------------- _handleDeviceConnected 1'); if (!isDeviceConnected || _isNavigatingToHome) return; - Logger.info('liwei----------------- _handleDeviceConnected 2'); // 如果已经完成关联,直接跳转 if (_companionAssociated.value) { _autoNavigateToHome(); return; } - Logger.info('liwei----------------- _handleDeviceConnected 3'); // 启动关联流程 _startCompanionAssociation(); } @@ -283,7 +278,6 @@ class PairingController extends GetxController { /// 启动Companion设备关联流程 Future _startCompanionAssociation() async { - Logger.info('liwei----------------- _startCompanionAssociation 1'); // 如果不是Android平台,已经跳转到首页,已经完成关联或正在进行关联,直接返回 if (!Platform.isAndroid || _isNavigatingToHome || @@ -291,7 +285,6 @@ class PairingController extends GetxController { _isAssociating) { return; } - Logger.info('liwei----------------- _startCompanionAssociation 2'); // 设置关联状态标志 _isAssociating = true; _updateStatusDescription(); @@ -305,7 +298,6 @@ class PairingController extends GetxController { _autoNavigateToHome(); return; } - Logger.info('liwei----------------- _startCompanionAssociation 3'); // 检查是否已有配对的MAC地址 final pairedMac = await _bleService.getPairedMacAddress(); if (pairedMac != null) { @@ -314,6 +306,7 @@ class PairingController extends GetxController { Logger.info('已关联的Companion设备与当前连接的设备一致'); _companionAssociated.value = true; await _bleService.registerBackgroundScan(); + // 调试情况下注释 保证正常能启动服务 // _autoNavigateToHome(); // return; } else { @@ -322,7 +315,6 @@ class PairingController extends GetxController { await _bleService.clearAssociations(); } } - Logger.info('liwei----------------- _startCompanionAssociation 4'); // 开始关联流程 if (currentDevice != null) { Logger.info('开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}'); @@ -331,7 +323,8 @@ class PairingController extends GetxController { deviceNamePattern: currentDevice!.name, macAddress: currentDevice!.address, ); - success = true; + Logger.info('Companion设备关联结果: $success'); + success = true; //李伟测试用,去掉关联流程 if (success) { Logger.info('Companion设备关联成功'); _companionAssociated.value = true; @@ -339,7 +332,6 @@ class PairingController extends GetxController { // 注册后台扫描,确保应用被杀死后仍能被唤醒 await _bleService.registerBackgroundScan( macAddress: currentDevice!.address); - Logger.info('liwei----------------- _startCompanionAssociation 5'); // 启动BLE后台服务(可选) await _startAgentService(); From 04857bb3cb63039174967f10e8f239f69b520b75 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 12 May 2025 15:20:02 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=80=B3=E6=9C=BA?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E5=BC=80=E5=85=B3=20=E6=96=B9=E4=BE=BF?= =?UTF-8?q?=E8=B0=83=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/pairing_controller.dart | 21 +++-- .../music_service/android/build.gradle | 1 + .../android/src/main/AndroidManifest.xml | 9 +- .../music_service/MusicService.kt | 87 ++++++++++++++++--- 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 44a30fa2c..d83d043dd 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -15,6 +15,8 @@ import 'package:ble_service/ble_service.dart'; class PairingController extends GetxController { static PairingController get to => Get.find(); + //是否需要连接耳机 + final isneedearphone = false; // 状态标志 final isLoading = false.obs; @@ -196,13 +198,17 @@ class PairingController extends GetxController { } _updateStatusDescription(); }); + if (!isneedearphone) { + //不需要耳机 + _handleDeviceConnected(); + } } /// 处理设备连接成功事件 Future _handleDeviceConnected() async { - if (!isDeviceConnected || _isNavigatingToHome) return; + if ((!isDeviceConnected && isneedearphone) || _isNavigatingToHome) return; // 如果已经完成关联,直接跳转 - if (_companionAssociated.value) { + if (isneedearphone && _companionAssociated.value) { _autoNavigateToHome(); return; } @@ -306,7 +312,6 @@ class PairingController extends GetxController { Logger.info('已关联的Companion设备与当前连接的设备一致'); _companionAssociated.value = true; await _bleService.registerBackgroundScan(); - // 调试情况下注释 保证正常能启动服务 // _autoNavigateToHome(); // return; } else { @@ -323,8 +328,6 @@ class PairingController extends GetxController { deviceNamePattern: currentDevice!.name, macAddress: currentDevice!.address, ); - Logger.info('Companion设备关联结果: $success'); - success = true; //李伟测试用,去掉关联流程 if (success) { Logger.info('Companion设备关联成功'); _companionAssociated.value = true; @@ -342,6 +345,12 @@ class PairingController extends GetxController { // 关联失败,但仍然可以跳转到主页(用户体验考虑) _autoNavigateToHome(); } + } else if (!isneedearphone) { + // 启动BLE后台服务(可选) + await _startAgentService(); + + // 关联和后台扫描都成功后,再跳转 + _autoNavigateToHome(); } } catch (e) { Logger.error('Companion设备关联过程发生错误: $e'); @@ -382,7 +391,7 @@ class PairingController extends GetxController { /// 启动BLE后台服务 Future _startAgentService() async { try { - if (!isDeviceConnected) return; + if (isneedearphone && !isDeviceConnected) return; // 启动BLE服务,无需传递配置参数,将从.env读取 final result = await BleManager.to.startAgentService(); diff --git a/local_plugins/music_service/android/build.gradle b/local_plugins/music_service/android/build.gradle index 8c1602312..3a98cc976 100644 --- a/local_plugins/music_service/android/build.gradle +++ b/local_plugins/music_service/android/build.gradle @@ -67,4 +67,5 @@ android { dependencies { implementation project(':deepvoice_utils') + implementation("androidx.media:media:1.6.0") } diff --git a/local_plugins/music_service/android/src/main/AndroidManifest.xml b/local_plugins/music_service/android/src/main/AndroidManifest.xml index bacfd3dee..334dcc16a 100644 --- a/local_plugins/music_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/music_service/android/src/main/AndroidManifest.xml @@ -3,7 +3,6 @@ - @@ -13,5 +12,13 @@ android:exported="false" android:enabled="true" android:foregroundServiceType="mediaPlayback" /> + + + + + + diff --git a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt index 0e3d34c3f..fd09c5a7c 100644 --- a/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt +++ b/local_plugins/music_service/android/src/main/kotlin/com/yunqiinnovation/music_service/MusicService.kt @@ -9,6 +9,8 @@ import android.os.Build import android.os.IBinder import android.util.Log import androidx.core.app.NotificationCompat +import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat import com.yunqiinnovation.deepvoice_utils.FileLogger import java.util.concurrent.atomic.AtomicBoolean @@ -28,7 +30,7 @@ class MusicService : Service() { fun isPlaying(): Boolean = currentStatus fun getCurrentUrl(): String? = currentUrlInternal } - + private lateinit var mediaSession: MediaSessionCompat private var mediaPlayer: MediaPlayer? = null private val playlist = mutableListOf() // 可选:初始化为预置歌曲 private var currentIndex = 0 @@ -38,9 +40,38 @@ class MusicService : Service() { FileLogger.init(this) FileLogger.i(TAG, "MusicService 正在创建...") isRunning.set(true) - Log.i(TAG, "MusicService Created") + mediaSession = MediaSessionCompat(this, TAG).apply { + setCallback(object : MediaSessionCompat.Callback() { + override fun onPlay() { + resume() + } + + override fun onPause() { + pause() + } + + override fun onSkipToNext() { + if (currentIndex + 1 < playlist.size) { + currentIndex++ + play(playlist[currentIndex]) + } + } + + override fun onSkipToPrevious() { + if (currentIndex - 1 >= 0) { + currentIndex-- + play(playlist[currentIndex]) + } + } + + override fun onStop() { + stopSelf() + } + }) + isActive = true + } startForeground(NOTIFICATION_ID, createNotification("等待播放...")) - isRunning.set(true) + Log.i(TAG, "MusicService Created") } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -173,15 +204,14 @@ class MusicService : Service() { manager.createNotificationChannel(channel) } - val intent = packageManager.getLaunchIntentForPackage(packageName)?.apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP - } - val pendingIntent = intent?.let { - PendingIntent.getActivity( - this, 0, it, - PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT - ) - } + // 修复 pendingIntent:打开 App 主 Activity + val activityIntent = packageManager.getLaunchIntentForPackage(packageName) + val pendingIntent = PendingIntent.getActivity( + this, + 0, + activityIntent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("DeepSound") @@ -189,6 +219,39 @@ class MusicService : Service() { .setSmallIcon(android.R.drawable.ic_media_play) .setContentIntent(pendingIntent) .setOngoing(true) + .setStyle( + androidx.media.app.NotificationCompat.MediaStyle() + .setMediaSession(mediaSession.sessionToken) + .setShowActionsInCompactView(0, 1, 2) + ) + .addAction( + NotificationCompat.Action( + android.R.drawable.ic_media_previous, "上一曲", + androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent( + this, + PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS + ) + ) + ) + .addAction( + NotificationCompat.Action( + if (currentStatus) android.R.drawable.ic_media_pause else android.R.drawable.ic_media_play, + if (currentStatus) "暂停" else "播放", + androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent( + this, + PlaybackStateCompat.ACTION_PLAY_PAUSE + ) + ) + ) + .addAction( + NotificationCompat.Action( + android.R.drawable.ic_media_next, "下一曲", + androidx.media.session.MediaButtonReceiver.buildMediaButtonPendingIntent( + this, + PlaybackStateCompat.ACTION_SKIP_TO_NEXT + ) + ) + ) .build() } From e51299168bd5c92b468f12c741d113546f2c95d3 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 12 May 2025 16:59:55 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=A4=A9=E6=B0=94?= =?UTF-8?q?=E9=A2=84=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/views/message_bubble.dart | 115 +++++++++++++++++++- 1 file changed, 110 insertions(+), 5 deletions(-) diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index fa0c81478..0e7be74b9 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -764,7 +764,7 @@ class MessageBubble extends GetView { ), ], ), - Icon( + const Icon( Icons.wb_sunny, size: 48, color: Colors.white, @@ -777,7 +777,7 @@ class MessageBubble extends GetView { color: Colors.white.withOpacity(0.2), ), SizedBox(height: 16), - Text( + const Text( '未来预报', style: TextStyle( fontSize: 14, @@ -803,11 +803,15 @@ class MessageBubble extends GetView { (index) { String dateStr = days[index]['date']; DateTime? date = DateUtil.getDateTime(dateStr); - String weekday = DateUtil.getWeekday(date); + IconData weatherIcon = + getWeatherIcon(days[index]['weatherCondition']); + String weekday = DateUtil.getWeekday(date, + languageCode: "zh", short: true); + String temp = "${days[index]['temperature']}°"; return _buildForecastDay( weekday, - Icons.wb_sunny, - days[index]['temperature'], + weatherIcon, + temp, ); }, ), @@ -915,6 +919,107 @@ class MessageBubble extends GetView { ], ); } + + /// 获取天气图标 + IconData getWeatherIcon(String description) { + description = description.trim(); + + if (["晴", "少云", "晴间多云"].contains(description)) { + return Icons.wb_sunny; + } + + if (["多云", "阴"].contains(description)) { + return Icons.cloud; + } + + if (description.contains("雾") || + ["雾", "浓雾", "强浓雾", "轻雾", "大雾", "特强浓雾", "霾", "中度霾", "重度霾", "严重霾"] + .contains(description)) { + return Icons.blur_on; + } + + if (description.contains("风") || + [ + "有风", + "平静", + "微风", + "和风", + "清风", + "强风/劲风", + "疾风", + "大风", + "烈风", + "风暴", + "狂爆风", + "飓风", + "热带风暴" + ].contains(description)) { + return Icons.air; + } + + if (description.contains("雨") || + [ + "阵雨", + "雷阵雨", + "雷阵雨并伴有冰雹", + "小雨", + "中雨", + "大雨", + "暴雨", + "大暴雨", + "特大暴雨", + "强阵雨", + "强雷阵雨", + "极端降雨", + "毛毛雨/细雨", + "雨", + "小雨-中雨", + "中雨-大雨", + "大雨-暴雨", + "暴雨-大暴雨", + "大暴雨-特大暴雨" + ].contains(description)) { + return Icons.grain; // 或使用 community icon 包:weather_icons 中的 rain + } + + if (description.contains("雪") || + [ + "雨雪天气", + "雨夹雪", + "阵雨夹雪", + "冻雨", + "雪", + "阵雪", + "小雪", + "中雪", + "大雪", + "暴雪", + "小雪-中雪", + "中雪-大雪", + "大雪-暴雪" + ].contains(description)) { + return Icons.ac_unit; + } + + if (["浮尘", "扬沙", "沙尘暴", "强沙尘暴"].contains(description)) { + return Icons.filter_drama; + } + + if (["龙卷风"].contains(description)) { + return Icons.waves; + } + + if (["热"].contains(description)) { + return Icons.local_fire_department; + } + + if (["冷"].contains(description)) { + return Icons.ac_unit; + } + + // 默认或未知天气 + return Icons.help_outline; + } } /// 动画点组件