From 947321c84994b5f01fa591666e90248421c835a0 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Sat, 3 May 2025 12:41:12 +0100 Subject: [PATCH] add --- android/app/build.gradle.kts | 5 +- .../yunqiinnovation/deepsound/MainActivity.kt | 6 +- android/app/src/profile/AndroidManifest.xml | 7 - android/settings.gradle.kts | 4 +- lib/core/bindings/initial_binding.dart | 5 - .../services/classic_bluetooth_service.dart | 243 ------------------ .../home/controllers/home_controller.dart | 112 +++++--- .../controllers/pairing_controller.dart | 194 ++++++++------ .../android/build.gradle.kts | 37 +++ .../android/settings.gradle.kts | 1 + .../android/src/main/AndroidManifest.xml | 11 + .../ClassicBluetoothHelper.kt | 23 +- .../ClassicBluetoothPlugin.kt | 13 +- .../lib/classic_bluetooth.dart | 115 +++++++++ local_plugins/classic_bluetooth/pubspec.yaml | 20 ++ pubspec.yaml | 2 + 16 files changed, 385 insertions(+), 413 deletions(-) delete mode 100644 android/app/src/profile/AndroidManifest.xml delete mode 100644 lib/data/services/classic_bluetooth_service.dart create mode 100644 local_plugins/classic_bluetooth/android/build.gradle.kts create mode 100644 local_plugins/classic_bluetooth/android/settings.gradle.kts create mode 100644 local_plugins/classic_bluetooth/android/src/main/AndroidManifest.xml rename {android/app/src/main/kotlin/com/yunqiinnovation/deepsound => local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth}/ClassicBluetoothHelper.kt (95%) rename {android/app/src/main/kotlin/com/yunqiinnovation/deepsound => local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth}/ClassicBluetoothPlugin.kt (95%) create mode 100644 local_plugins/classic_bluetooth/lib/classic_bluetooth.dart create mode 100644 local_plugins/classic_bluetooth/pubspec.yaml diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 9853fa6a0..8c29dfd84 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -124,12 +124,9 @@ dependencies { // 添加本地插件模块依赖 - implementation(project(":azure_speech")) - implementation(project(":open_ai_service")) - implementation(project(":chat_storage")) // 添加新创建的SQLite聊天存储插件 - implementation(project(":jieli")) // 添加新创建的SQLite聊天存储插件 implementation(project(":spotify_sdk")) implementation(project(":deepvoice_utils")) + implementation(project(":ble_service")) // 添加OkHttp依赖 implementation("com.squareup.okhttp3:okhttp:4.9.3") diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt index 867a5215d..872981220 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MainActivity.kt @@ -52,7 +52,7 @@ class MainActivity: FlutterActivity() { FileLogger.init(applicationContext) // 初始化BLE后台唤醒功能 - initBleBackgroundScan() + // initBleBackgroundScan() FileLogger.d(TAG, "初始化完成") } @@ -69,13 +69,9 @@ class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) GeneratedPluginRegistrant.registerWith(flutterEngine) - - // 注册经典蓝牙插件 - flutterEngine.plugins.add(ClassicBluetoothPlugin()) } override fun onDestroy() { - super.onDestroy() } diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml deleted file mode 100644 index 399f6981d..000000000 --- a/android/app/src/profile/AndroidManifest.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 92eb380f8..5f9c97be6 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -38,6 +38,7 @@ include(":jieli") include(":spotify_sdk") include(":ble_service") include(":deepvoice_utils") +include(":classic_bluetooth") // 设置azure_speech项目的路径 project(":azure_speech").projectDir = file("../local_plugins/azure_speech/android") project(":open_ai_service").projectDir = file("../local_plugins/open_ai_service/android") @@ -45,4 +46,5 @@ project(":chat_storage").projectDir = file("../local_plugins/chat_storage/androi project(":jieli").projectDir = file("../local_plugins/jieli/android") project(":spotify_sdk").projectDir = file("../local_plugins/spotify_sdk/android") project(":ble_service").projectDir = file("../local_plugins/ble_service/android") -project(":deepvoice_utils").projectDir = file("../local_plugins/deepvoice_utils/android") \ No newline at end of file +project(":deepvoice_utils").projectDir = file("../local_plugins/deepvoice_utils/android") +project(":classic_bluetooth").projectDir = file("../local_plugins/classic_bluetooth/android") \ No newline at end of file diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index a3a096b3a..091f4e5e4 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -4,10 +4,7 @@ import '../../data/services/chat_history_service.dart'; import '../../data/services/volcano_translation_service.dart'; import '../../data/services/language_manager.dart'; import '../../data/services/volcano_ai_service.dart'; -import '../../data/services/classic_bluetooth_service.dart'; -import '../../data/services/bluetooth_media_button_service.dart'; import '../../core/utils/logger.dart'; -import '../../data/services/voice_interaction_service.dart'; import '../../data/services/open_ai_service_adapter.dart'; /// 初始绑定,用于管理全局依赖 class InitialBinding extends Bindings { @@ -39,8 +36,6 @@ class InitialBinding extends Bindings { // 火山AI服务 Get.lazyPut(() => VolcanoAIService(), fenix: true); - // 经典蓝牙服务 - 立即初始化以监听蓝牙状态 - Get.lazyPut(() =>ClassicBluetoothService(), fenix: true); // 蓝牙媒体按钮服务 // Get.lazyPut( diff --git a/lib/data/services/classic_bluetooth_service.dart b/lib/data/services/classic_bluetooth_service.dart deleted file mode 100644 index 09319daea..000000000 --- a/lib/data/services/classic_bluetooth_service.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'dart:async'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import 'package:permission_handler/permission_handler.dart'; -import '../../core/utils/logger.dart'; -import 'dart:io'; - -/// 蓝牙设备信息 -class BluetoothDevice { - final String name; - final String address; - final String type; - - BluetoothDevice({ - required this.name, - required this.address, - required this.type - }); - - factory BluetoothDevice.fromMap(Map map) { - return BluetoothDevice( - name: map['name'] ?? '未知设备', - address: map['address'] ?? '', - type: map['type'] ?? 'unknown', - ); - } - - @override - String toString() => 'BluetoothDevice(name: $name, address: $address, type: $type)'; -} - -/// 经典蓝牙服务 -/// 负责经典蓝牙设备的连接状态管理 -class ClassicBluetoothService extends GetxService { - static ClassicBluetoothService get to => Get.find(); - - // 通道名称 - final _methodChannel = const MethodChannel('com.yunqiinnovation.classic_bluetooth/methods'); - final _eventChannel = const EventChannel('com.yunqiinnovation.classic_bluetooth/events'); - - /// 当前连接的设备 - final currentDevice = Rx(null); - - /// 已连接耳机设备列表 - final connectedDevices = [].obs; - - /// 蓝牙事件监听 - StreamSubscription? _eventSubscription; - - /// 蓝牙开关状态 - final bluetoothState = 'unknown'.obs; - - /// 蓝牙是否可用 - bool get isBluetoothEnabled => bluetoothState.value == 'on'; - - /// 服务是否已初始化 - bool _isInitialized = false; - - /// 初始化蓝牙服务 - Future initialize() async { - if (_isInitialized) { - Logger.info('蓝牙服务已经初始化,跳过'); - return true; - } - - try { - Logger.info('正在初始化蓝牙服务...'); - - // 检查蓝牙是否启用 - final bool isEnabled = await _methodChannel.invokeMethod('isBluetoothEnabled') ?? false; - bluetoothState.value = isEnabled ? 'on' : 'off'; - Logger.info('初始蓝牙状态: ${bluetoothState.value}'); - - // 设置事件监听 - _setupEventListener(); - - _isInitialized = true; - Logger.info('蓝牙服务初始化成功'); - - // 如果蓝牙已启用,自动获取已连接设备 - if (isEnabled) { - await refreshConnectedDevices(); - } - - return true; - } catch (e) { - Logger.error('初始化蓝牙服务失败: $e'); - return false; - } - } - - /// 设置事件监听 - void _setupEventListener() { - _eventSubscription?.cancel(); - _eventSubscription = _eventChannel.receiveBroadcastStream().listen((event) { - if (event is Map) { - Logger.info('收到蓝牙事件: $event'); - - // 根据事件类型处理 - final type = event['type'] as String?; - if (type == 'stateChanged') { - final state = event['state'] as String?; - if (state != null) { - _handleBluetoothStateChange(state); - } - } else if (type == 'deviceConnected') { - final deviceMap = event['device'] as Map?; - if (deviceMap != null) { - _handleDeviceConnected(deviceMap); - } - } else if (type == 'deviceDisconnected') { - final deviceMap = event['device'] as Map?; - if (deviceMap != null) { - _handleDeviceDisconnected(deviceMap); - } - } - } - }, onError: (e) { - Logger.error('蓝牙事件监听错误: $e'); - }); - } - - - /// 刷新已连接的蓝牙设备 - Future refreshConnectedDevices() async { - // 确保服务已初始化 - if (!_isInitialized && !(await initialize())) { - return false; - } - - // 如果蓝牙未启用,无法获取设备 - if (!isBluetoothEnabled) { - Logger.info('蓝牙未启用,无法获取设备'); - return false; - } - - try { - // 获取已连接的设备 - final devices = await getConnectedDevices(); - - // 更新设备列表 - connectedDevices.value = devices; - - // 如果列表不为空且当前无选定设备,选择第一个 - if (devices.isNotEmpty && currentDevice.value == null) { - currentDevice.value = devices.first; - } - - return devices.isNotEmpty; - } catch (e) { - Logger.error('刷新已连接设备失败: $e'); - return false; - } - } - - /// 获取已连接的蓝牙设备 - Future> getConnectedDevices() async { - try { - // 确保服务已初始化 - if (!_isInitialized && !(await initialize())) { - return []; - } - - final result = await _methodChannel.invokeMethod('getConnectedDevices'); - - if (result is List) { - return result.map((device) => BluetoothDevice.fromMap(device)).toList(); - } - - return []; - } catch (e) { - Logger.error('获取已连接设备失败: $e'); - return []; - } - } - - /// 检查蓝牙是否已开启 - Future checkBluetoothEnabled() async { - try { - return await _methodChannel.invokeMethod('isBluetoothEnabled') ?? false; - } catch (e) { - Logger.error('检查蓝牙状态失败: $e'); - return false; - } - } - - /// 处理设备连接事件 - void _handleDeviceConnected(Map deviceMap) { - final device = BluetoothDevice.fromMap(deviceMap); - - // 更新设备列表 - if (!connectedDevices.any((d) => d.address == device.address)) { - connectedDevices.add(device); - } - - // 更新当前设备 - currentDevice.value = device; - - Logger.info('设备已连接: ${device.name}'); - } - - /// 处理设备断开事件 - void _handleDeviceDisconnected(Map deviceMap) { - final address = deviceMap['address'] as String?; - - if (address != null) { - // 更新设备列表 - connectedDevices.removeWhere((d) => d.address == address); - - // 如果是当前设备,清除当前设备 - if (currentDevice.value?.address == address) { - currentDevice.value = connectedDevices.isEmpty ? null : connectedDevices.first; - Logger.info('当前设备已断开连接'); - } - } - } - - /// 处理蓝牙状态变化 - void _handleBluetoothStateChange(String state) { - Logger.info('蓝牙状态变化: $state'); - - // 更新蓝牙状态 - bluetoothState.value = state; - - if (state == 'off') { - // 蓝牙关闭,清空设备信息 - connectedDevices.clear(); - currentDevice.value = null; - } else if (state == 'on') { - // 蓝牙重新开启,延迟一小段时间后刷新设备 - Future.delayed(const Duration(milliseconds: 1000), () { - refreshConnectedDevices(); - }); - } - } - - @override - void onClose() { - // 取消事件监听 - _eventSubscription?.cancel(); - super.onClose(); - } -} diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index db2049bc9..84358806c 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -1,10 +1,10 @@ import 'package:get/get.dart'; import 'dart:async'; import '../../../routes/app_routes.dart'; -import '../../../data/services/classic_bluetooth_service.dart'; import '../../../core/utils/logger.dart'; import 'package:flutter/material.dart'; import '../../explore/controllers/explore_controller.dart'; +import 'package:classic_bluetooth/classic_bluetooth.dart'; // 连接状态枚举 enum ConnectionStatus { @@ -18,9 +18,6 @@ class HomeController extends GetxController { static HomeController get to => Get.find(); final ExploreController exploreController = Get.find(); - // 经典蓝牙服务 - final ClassicBluetoothService _bluetoothService = Get.find(); - // 连接状态 final _connectionStatus = ConnectionStatus.disconnected.obs; ConnectionStatus get connectionStatus => _connectionStatus.value; @@ -36,13 +33,18 @@ class HomeController extends GetxController { final _connectionErrorMsg = ''.obs; String get connectionErrorMsg => _connectionErrorMsg.value; - // 从蓝牙服务中直接获取的属性 - Rx get currentDevice => _bluetoothService.currentDevice; - List get connectedDevices => _bluetoothService.connectedDevices; - RxString get bluetoothState => _bluetoothService.bluetoothState; + // 蓝牙状态和设备信息 + final _bluetoothState = 'unknown'.obs; + String get bluetoothState => _bluetoothState.value; + + final Rx _currentDevice = Rx(null); + BluetoothDevice? get currentDevice => _currentDevice.value; + + // 蓝牙事件订阅 + StreamSubscription? _bluetoothSubscription; // 蓝牙是否已连接 - bool get isConnected => currentDevice.value != null; + bool get isConnected => currentDevice != null; @override void onInit() { @@ -54,22 +56,33 @@ class HomeController extends GetxController { // 电池模拟更新 _simulateBatteryUpdate(); } + + @override + void onClose() { + _bluetoothSubscription?.cancel(); + super.onClose(); + } // 初始化蓝牙 Future _initBluetooth() async { try { - // 确保蓝牙服务已初始化 - if (!(await _bluetoothService.initialize())) { - _connectionStatus.value = ConnectionStatus.failed; - _connectionErrorMsg.value = '初始化蓝牙服务失败'; + _connectionStatus.value = ConnectionStatus.connecting; + + // 检查蓝牙状态 + final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); + _bluetoothState.value = isEnabled ? 'on' : 'off'; + + if (!isEnabled) { + _connectionStatus.value = ConnectionStatus.disconnected; + _connectionErrorMsg.value = '蓝牙未开启'; return; } - // 设置监听器 - _setupBluetoothListeners(); + // 设置蓝牙事件监听 + _setupBluetoothListener(); - // 更新初始状态 - _updateConnectionStatus(); + // 获取已连接设备 + await refreshDeviceConnection(); } catch (e) { Logger.error('初始化蓝牙失败: $e'); @@ -78,29 +91,54 @@ class HomeController extends GetxController { } } - // 设置蓝牙状态监听 - void _setupBluetoothListeners() { - // 监听当前设备变化 - ever(currentDevice, (_) { - _updateConnectionStatus(); - }); - - // 监听蓝牙状态变化 - ever(bluetoothState, (_) { - _updateConnectionStatus(); + // 设置蓝牙事件监听 + void _setupBluetoothListener() { + _bluetoothSubscription?.cancel(); + + _bluetoothSubscription = ClassicBluetooth.events.listen((event) { + if (event.type == 'stateChanged' && event.state != null) { + _bluetoothState.value = event.state!; + + // 如果蓝牙关闭,清空设备信息 + if (event.state == 'off') { + _currentDevice.value = null; + _connectionErrorMsg.value = '蓝牙未开启'; + } + + _updateConnectionStatus(); + } else if (event.type == 'deviceConnected' && event.device != null) { + final newDevice = event.device!; + + // 更新当前设备 + _currentDevice.value = newDevice; + _connectionErrorMsg.value = ''; + _updateConnectionStatus(); + } else if (event.type == 'deviceDisconnected' && event.device != null) { + final disconnectedDevice = event.device!; + + // 如果断开的是当前设备,清除当前设备 + if (_currentDevice.value?.address == disconnectedDevice.address) { + _currentDevice.value = null; + _connectionErrorMsg.value = '设备已断开连接'; + } + + _updateConnectionStatus(); + } + }, onError: (error) { + Logger.error('蓝牙事件流错误: $error'); }); } // 更新连接状态 void _updateConnectionStatus() { - if (bluetoothState.value == 'off') { + if (bluetoothState == 'off') { _connectionStatus.value = ConnectionStatus.disconnected; _deviceBattery.value = null; update(); return; } - if (currentDevice.value != null) { + if (currentDevice != null) { _connectionStatus.value = ConnectionStatus.connected; } else { _connectionStatus.value = ConnectionStatus.disconnected; @@ -116,12 +154,14 @@ class HomeController extends GetxController { _connectionStatus.value = ConnectionStatus.connecting; update(); - final success = await _bluetoothService.refreshConnectedDevices(); + final devices = await ClassicBluetooth.getConnectedDevices(); - if (!success) { - _connectionErrorMsg.value = '未找到已连接的设备'; - } else { + if (devices.isNotEmpty) { + _currentDevice.value = devices.first; _connectionErrorMsg.value = ''; + } else { + _currentDevice.value = null; + _connectionErrorMsg.value = '未找到已连接的设备'; } } catch (e) { Logger.error('刷新设备连接失败: $e'); @@ -184,7 +224,7 @@ class HomeController extends GetxController { // 获取连接状态对应的文本 String getConnectionStatusText() { // 如果蓝牙关闭,优先显示蓝牙状态 - if (bluetoothState.value == 'off') { + if (bluetoothState == 'off') { return 'bluetoothOff'.tr; // 蓝牙已关闭 } @@ -203,7 +243,7 @@ class HomeController extends GetxController { // 获取设备名称 String getDeviceName() { - return currentDevice.value?.name ?? 'unknownEquipment'.tr; // 未知设备 + return currentDevice?.name ?? 'unknownEquipment'.tr; // 未知设备 } // 获取信号强度(模拟数据) @@ -226,6 +266,6 @@ class HomeController extends GetxController { // 获取设备ID String getDeviceId() { - return currentDevice.value?.address ?? 'unknown'.tr; // 未知 + return currentDevice?.address ?? 'unknown'.tr; // 未知 } } diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index a8c1798ca..9a79931be 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -3,17 +3,14 @@ import 'package:get/get.dart'; import 'dart:async'; import 'package:permission_handler/permission_handler.dart'; import '../../../routes/app_routes.dart'; -import '../../../data/services/classic_bluetooth_service.dart'; import '../../../core/utils/logger.dart'; import 'dart:io'; +import 'package:classic_bluetooth/classic_bluetooth.dart'; -/// 配对控制器,负责显示已连接经典蓝牙设备和处理基本配对流程 +/// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程 class PairingController extends GetxController { static PairingController get to => Get.find(); - // 经典蓝牙服务 - final ClassicBluetoothService _bluetoothService = Get.find(); - // 状态标志 final isLoading = false.obs; @@ -21,20 +18,22 @@ class PairingController extends GetxController { final _hasPermissions = false.obs; bool get hasPermissions => _hasPermissions.value; - // 蓝牙服务是否可用 - final _isBluetoothServiceAvailable = true.obs; - bool get isBluetoothServiceAvailable => _isBluetoothServiceAvailable.value; - + // 蓝牙状态 + final _bluetoothState = 'unknown'.obs; + String get bluetoothState => _bluetoothState.value; + // 状态描述(用于UI显示) final statusDescription = 'initializing'.tr.obs; // 是否正在跳转到主页 bool _isNavigatingToHome = false; - // 当前连接设备和设备列表(直接从服务获取) - Rx get currentDevice => _bluetoothService.currentDevice; - RxList get connectedDevices => _bluetoothService.connectedDevices; - RxString get bluetoothState => _bluetoothService.bluetoothState; + // 当前连接设备 + final Rx _currentDevice = Rx(null); + BluetoothDevice? get currentDevice => _currentDevice.value; + + // 蓝牙事件订阅 + StreamSubscription? _bluetoothSubscription; @override void onInit() { @@ -42,6 +41,12 @@ class PairingController extends GetxController { _initialize(); } + @override + void onClose() { + _bluetoothSubscription?.cancel(); + super.onClose(); + } + /// 初始化流程 Future _initialize() async { try { @@ -54,31 +59,28 @@ class PairingController extends GetxController { return; } - statusDescription.value = 'initializing'.tr; + // 监听蓝牙状态变化 + _setupBluetoothListener(); + + // 检查蓝牙状态 + statusDescription.value = 'checkingBluetoothStatus'.tr; + final isEnabled = await ClassicBluetooth.isBluetoothEnabled(); + _bluetoothState.value = isEnabled ? 'on' : 'off'; - // 初始化蓝牙服务 - if (!await _bluetoothService.initialize()) { - Logger.error('蓝牙服务初始化失败'); - statusDescription.value = 'bluetoothInitializationFailure'.tr; - _isBluetoothServiceAvailable.value = false; + if (!isEnabled) { + statusDescription.value = 'bluetoothTurnedOff'.tr; return; } - // 设置状态监听 - _setupStatusListeners(); - // 刷新已连接设备 statusDescription.value = 'searchingDevice'.tr; await refreshConnectedDevices(); } catch (e) { Logger.error('初始化配对控制器失败: $e'); statusDescription.value = 'initializationFailure'.tr; - _isBluetoothServiceAvailable.value = false; } finally { isLoading.value = false; - if (hasPermissions) { - _updateStatusDescription(); - } + _updateStatusDescription(); } } @@ -91,11 +93,11 @@ class PairingController extends GetxController { // Android 12+ (SDK 31+) 需要 SCAN 和 CONNECT // Android 6+ (SDK 23+) 需要 LOCATION permissionsToRequest.add(Permission.locationWhenInUse); // 基本定位权限 - // Android 12及以上版本才需要请求蓝牙扫描和连接权限 - if (await _getAndroidSdkVersion() >= 31) { - permissionsToRequest.add(Permission.bluetoothScan); - permissionsToRequest.add(Permission.bluetoothConnect); - } + // Android 12及以上版本才需要请求蓝牙扫描和连接权限 + if (await _getAndroidSdkVersion() >= 31) { + permissionsToRequest.add(Permission.bluetoothScan); + permissionsToRequest.add(Permission.bluetoothConnect); + } } else if (Platform.isIOS) { permissionsToRequest.add(Permission.bluetooth); } @@ -119,50 +121,71 @@ class PairingController extends GetxController { if (Platform.isAndroid) { // 这里需要原生代码支持来获取确切的SDK版本, // 或者使用 device_info_plus 插件 - // 暂时返回一个较高值以触发新权限请求,如果需要精确控制则需要引入插件 - // 或者,如果你的minSdkVersion >= 31,可以直接请求新权限 - // 假设minSdkVersion < 31,我们需要一种方式判断当前运行设备的SDK版本 - // 为了简化,我们这里先假设需要请求新权限,如果你的目标SDK较低,需要调整 + // 暂时返回一个较高值以触发新权限请求 return 31; // 假设运行在需要新权限的设备上 } return 0; } - /// 设置状态变化监听 - void _setupStatusListeners() { - // 监听蓝牙状态变化 - ever(bluetoothState, (_) => _updateStatusDescription()); - - // 监听当前设备变化 - ever(currentDevice, (device) { - _updateStatusDescription(); - - // 当设备连接时自动跳转 - if (device != null) { - _autoNavigateToHome(); - } - }); + /// 设置蓝牙状态监听 + void _setupBluetoothListener() { + _bluetoothSubscription?.cancel(); + + try { + _bluetoothSubscription = ClassicBluetooth.events.listen( + (event) { + if (event.type == 'stateChanged' && event.state != null) { + _bluetoothState.value = event.state!; + _updateStatusDescription(); + + // 如果蓝牙关闭,清除设备 + if (event.state == 'off') { + _currentDevice.value = null; + } + } else if (event.type == 'deviceConnected' && event.device != null) { + // 直接从事件中获取设备信息 + final newDevice = event.device!; + + // 更新当前设备 + _currentDevice.value = newDevice; + _updateStatusDescription(); + + // 设备连接时自动跳转到主页 + _autoNavigateToHome(); + } else if (event.type == 'deviceDisconnected' && event.device != null) { + // 直接从事件中获取设备信息 + final disconnectedDevice = event.device!; + + // 如果断开的是当前设备,清除当前设备 + if (_currentDevice.value?.address == disconnectedDevice.address) { + _currentDevice.value = null; + } + _updateStatusDescription(); + } + }, + onError: (error) { + Logger.error('蓝牙事件流错误: $error'); + }, + ); + } catch (e) { + Logger.error('设置蓝牙事件监听器失败: $e'); + } } /// 更新状态描述 void _updateStatusDescription() { - if (!hasPermissions) { - statusDescription.value = 'permissionDenied'.tr; - return; - } - - if (bluetoothState.value == 'off') { - statusDescription.value = 'bluetoothTurnedOff'.tr; + if (!hasPermissions) { + statusDescription.value = 'permissionDenied'.tr; return; } - - if (currentDevice.value != null) { - statusDescription.value = '${'connectedDevice'.tr} ${currentDevice.value!.name}'; + + if (bluetoothState == 'off') { + statusDescription.value = 'bluetoothTurnedOff'.tr; return; } - if (connectedDevices.isNotEmpty) { - statusDescription.value = '${'findConnectedDevice'.tr} ${connectedDevices.first.name}'; + if (currentDevice != null) { + statusDescription.value = '${'connectedDevice'.tr} ${currentDevice!.name}'; return; } @@ -176,9 +199,7 @@ class PairingController extends GetxController { /// 刷新已连接设备 Future refreshConnectedDevices() async { - if (!_isBluetoothServiceAvailable.value) return; - - if (!await _checkAndRequestPermissions()) { + if (!hasPermissions) { statusDescription.value = 'permissionDenied'.tr; return; } @@ -187,24 +208,31 @@ class PairingController extends GetxController { isLoading.value = true; statusDescription.value = 'searchingDevice'.tr; - await _bluetoothService.refreshConnectedDevices(); + // 获取已连接的蓝牙设备 + final devices = await ClassicBluetooth.getConnectedDevices(); + + // 如果有设备则设置为当前设备 + if (devices.isNotEmpty) { + _currentDevice.value = devices.first; + _autoNavigateToHome(); + } else { + _currentDevice.value = null; + } } catch (e) { Logger.error('刷新设备失败: $e'); statusDescription.value = 'failedRefreshDevice'.tr; } finally { isLoading.value = false; - if (hasPermissions) { - _updateStatusDescription(); - } + _updateStatusDescription(); } } /// 自动跳转到主页 void _autoNavigateToHome() { - if (_isNavigatingToHome || currentDevice.value == null) return; + if (_isNavigatingToHome || currentDevice == null) return; _isNavigatingToHome = true; - Logger.info('已找到连接的设备 ${currentDevice.value?.name},自动跳转到主页'); + Logger.info('已找到连接的设备 ${currentDevice?.name},自动跳转到主页'); Future.delayed(const Duration(milliseconds: 500), () { Get.offAllNamed(Routes.home); @@ -214,24 +242,24 @@ class PairingController extends GetxController { /// 打开系统蓝牙设置 void openBluetoothSettings() { try { - if (!hasPermissions) { - openAppSettings(); - } else if (Platform.isAndroid && bluetoothState.value == 'off') { - AppSettings.openAppSettings(type: AppSettingsType.bluetooth); - } else if (Platform.isIOS) { - openAppSettings(); - } else { - AppSettings.openAppSettings(type: AppSettingsType.bluetooth); - } + if (!hasPermissions) { + openAppSettings(); + } else if (Platform.isAndroid && bluetoothState == 'off') { + AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + } else if (Platform.isIOS) { + openAppSettings(); + } else { + AppSettings.openAppSettings(type: AppSettingsType.bluetooth); + } } catch (e) { Logger.error('打开设置失败: $e'); } } - /// 打开应用设置 - Future openAppSettings() async { - await AppSettings.openAppSettings(); - } + /// 打开应用设置 + Future openAppSettings() async { + await AppSettings.openAppSettings(); + } /// 跳过连接 void skipConnecting() { diff --git a/local_plugins/classic_bluetooth/android/build.gradle.kts b/local_plugins/classic_bluetooth/android/build.gradle.kts new file mode 100644 index 000000000..1e055e488 --- /dev/null +++ b/local_plugins/classic_bluetooth/android/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + // Android Library 插件 + id("com.android.library") + // Kotlin Android 插件 + id("org.jetbrains.kotlin.android") + id("com.github.johnrengelman.shadow") version "8.1.1" +} + +android { + // 命名空间,对应你插件的包名(需与代码内包名保持一致) + namespace = "com.yunqiinnovation.classic_bluetooth" + + // 目标 SDK 版本 + compileSdk = 33 + + defaultConfig { + // 最低 SDK 版本 + minSdk = 21 + targetSdk = 33 + } + + // Java 语言级别兼容配置 + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + // Kotlin 语言级别 + kotlinOptions { + jvmTarget = "11" + } + +} + +dependencies { + add("implementation", "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.10") +} diff --git a/local_plugins/classic_bluetooth/android/settings.gradle.kts b/local_plugins/classic_bluetooth/android/settings.gradle.kts new file mode 100644 index 000000000..22e118edf --- /dev/null +++ b/local_plugins/classic_bluetooth/android/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "classic_bluetooth" diff --git a/local_plugins/classic_bluetooth/android/src/main/AndroidManifest.xml b/local_plugins/classic_bluetooth/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000..f95589110 --- /dev/null +++ b/local_plugins/classic_bluetooth/android/src/main/AndroidManifest.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt b/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt similarity index 95% rename from android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt rename to local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt index e8ba00348..254ca1273 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothHelper.kt +++ b/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothHelper.kt @@ -1,4 +1,4 @@ -package com.yunqiinnovation.deepsound +package com.yunqiinnovation.classic_bluetooth import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice @@ -79,7 +79,6 @@ class ClassicBluetoothHelper(private val context: Context) { return bluetoothAdapter.isEnabled } catch (e: Exception) { - Log.e(TAG, "检查蓝牙状态失败", e) return false } } @@ -187,24 +186,6 @@ class ClassicBluetoothHelper(private val context: Context) { callback.onSuccess(devicesList) } - /** - * 检查是否有蓝牙权限 - */ - fun hasBluetoothPermission(): Boolean { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - ContextCompat.checkSelfPermission( - context, - Manifest.permission.BLUETOOTH_CONNECT - ) == PackageManager.PERMISSION_GRANTED - } else { - ContextCompat.checkSelfPermission( - context, - Manifest.permission.BLUETOOTH - ) == PackageManager.PERMISSION_GRANTED - } - } - - /** * 开始监听蓝牙状态变化 * @param callback 蓝牙状态回调接口 @@ -314,7 +295,7 @@ class ClassicBluetoothHelper(private val context: Context) { try { context.unregisterReceiver(bluetoothReceiver) } catch (e: Exception) { - Log.e(TAG, "注销蓝牙接收器失败", e) + // 忽略注销失败异常 } bluetoothReceiver = null } diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothPlugin.kt b/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothPlugin.kt similarity index 95% rename from android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothPlugin.kt rename to local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothPlugin.kt index e4c064b40..c6b632deb 100644 --- a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/ClassicBluetoothPlugin.kt +++ b/local_plugins/classic_bluetooth/android/src/main/kotlin/com/yunqiinnovation/classic_bluetooth/ClassicBluetoothPlugin.kt @@ -1,4 +1,4 @@ -package com.yunqiinnovation.deepsound +package com.yunqiinnovation.classic_bluetooth import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothDevice @@ -58,9 +58,6 @@ class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler { "isBluetoothEnabled" -> { result.success(bluetoothHelper.isBluetoothEnabled()) } - "hasBluetoothPermission" -> { - result.success(bluetoothHelper.hasBluetoothPermission()) - } else -> { result.notImplemented() } @@ -151,8 +148,6 @@ class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler { } override fun onDeviceConnected(device: Map) { - FileLogger.d(TAG, "设备连接: ${device["name"]} (${device["address"]})") - // 发送设备连接事件 val eventMap = mapOf( "type" to "deviceConnected", @@ -167,8 +162,6 @@ class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler { } override fun onDeviceDisconnected(device: Map) { - FileLogger.d(TAG, "设备断开: ${device["name"]} (${device["address"]})") - // 发送设备断开事件 val eventMap = mapOf( "type" to "deviceDisconnected", @@ -189,6 +182,10 @@ class ClassicBluetoothPlugin: FlutterPlugin, MethodCallHandler { */ private fun sendEvent(event: Map) { try { + if (eventSink == null) { + return + } + eventSink?.success(event) } catch (e: Exception) { FileLogger.e(TAG, "事件发送失败", e) diff --git a/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart b/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart new file mode 100644 index 000000000..eb66ae5b8 --- /dev/null +++ b/local_plugins/classic_bluetooth/lib/classic_bluetooth.dart @@ -0,0 +1,115 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; + +/// 经典蓝牙插件 +class ClassicBluetooth { + static const MethodChannel _methodChannel = + MethodChannel('com.yunqiinnovation.classic_bluetooth/methods'); + + static const EventChannel _eventChannel = + EventChannel('com.yunqiinnovation.classic_bluetooth/events'); + + static Stream? _eventStream; + + /// 获取已连接的蓝牙设备 + static Future> getConnectedDevices() async { + 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'); + }); + + return _eventStream!; + } +} + +/// 蓝牙设备模型 +class BluetoothDevice { + final String name; + final String address; + + BluetoothDevice({ + required this.name, + required this.address, + }); + + factory BluetoothDevice.fromJson(Map json) { + return BluetoothDevice( + name: json['name'] as String, + address: json['address'] as String, + ); + } + + Map toJson() { + return { + 'name': name, + 'address': address, + }; + } + + @override + String toString() => 'BluetoothDevice(name: $name, address: $address)'; +} + +/// 简化的蓝牙事件类 +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); + + // 处理设备相关事件 + BluetoothDevice? device; + if (json.containsKey('device')) { + device = BluetoothDevice.fromJson(json['device']); + } + + return BluetoothEvent( + type: type, + state: json['state'] as String?, + device: device, + timestamp: timestamp, + ); + } + + @override + String toString() { + if (type == 'stateChanged') { + return 'BluetoothEvent(type: $type, state: $state)'; + } else if (type == 'deviceConnected' || type == 'deviceDisconnected') { + return 'BluetoothEvent(type: $type, device: $device)'; + } else { + return 'BluetoothEvent(type: $type)'; + } + } +} \ No newline at end of file diff --git a/local_plugins/classic_bluetooth/pubspec.yaml b/local_plugins/classic_bluetooth/pubspec.yaml new file mode 100644 index 000000000..eca826603 --- /dev/null +++ b/local_plugins/classic_bluetooth/pubspec.yaml @@ -0,0 +1,20 @@ +name: classic_bluetooth +description: 经典蓝牙设备连接和状态监控插件 +version: 0.0.1 + +environment: + sdk: ">=2.17.0 <3.0.0" + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + deepvoice_utils: + path: ../deepvoice_utils + +flutter: + plugin: + platforms: + android: + package: com.yunqiinnovation.classic_bluetooth + pluginClass: ClassicBluetoothPlugin \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index d410fbe5e..ff72848c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -82,6 +82,8 @@ dependencies: path: local_plugins/ble_service deepvoice_utils: path: local_plugins/deepvoice_utils + classic_bluetooth: + path: local_plugins/classic_bluetooth dev_dependencies: