diff --git a/lib/data/services/bluetooth_manager.dart b/lib/data/services/bluetooth_manager.dart index 4a3ccde15..713bcb754 100644 --- a/lib/data/services/bluetooth_manager.dart +++ b/lib/data/services/bluetooth_manager.dart @@ -9,59 +9,58 @@ export 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice; /// 蓝牙服务 - 管理经典蓝牙服务 class BluetoothManager extends GetxService { static BluetoothManager get to => Get.find(); - + // 蓝牙状态 final _bluetoothState = 'unknown'.obs; String get bluetoothState => _bluetoothState.value; RxString get bluetoothStateRx => _bluetoothState; - + // 当前经典蓝牙设备 final Rx currentDeviceRx = Rx(null); BluetoothDevice? get currentDevice => currentDeviceRx.value; - + // 订阅 StreamSubscription? _bluetoothSubscription; - /// 初始化 @override void onInit() { super.onInit(); Logger.info('蓝牙服务初始化'); - + // 设置蓝牙事件监听 _setupBluetoothListener(); - + // 主动检查当前已连接的设备和蓝牙状态 Future.delayed(Duration.zero, () async { Logger.info('检查初始蓝牙连接状态'); - + // 先检查蓝牙是否开启 await isBluetoothEnabled(); - + // 然后刷新设备连接 await refreshDeviceConnection(); }); } - + /// 释放资源 @override void onClose() { _bluetoothSubscription?.cancel(); super.onClose(); } - + /// 设置经典蓝牙事件监听 void _setupBluetoothListener() { _bluetoothSubscription?.cancel(); - + try { _bluetoothSubscription = ClassicBluetooth.events.listen( (event) { if (event.type == 'stateChanged' && event.state != null) { _bluetoothState.value = event.state!; Logger.info('蓝牙状态变更: ${event.state}'); - + // 如果蓝牙关闭,清除设备 if (event.state == 'off') { currentDeviceRx.value = null; @@ -69,17 +68,19 @@ class BluetoothManager extends GetxService { } else if (event.type == 'deviceConnected' && event.device != null) { // 直接从事件中获取设备信息 final newDevice = event.device!; - + // 更新当前设备 currentDeviceRx.value = newDevice; - + Logger.info('经典蓝牙设备已连接: ${newDevice.name} (${newDevice.address})'); - } else if (event.type == 'deviceDisconnected' && event.device != null) { + } else if (event.type == 'deviceDisconnected' && + event.device != null) { // 直接从事件中获取设备信息 final disconnectedDevice = event.device!; - - Logger.info('经典蓝牙设备已断开: ${disconnectedDevice.name} (${disconnectedDevice.address})'); - + + Logger.info( + '经典蓝牙设备已断开: ${disconnectedDevice.name} (${disconnectedDevice.address})'); + // 如果断开的是当前设备,清除当前设备 if (currentDeviceRx.value?.address == disconnectedDevice.address) { currentDeviceRx.value = null; @@ -95,34 +96,36 @@ class BluetoothManager extends GetxService { Logger.error('设置蓝牙事件监听器失败: ${e.toString()}'); } } - + /// 刷新设备连接状态 Future refreshDeviceConnection() async { try { - - // 如果BLE未连接,则检查经典蓝牙连接 final devices = await ClassicBluetooth.getConnectedDevices(); - + if (devices.isNotEmpty) { + for (var device in devices) { + if (device.isActive) { + // 如果有活动设备,直接使用它 + currentDeviceRx.value = device; + Logger.info('已连接经典蓝牙设备: ${device.name} (${device.address})'); + return; + } + } final newDevice = devices.first; - - // 更新当前设备 currentDeviceRx.value = newDevice; - Logger.info('已连接经典蓝牙设备: ${newDevice.name} (${newDevice.address})'); return; } - + // 没有任何连接 currentDeviceRx.value = null; Logger.info('未找到已连接的蓝牙设备'); - } catch (e) { Logger.error('刷新设备连接失败: ${e.toString()}'); } } - + /// 检查蓝牙是否已启用 Future isBluetoothEnabled() async { try { @@ -134,5 +137,4 @@ class BluetoothManager extends GetxService { return false; } } - -} \ No newline at end of file +} diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 45912c231..a68b4b62b 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -490,9 +490,11 @@ class PairingController extends GetxController { } else { final authcode_open = AppConfig.env("APP_AUTHCODE_OPEN") ?? ""; if (authcode_open == "true") { + Logger.i(_tag, '进入手动验证流: ${device.name} (${device.address})'); //验证失败 进入手动授权 _transition(PairingState.manualbinding); } else { + Logger.i(_tag, '设备不支持: ${device.name} (${device.address})'); _setError(ErrorType.deviceNotMatch, 'pleaseConnectDeepSoundDevice'.tr); //请连接DeepSound设备 } @@ -610,34 +612,42 @@ class PairingController extends GetxController { Future authbindingDevice(BluetoothDevice device) async { // 检查设备名称是否包含 DeepSound 或 DeapSound final deviceName = device.name; - + DBProduct? product; // 检查是否已有BLE绑定的设备 - final product = AppConfig.config.products - .firstWhere((d) => d.devicename == device.name); + /// 检查是否已有BLE绑定的设备 + if (AppConfig.config.products.isNotEmpty) { + final match = AppConfig.config.products.where( + (d) => d.devicename == device.name, + ); + product = match.isNotEmpty ? match.first : null; + } final names = AppConfig.allproductNames(); // 检查是否已有绑定的设备 print("device.address${device.address}"); - var existingDevice = User.instance.devices.firstWhere( - (d) => - d.devicetype == 1 && - d.devicename == device.name && - d.devicemac == device.address, - orElse: () => UserDevice( - id: -1, - uid: '', - devicetype: -1, - devicename: '', - devicemac: '', - license: ''), - ); - if (existingDevice.id != -1) { - // 已经绑定 - return 9; + if (User.instance.devices.isNotEmpty) { + final existingDevice = User.instance.devices.firstWhere( + (d) => + d.devicetype == 1 && + d.devicename == device.name && + d.devicemac == device.address, + orElse: () => UserDevice( + id: -1, + uid: '', + devicetype: -1, + devicename: '', + devicemac: '', + license: ''), + ); + if (existingDevice.id != -1) { + // 已经绑定 + return 9; + } } + if (Platform.isAndroid) { // 检查是否已有BLE绑定的设备 - existingDevice = User.instance.devices.firstWhere( + final existingDevice = User.instance.devices.firstWhere( (d) => d.devicetype == 2 && d.devicename == device.name && @@ -751,7 +761,7 @@ class PairingController extends GetxController { } await Future.delayed(Duration(seconds: 1)); } - if (product.devicetype == 2) { + if (product != null && product.devicetype == 2) { //ble设备 必须要扫描到结果 return 0; } else { diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift index b42708dca..3f0caeab3 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift @@ -294,7 +294,7 @@ public class MCPSubClient { isConnectedFlag = false // 启动重连逻辑 - startReconnection() + // startReconnection() } } } diff --git a/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt b/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt index 254ca1273..3f5ca6e52 100644 --- a/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt +++ b/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt @@ -174,11 +174,16 @@ class ClassicBluetoothHelper(private val context: Context) { // 获取已连接设备 val connectedDevices = proxy.connectedDevices + // 获取当前活跃设备 + val activeDevice = getActiveDevice(proxy, profileId) + // 转换为设备信息列表 val devicesList = connectedDevices.map { device -> + val isActive = activeDevice?.address == device.address mapOf( "name" to (device.name ?: "未知设备"), - "address" to device.address + "address" to device.address, + "isActive" to isActive.toString() ) } @@ -186,6 +191,54 @@ class ClassicBluetoothHelper(private val context: Context) { callback.onSuccess(devicesList) } + /** + * 获取当前活跃的蓝牙设备 + */ + private fun getActiveDevice(proxy: BluetoothProfile, profileId: Int): BluetoothDevice? { + return try { + when (profileId) { + BluetoothProfile.A2DP -> { + // 使用反射调用 getActiveDevice 方法 + val method = proxy.javaClass.getDeclaredMethod("getActiveDevice") + method.isAccessible = true + method.invoke(proxy) as? BluetoothDevice + } + BluetoothProfile.HEADSET -> { + // 对于 HEADSET 配置文件,也尝试获取活跃设备 + val method = proxy.javaClass.getDeclaredMethod("getActiveDevice") + method.isAccessible = true + method.invoke(proxy) as? BluetoothDevice + } + else -> null + } + } catch (e: Exception) { + Log.w(TAG, "无法获取活跃设备: ${e.message}") + // 如果无法获取活跃设备,可以尝试其他方法 + // 比如检查连接状态或音频焦点 + getActiveDeviceFallback(proxy) + } + } + + /** + * 获取活跃设备的备用方法 + */ + private fun getActiveDeviceFallback(proxy: BluetoothProfile): BluetoothDevice? { + return try { + val connectedDevices = proxy.connectedDevices + // 如果只有一个连接的设备,则认为它是活跃的 + if (connectedDevices.size == 1) { + connectedDevices.first() + } else { + // 如果有多个设备,可以通过其他方式判断 + // 这里简单返回第一个,实际应用中可能需要更复杂的逻辑 + connectedDevices.firstOrNull() + } + } catch (e: Exception) { + Log.w(TAG, "备用方法获取活跃设备失败: ${e.message}") + null + } + } + /** * 开始监听蓝牙状态变化 * @param callback 蓝牙状态回调接口 @@ -361,4 +414,4 @@ class ClassicBluetoothHelper(private val context: Context) { TURNING_ON, TURNING_OFF } -} \ No newline at end of file +} \ No newline at end of file diff --git a/local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift b/local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift index 1e1ecc147..9d0b31890 100644 --- a/local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift +++ b/local_plugins/classic_bluetooth/ios/Classes/ClassicBluetoothHelper.swift @@ -75,7 +75,8 @@ class ClassicBluetoothHelper: NSObject { for output in outputs where isBluetoothOutput(output.portType) { let device: [String: String] = [ "name": output.portName, - "address": cleanDeviceAddress(output.uid) + "address": cleanDeviceAddress(output.uid), + "isActive": "true" // 在 currentRoute.outputs 中的蓝牙设备都是活跃的 ] result.append(device) } diff --git a/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart b/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart index eb66ae5b8..2deef9820 100644 --- a/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart +++ b/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart @@ -3,34 +3,35 @@ import 'package:flutter/services.dart'; /// 经典蓝牙插件 class ClassicBluetooth { - static const MethodChannel _methodChannel = + static const MethodChannel _methodChannel = MethodChannel('com.yunqiinnovation.classic_bluetooth/methods'); - - static const EventChannel _eventChannel = + + static const EventChannel _eventChannel = EventChannel('com.yunqiinnovation.classic_bluetooth/events'); - + static Stream? _eventStream; - + /// 获取已连接的蓝牙设备 static Future> getConnectedDevices() async { - final List devices = await _methodChannel.invokeMethod('getConnectedDevices'); + final List devices = + await _methodChannel.invokeMethod('getConnectedDevices'); return devices.map((device) => BluetoothDevice.fromJson(device)).toList(); } - + /// 检查蓝牙是否已启用 static Future isBluetoothEnabled() async { return await _methodChannel.invokeMethod('isBluetoothEnabled'); } - + /// 监听蓝牙事件(状态变化、设备连接/断开) static Stream get events { _eventStream ??= _eventChannel .receiveBroadcastStream() .map((dynamic event) => BluetoothEvent.fromJson(event)) .handleError((error) { - print('[ClassicBluetooth] 事件流错误: $error'); - }); - + print('[ClassicBluetooth] 事件流错误: $error'); + }); + return _eventStream!; } } @@ -39,61 +40,67 @@ class ClassicBluetooth { class BluetoothDevice { final String name; final String address; - + final bool isActive; + BluetoothDevice({ required this.name, required this.address, + this.isActive = false, }); - + factory BluetoothDevice.fromJson(Map json) { return BluetoothDevice( name: json['name'] as String, address: json['address'] as String, + isActive: json['isActive'] == 'true', ); } - + Map toJson() { return { 'name': name, 'address': address, + 'isActive': isActive, }; } - + @override - String toString() => 'BluetoothDevice(name: $name, address: $address)'; + String toString() => + 'BluetoothDevice(name: $name, address: $address, isActive: $isActive)'; } /// 简化的蓝牙事件类 class BluetoothEvent { /// 事件类型:stateChanged, deviceConnected, deviceDisconnected final String type; - + /// 蓝牙状态: on, off, turningOn, turningOff, unknown final String? state; - + /// 相关的蓝牙设备 final BluetoothDevice? device; - + /// 事件发生时间 final DateTime timestamp; - + BluetoothEvent({ required this.type, this.state, this.device, required this.timestamp, }); - + factory BluetoothEvent.fromJson(Map json) { final String type = json['type'] as String; - final timestamp = DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int); - + final timestamp = + DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int); + // 处理设备相关事件 BluetoothDevice? device; if (json.containsKey('device')) { device = BluetoothDevice.fromJson(json['device']); } - + return BluetoothEvent( type: type, state: json['state'] as String?, @@ -101,7 +108,7 @@ class BluetoothEvent { timestamp: timestamp, ); } - + @override String toString() { if (type == 'stateChanged') { @@ -112,4 +119,4 @@ class BluetoothEvent { return 'BluetoothEvent(type: $type)'; } } -} \ No newline at end of file +} diff --git a/macos/Podfile.lock b/macos/Podfile.lock index ae0a8cb45..91e000f41 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -14,6 +14,8 @@ PODS: - FlutterMacOS (1.0.0) - record_darwin (1.0.0): - FlutterMacOS + - sign_in_with_apple (0.0.1): + - FlutterMacOS - window_size (0.0.2): - FlutterMacOS @@ -26,6 +28,7 @@ DEPENDENCIES: - flutter_tts (from `Flutter/ephemeral/.symlinks/plugins/flutter_tts/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - record_darwin (from `Flutter/ephemeral/.symlinks/plugins/record_darwin/macos`) + - sign_in_with_apple (from `Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos`) - window_size (from `Flutter/ephemeral/.symlinks/plugins/window_size/macos`) EXTERNAL SOURCES: @@ -45,6 +48,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral record_darwin: :path: Flutter/ephemeral/.symlinks/plugins/record_darwin/macos + sign_in_with_apple: + :path: Flutter/ephemeral/.symlinks/plugins/sign_in_with_apple/macos window_size: :path: Flutter/ephemeral/.symlinks/plugins/window_size/macos @@ -57,6 +62,7 @@ SPEC CHECKSUMS: flutter_tts: ae915565cc6948444b513acc8ee021993281e027 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 record_darwin: 30509266ae213af8afdb09a8ae7467cb64c1377e + sign_in_with_apple: 6673c03c9e3643f6c8d33601943fbfa9ae99f94e window_size: 4bd15034e6e3d0720fd77928a7c42e5492cfece9 PODFILE CHECKSUM: 8c4442624fc136b0afb378a2502958e01c05d073 diff --git a/pubspec.yaml b/pubspec.yaml index 4e7144562..64a6f01ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: voitrans description: "Voitrans - AI Voice Assistant." publish_to: "none" -version: 1.0.3+9 +version: 1.0.3+11 environment: sdk: ">=3.3.0 <4.0.0"