diff --git a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt index a5b35ca5f..d979161d4 100644 --- a/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt +++ b/android/app/src/main/kotlin/com/example/deep_voice/VoiceInteractionService.kt @@ -37,7 +37,7 @@ class VoiceInteractionService : Service() { private const val CHANNEL_ID = "voice_interaction_channel" // 语音识别超时时间(毫秒) - private const val RECOGNITION_TIMEOUT = 10000L + private const val RECOGNITION_TIMEOUT = 8000L // 用于跟踪服务是否正在运行 private val isRunning = AtomicBoolean(false) @@ -321,7 +321,7 @@ class VoiceInteractionService : Service() { if (!hasSpeechDetected && !isTtsSpeaking && elapsedTime >= RECOGNITION_TIMEOUT) { Log.d(TAG, "超过${RECOGNITION_TIMEOUT/1000}秒未检测到语音,停止识别") isTimeoutPaused = true - playNotification("没有听到您说话,已暂停语音识别。双击耳机按钮可重新开始。") + playNotification("没有听到您说话,已暂停对话。双击耳机按钮可重新开始。") // 停止语音识别并确保资源完全释放 stopVoiceRecognition() diff --git a/lib/data/services/bluetooth_service.dart b/lib/data/services/bluetooth_service.dart index fb74a27b0..eae177804 100644 --- a/lib/data/services/bluetooth_service.dart +++ b/lib/data/services/bluetooth_service.dart @@ -18,60 +18,44 @@ enum DeviceType { class BluetoothService extends GetxService { static BluetoothService get to => Get.find(); - // 单例实例 + // 内部实例 + BluetoothService._internal(); static final BluetoothService _instance = BluetoothService._internal(); factory BluetoothService() => _instance; - BluetoothService._internal(); - - // 设备状态 + + // ====== 状态管理 ====== + // 蓝牙适配器状态 final _isAvailable = false.obs; final _isOn = false.obs; + final _isInitialized = false.obs; - // 已连接的设备 - final _connectedDevices = [].obs; - final _systemPairedDevices = [].obs; - - // 扫描状态 + // 操作状态 final _isScanning = false.obs; + final _permissionsGranted = false.obs; - // 扫描结果 + // 设备管理 + final _connectedDevices = [].obs; + final _systemPairedDevices = [].obs; final _scanResults = [].obs; - - // 当前选中的设备 final _selectedDevice = Rx(null); final _deviceType = Rx(DeviceType.unknown); - - // 连接状态 final _connectionState = Rx(BluetoothConnectionState.disconnected); - - // 存储最后连接的设备信息,供其他控制器使用 final _lastConnectedDeviceInfo = Rx>({}); - // 订阅 + // 流订阅 StreamSubscription? _stateSubscription; StreamSubscription? _scanSubscription; StreamSubscription? _connectionSubscription; - // 连接锁,防止并发操作 - bool _isConnecting = false; - - // 记录连接尝试次数 - int _connectionAttempts = 0; - static const int MAX_CONNECTION_ATTEMPTS = 2; - - // 权限状态 - final _permissionsGranted = false.obs; - bool get permissionsGranted => _permissionsGranted.value; - - // 初始化完成标志 - final _isInitialized = false.obs; - bool get isInitialized => _isInitialized.value; - RxBool get isInitializedObs => _isInitialized; - - // Getters + // ====== Getters ====== + // 基本getter bool get isAvailable => _isAvailable.value; bool get isOn => _isOn.value; + bool get isInitialized => _isInitialized.value; bool get isScanning => _isScanning.value; + bool get permissionsGranted => _permissionsGranted.value; + + // 设备相关getter List get connectedDevices => _connectedDevices; List get systemPairedDevices => _systemPairedDevices; List get scanResults => _scanResults; @@ -80,9 +64,12 @@ class BluetoothService extends GetxService { BluetoothConnectionState get connectionState => _connectionState.value; Map get lastConnectedDeviceInfo => _lastConnectedDeviceInfo.value; - // Observable getters (for reactive programming) + // Observable getters RxBool get isAvailableObs => _isAvailable; RxBool get isOnObs => _isOn; + RxBool get isInitializedObs => _isInitialized; + RxBool get isScanningObs => _isScanning; + RxBool get permissionsGrantedObs => _permissionsGranted; RxList get connectedDevicesObs => _connectedDevices; RxList get scanResultsObs => _scanResults; Rx get selectedDeviceObs => _selectedDevice; @@ -90,61 +77,23 @@ class BluetoothService extends GetxService { Rx get connectionStateObs => _connectionState; Rx> get lastConnectedDeviceInfoObs => _lastConnectedDeviceInfo; - // 电池服务UUID - static const String BATTERY_SERVICE_UUID = "0000180f-0000-1000-8000-00805f9b34fb"; - // 电池电量特征UUID - static const String BATTERY_CHARACTERISTIC_UUID = "00002a19-0000-1000-8000-00805f9b34fb"; - /// 初始化蓝牙服务 Future init() async { + if (_isInitialized.value) { + Logger.info('蓝牙服务已初始化,无需重复初始化'); + return this; + } + + Logger.info('开始初始化蓝牙服务'); + try { - Logger.info('开始初始化蓝牙服务'); - - // 先请求权限,确保权限请求完成后再进行后续操作 - if (Platform.isAndroid) { - Logger.info('Android平台,请求蓝牙权限'); - await _checkAndRequestPermissions(); - } + // 1. 设置蓝牙状态监听 + _setupBluetoothListeners(); - // 设置蓝牙状态监听 - _stateSubscription = FlutterBluePlus.adapterState.listen((state) { - _isOn.value = state == BluetoothAdapterState.on; - _isAvailable.value = state != BluetoothAdapterState.unavailable; - - if (state == BluetoothAdapterState.on) { - // 蓝牙打开时,获取已连接和已配对设备 - _refreshSystemDevices(); - } else if (state == BluetoothAdapterState.off) { - // 蓝牙关闭时,清空设备列表 - _connectedDevices.clear(); - _systemPairedDevices.clear(); - _scanResults.clear(); - _selectedDevice.value = null; - _lastConnectedDeviceInfo.value = {}; - _deviceType.value = DeviceType.unknown; - } - }); + // 2. 初始检查蓝牙状态 + _checkBluetoothState(); - // 检查蓝牙状态 - try { - bool isOn = await FlutterBluePlus.isOn - .timeout(const Duration(seconds: 5), onTimeout: () { - Logger.error('检查蓝牙状态超时'); - return false; - }); - - _isOn.value = isOn; - _isAvailable.value = isOn; - - if (isOn) { - // 刷新设备状态 - await _refreshSystemDevices(); - } - } catch (e) { - Logger.error('检查蓝牙状态失败: $e'); - } - - // 标记初始化完成 + // 3. 标记初始化完成 _isInitialized.value = true; Logger.info('蓝牙服务初始化完成'); return this; @@ -154,178 +103,186 @@ class BluetoothService extends GetxService { } } - /// 检查并请求蓝牙所需权限(Android 12+) - Future _checkAndRequestPermissions() async { - try { - if (Platform.isAndroid) { - // 对于Android 12+,需要BLUETOOTH_SCAN和BLUETOOTH_CONNECT权限 - Map statuses = await [ - Permission.bluetoothScan, - Permission.bluetoothConnect, - Permission.location, - ].request(); - - bool allGranted = statuses.values.every((status) => status.isGranted); - _permissionsGranted.value = allGranted; - - if (!allGranted) { - Logger.warning('蓝牙权限未完全授予: $statuses'); + /// 设置蓝牙状态监听 + void _setupBluetoothListeners() { + // 取消旧订阅(如果有) + _stateSubscription?.cancel(); + + // 监听蓝牙适配器状态变化 + _stateSubscription = FlutterBluePlus.adapterState.listen((state) { + _isOn.value = state == BluetoothAdapterState.on; + _isAvailable.value = state != BluetoothAdapterState.unavailable; + + if (state == BluetoothAdapterState.on) { + Logger.info('蓝牙已开启'); + if (_permissionsGranted.value) { + _refreshSystemDevices(); } - - return allGranted; + } else if (state == BluetoothAdapterState.off) { + Logger.info('蓝牙已关闭'); + _resetDeviceState(); } + }); + } + + /// 初始检查蓝牙状态 + Future _checkBluetoothState() async { + try { + bool isOn = await FlutterBluePlus.isOn + .timeout(const Duration(seconds: 5), onTimeout: () => false); - // iOS不需要显式请求蓝牙权限 - _permissionsGranted.value = true; - return true; + _isOn.value = isOn; + _isAvailable.value = isOn; + Logger.info('当前蓝牙是否已开启: $isOn'); } catch (e) { - Logger.error('检查蓝牙权限失败: $e'); - _permissionsGranted.value = false; - return false; + Logger.error('检查蓝牙状态失败: $e'); + _isOn.value = false; + _isAvailable.value = false; } } - /// 再次请求权限 - Future requestPermissions() async { - return await _checkAndRequestPermissions(); + /// 重置设备状态 + void _resetDeviceState() { + _connectedDevices.clear(); + _systemPairedDevices.clear(); + _scanResults.clear(); + _selectedDevice.value = null; + _lastConnectedDeviceInfo.value = {}; + _deviceType.value = DeviceType.unknown; + + // 取消正在进行的操作 + _scanSubscription?.cancel(); + _connectionSubscription?.cancel(); + _isScanning.value = false; } - /// 刷新系统设备状态(已连接和已配对设备) - Future _refreshSystemDevices() async { + /// 检查并请求蓝牙权限 + Future requestPermissions() async { try { - // 避免重复刷新,使用标志位 - bool _isRefreshing = false; - if (_isRefreshing) return; + // iOS无需额外权限 + if (!Platform.isAndroid) { + _permissionsGranted.value = true; + return true; + } - _isRefreshing = true; + // 请求所有必要权限 + final permissions = [ + Permission.bluetoothScan, + Permission.bluetoothConnect, + Permission.location, // Android需要定位权限来扫描BLE设备 + Permission.microphone + ]; - // 获取系统已连接设备 - try { - List devices = await FlutterBluePlus.connectedSystemDevices - .timeout(const Duration(seconds: 5), onTimeout: () { - Logger.error('获取已连接设备超时'); - return []; - }); - - _connectedDevices.value = devices; - Logger.info('已连接系统设备数量: ${_connectedDevices.length}'); - - // 如果有当前选中设备,更新其状态 - if (_selectedDevice.value != null) { - // 检查选中设备是否仍然在已连接列表中 - bool stillConnected = _connectedDevices.any( - (d) => d.remoteId.str == _selectedDevice.value!.remoteId.str - ); - - if (stillConnected) { - _connectionState.value = BluetoothConnectionState.connected; - _updateLastConnectedDeviceInfo(); - } else { - _connectionState.value = BluetoothConnectionState.disconnected; - } - } - } catch (e) { - Logger.error('获取已连接设备失败: $e'); - } + // 一次性请求所有权限 + Map statuses = await permissions.request(); - // 获取已配对设备 - try { - List pairedDevices = await FlutterBluePlus.bondedDevices - .timeout(const Duration(seconds: 5), onTimeout: () { - Logger.error('获取已配对设备超时'); - return []; + // 检查所有权限是否已授予 + bool allGranted = statuses.values.every((status) => status.isGranted); + _permissionsGranted.value = allGranted; + + if (!allGranted) { + // 检查哪些权限被拒绝 + List deniedPermissions = []; + statuses.forEach((permission, status) { + if (!status.isGranted) { + deniedPermissions.add(permission.toString().split('.').last); + } }); - _systemPairedDevices.value = pairedDevices; - Logger.info('系统已配对设备数量: ${_systemPairedDevices.length}'); - } catch (e) { - Logger.error('获取已配对设备失败: $e'); - } - - // 如果有已连接的设备但没有选中设备,自动选择第一个 - if (_connectedDevices.isNotEmpty && _selectedDevice.value == null) { - _selectedDevice.value = _connectedDevices[0]; - _determineDeviceType(_selectedDevice.value!); - _connectionState.value = BluetoothConnectionState.connected; - _updateLastConnectedDeviceInfo(); + Logger.warning('权限被拒绝: ${deniedPermissions.join(", ")}'); } - _isRefreshing = false; + return allGranted; } catch (e) { - Logger.error('刷新系统设备状态失败: $e'); + Logger.error('请求权限失败: $e'); + _permissionsGranted.value = false; + return false; } } - /// 确定设备类型(经典蓝牙/BLE/双模) - void _determineDeviceType(BluetoothDevice device) { + /// 刷新系统蓝牙设备(已连接和已配对) + Future _refreshSystemDevices() async { + if (!_permissionsGranted.value || !_isOn.value) { + Logger.warning('无法刷新系统设备:蓝牙未开启或缺少权限'); + return; + } + try { - // 默认设为未知类型 - _deviceType.value = DeviceType.unknown; - - // 设备名称包含耳机/headphone/earphone/earbuds等关键词可能是音频设备 - final String deviceName = device.platformName.toLowerCase(); - final List audioKeywords = [ - 'headphone', 'earphone', 'earbuds', 'headset', - 'speaker', 'audio', '耳机', '音箱', '音响' - ]; - - bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword)); + // 获取已连接的系统设备 + List devices = await FlutterBluePlus.connectedSystemDevices + .timeout(const Duration(seconds: 10), onTimeout: () => []); - // 如果在已连接设备列表中,很可能是已经通过系统连接的经典蓝牙 - bool inConnectedList = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str); + _connectedDevices.value = devices; + Logger.info('获取到${devices.length}个系统已连接设备'); - if (hasAudioKeyword || inConnectedList) { - _deviceType.value = DeviceType.classicAudio; - Logger.info('设备${device.platformName}被判定为经典蓝牙音频设备'); - } else { - // 不确定的情况,先假设是BLE设备,连接后可能会更新 - _deviceType.value = DeviceType.ble; - Logger.info('设备${device.platformName}被判定为BLE设备'); + // 更新选中设备的连接状态(如果有) + if (_selectedDevice.value != null) { + bool stillConnected = _connectedDevices.any( + (d) => d.remoteId.str == _selectedDevice.value!.remoteId.str + ); + + _connectionState.value = stillConnected + ? BluetoothConnectionState.connected + : BluetoothConnectionState.disconnected; + + if (stillConnected) { + _updateLastConnectedDeviceInfo(); + } } + + // 获取系统已配对设备 + List pairedDevices = await FlutterBluePlus.bondedDevices + .timeout(const Duration(seconds: 10), onTimeout: () => []); + + _systemPairedDevices.value = pairedDevices; + Logger.info('获取到${pairedDevices.length}个系统已配对设备'); } catch (e) { - Logger.error('确定设备类型失败: $e'); - _deviceType.value = DeviceType.unknown; + Logger.error('刷新系统设备失败: $e'); } } /// 开始扫描附近的蓝牙设备 Future> startScan({Duration? timeout}) async { + // 检查条件 + if (!_permissionsGranted.value) { + Logger.warning('缺少蓝牙扫描权限,无法扫描设备'); + return []; + } + + if (!_isOn.value) { + Logger.warning('蓝牙未开启,无法扫描设备'); + return []; + } + + // 如果已经在扫描,先停止 if (_isScanning.value) { await stopScan(); } - // 清空旧的扫描结果 _scanResults.clear(); + Logger.info('开始扫描附近蓝牙设备...'); try { - // 确保拥有扫描权限 - if (Platform.isAndroid && !await Permission.bluetoothScan.isGranted) { - Logger.warning('没有蓝牙扫描权限,尝试请求权限'); - await _checkAndRequestPermissions(); - } - - // 设置扫描监听 + // 设置结果监听 _scanSubscription?.cancel(); _scanSubscription = FlutterBluePlus.scanResults.listen((results) { _scanResults.value = results; }); - // 设置扫描中状态 - _isScanning.value = true; - // 开始扫描 + _isScanning.value = true; await FlutterBluePlus.startScan( - timeout: timeout ?? const Duration(seconds: 5), + timeout: timeout ?? const Duration(seconds: 10), + androidScanMode: AndroidScanMode.lowLatency, ); // 扫描完成 _isScanning.value = false; - Logger.info('蓝牙扫描完成,发现 ${_scanResults.length} 个设备'); - + Logger.info('扫描完成,发现${_scanResults.length}个设备'); return _scanResults; } catch (e) { _isScanning.value = false; - Logger.error('蓝牙扫描失败: $e'); + Logger.error('扫描设备失败: $e'); return []; } } @@ -337,246 +294,261 @@ class BluetoothService extends GetxService { try { await FlutterBluePlus.stopScan(); _isScanning.value = false; + Logger.info('已停止蓝牙扫描'); } catch (e) { - Logger.error('停止蓝牙扫描失败: $e'); + Logger.error('停止扫描失败: $e'); } } - /// 连接到指定的蓝牙设备 - /// 针对不同类型设备使用不同的连接策略 + /// 连接到指定蓝牙设备 Future connectToDevice(BluetoothDevice device) async { - // 防止并发连接操作 - if (_isConnecting) { - Logger.warning('已有正在进行的连接操作,请等待完成'); + // 检查条件 + if (!_permissionsGranted.value) { + Logger.warning('缺少蓝牙连接权限,无法连接设备'); + return false; + } + + if (!_isOn.value) { + Logger.warning('蓝牙未开启,无法连接设备'); return false; } - _isConnecting = true; - _connectionAttempts = 0; + Logger.info('尝试连接设备: ${device.platformName}'); try { - Logger.info('尝试连接设备: ${device.platformName} (${device.remoteId.str})'); - // 确定设备类型 _determineDeviceType(device); - // 检查设备是否已在系统连接列表中 + // 检查设备是否已连接 bool alreadyConnected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str); - // 如果是当前已选择且已连接的设备,直接返回成功 + // 如果当前已选择该设备且已连接,则无需再连接 if (_selectedDevice.value == device && alreadyConnected) { - Logger.info('设备已连接,无需重新连接'); + Logger.info('设备已连接,无需重复连接'); _connectionState.value = BluetoothConnectionState.connected; _updateLastConnectedDeviceInfo(); - _isConnecting = false; return true; } - // 如果有其他已选择的设备,先取消监听并清除 + // 如果已选择了其他设备,先断开 if (_selectedDevice.value != null && _selectedDevice.value != device) { - _connectionSubscription?.cancel(); - _connectionSubscription = null; + await disconnectFromDevice(); } - // 更新选中的设备 + // 设置新的目标设备 _selectedDevice.value = device; - // 对于经典蓝牙音频设备,不尝试BLE连接,直接检查系统连接状态 + + // 针对不同类型设备采取不同连接策略 if (_deviceType.value == DeviceType.classicAudio) { - Logger.info('经典蓝牙音频设备,检查系统连接状态'); - - // 刷新系统连接设备列表 - await _refreshSystemDevices(); - - // 再次检查设备是否在已连接列表中 - alreadyConnected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str); - - if (alreadyConnected) { - Logger.info('设备已在系统连接列表中,视为已连接'); - _connectionState.value = BluetoothConnectionState.connected; - _monitorSystemConnectionState(device); - _updateLastConnectedDeviceInfo(); - _isConnecting = false; - return true; - } else { - Logger.info('经典蓝牙设备未在系统连接列表中,需要在系统设置中配对和连接'); - _connectionState.value = BluetoothConnectionState.disconnected; - _isConnecting = false; - return false; - } + // 经典蓝牙设备(如音频设备)依赖系统连接 + return _connectToClassicAudioDevice(device); + } else { + // BLE设备直接通过库连接 + return _connectToBLEDevice(device); } - - // 对于BLE或双模设备,尝试进行BLE连接 - Logger.info('尝试进行BLE连接'); - return await _attemptBleConnection(device); } catch (e) { - Logger.error('连接设备 ${device.platformName} 失败: $e'); + Logger.error('连接设备过程中出错: $e'); _connectionState.value = BluetoothConnectionState.disconnected; - _isConnecting = false; return false; } } - /// 尝试进行BLE连接 - Future _attemptBleConnection(BluetoothDevice device) async { + /// 连接经典蓝牙音频设备 + Future _connectToClassicAudioDevice(BluetoothDevice device) async { + Logger.info('尝试连接经典蓝牙音频设备'); + try { - _connectionState.value = BluetoothConnectionState.connecting; - - // 增加连接尝试次数 - _connectionAttempts++; - - // 不使用autoConnect=true来避免与mtu参数冲突 - // 根据错误日志,mtu和autoConnect是不兼容的 - bool autoConnect = false; // 始终使用false避免兼容性问题 - Logger.info('BLE连接尝试 #$_connectionAttempts,autoConnect=$autoConnect'); - - // 开始监听连接状态 - _monitorConnectionState(device); - - // 尝试连接,不同的尝试使用不同的超时时间 - if (_connectionAttempts == 1) { - // 第一次尝试:短超时 - Logger.info('第一次尝试使用短超时(4秒)'); - await device.connect( - autoConnect: autoConnect, - timeout: const Duration(seconds: 4) - ); - } else { - // 第二次尝试:长超时 - Logger.info('第二次尝试使用长超时(8秒)'); - await device.connect( - autoConnect: autoConnect, - timeout: const Duration(seconds: 8) - ); - } + // 刷新系统设备列表 + await _refreshSystemDevices(); - // 如果连接成功,更新状态 - _connectionState.value = BluetoothConnectionState.connected; - _updateLastConnectedDeviceInfo(); + // 检查设备是否在系统已连接列表中 + bool connected = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str); - Logger.info('设备 ${device.platformName} 连接成功'); - _isConnecting = false; - return true; + if (connected) { + Logger.info('经典蓝牙设备已在系统连接列表中'); + _connectionState.value = BluetoothConnectionState.connected; + _updateLastConnectedDeviceInfo(); + return true; + } else { + Logger.info('经典蓝牙设备未在系统连接列表中,需要在系统设置中进行配对'); + _connectionState.value = BluetoothConnectionState.disconnected; + return false; + } } catch (e) { - // 连接失败,检查是否需要重试 - Logger.error('BLE连接尝试 #$_connectionAttempts 失败: $e'); - - if (_connectionAttempts < MAX_CONNECTION_ATTEMPTS) { - Logger.info('尝试使用不同参数重新连接'); + Logger.error('连接经典蓝牙设备失败: $e'); + _connectionState.value = BluetoothConnectionState.disconnected; + return false; + } + } + + /// 连接BLE设备 + Future _connectToBLEDevice(BluetoothDevice device) async { + Logger.info('尝试连接BLE设备'); + _connectionState.value = BluetoothConnectionState.connecting; + + try { + // 设置连接状态监听 + _connectionSubscription?.cancel(); + _connectionSubscription = device.connectionState.listen((state) { + Logger.info('BLE连接状态变化: $state'); + _connectionState.value = state; - // 断开连接并等待一段时间 - try { - await device.disconnect(); - } catch (disconnectError) { - // 忽略断开连接的错误 - Logger.error('断开连接时出错,继续尝试: $disconnectError'); + if (state == BluetoothConnectionState.connected) { + _updateLastConnectedDeviceInfo(); } - - // 等待时间更长,确保设备和系统状态重置 - await Future.delayed(const Duration(milliseconds: 2000)); - - // 重试连接 - return await _attemptBleConnection(device); + }); + + // 尝试连接 + await device.connect( + autoConnect: false, + timeout: const Duration(seconds: 10) + ); + + // 等待连接状态 + final completer = Completer(); + bool isCompleted = false; // 添加一个本地变量跟踪完成状态 + + // 检查当前状态 + if (_connectionState.value == BluetoothConnectionState.connected) { + completer.complete(true); + isCompleted = true; } else { - // 连接失败且达到最大尝试次数 - _connectionState.value = BluetoothConnectionState.disconnected; - _isConnecting = false; - return false; + // 设置超时 + Timer(const Duration(seconds: 12), () { + if (!isCompleted) { // 使用本地变量而不是 completer.isCompleted + Logger.warning('连接设备超时'); + completer.complete(false); + isCompleted = true; + } + }); + } + + bool result = await completer.future; + + if (result) { + Logger.info('BLE设备连接成功'); + _updateLastConnectedDeviceInfo(); + } else { + Logger.warning('BLE设备连接失败'); + // 尝试断开连接 + await device.disconnect().catchError((_) {}); } + + return result; + } catch (e) { + Logger.error('连接BLE设备失败: $e'); + _connectionState.value = BluetoothConnectionState.disconnected; + + // 尝试断开连接 + try { + await device.disconnect(); + } catch (_) {} + + return false; } } - /// 断开与当前设备的连接 + /// 断开当前设备连接 Future disconnectFromDevice() async { + // 没有已选择的设备 if (_selectedDevice.value == null) { - Logger.info('没有当前选中的设备,无需断开连接'); return true; } - // 取消设备监听 + // 取消连接状态监听 _connectionSubscription?.cancel(); _connectionSubscription = null; try { - // 对于经典蓝牙设备,不调用disconnect - if (_deviceType.value == DeviceType.classicAudio) { - Logger.info('经典蓝牙设备,仅清除选择状态,不调用disconnect'); - _selectedDevice.value = null; - _connectionState.value = BluetoothConnectionState.disconnected; - return true; + // 对BLE设备才需要执行断开操作 + if (_deviceType.value != DeviceType.classicAudio) { + await _selectedDevice.value!.disconnect(); } - // 对于BLE设备,调用disconnect - Logger.info('正在断开设备 ${_selectedDevice.value!.platformName} 的连接'); - await _selectedDevice.value!.disconnect(); + // 清除设备状态 _selectedDevice.value = null; _connectionState.value = BluetoothConnectionState.disconnected; - return true; } catch (e) { - Logger.error('断开蓝牙设备连接失败: $e'); - // 即使失败也清除状态 + Logger.error('断开设备连接失败: $e'); + + // 即使失败,也清除选中设备 _selectedDevice.value = null; _connectionState.value = BluetoothConnectionState.disconnected; return false; } } - /// 监控BLE连接状态 - void _monitorConnectionState(BluetoothDevice device) { - Logger.info('设置设备 ${device.platformName} 的BLE连接状态监听'); - - // 取消之前的监听 - _connectionSubscription?.cancel(); + /// 查找系统已连接设备 + Future findConnectedSystemDevice() async { + if (!_permissionsGranted.value || !_isOn.value) { + Logger.warning('蓝牙未开启或缺少权限,无法查找系统连接设备'); + return null; + } - // 设置新的监听 - _connectionSubscription = device.connectionState.listen((state) { - Logger.info('设备 ${device.platformName} BLE连接状态变更为: $state'); - _connectionState.value = state; - - if (state == BluetoothConnectionState.disconnected) { - Logger.info('设备 ${device.platformName} BLE连接已断开'); - // 设备断开连接时,不清除设备信息,以便下次可以重新连接 - } else if (state == BluetoothConnectionState.connected) { - Logger.info('设备 ${device.platformName} BLE连接已成功'); - // 设备连接成功时,更新最后连接的设备信息 - _updateLastConnectedDeviceInfo(); + try { + // 刷新系统设备列表 + await _refreshSystemDevices(); + + if (_connectedDevices.isEmpty) { + return null; } - }); + + // 优先找出可能的音频设备 + BluetoothDevice device = _findAudioDevice(_connectedDevices) ?? _connectedDevices.first; + + // 自动选择设备 + _selectedDevice.value = device; + _determineDeviceType(device); + _connectionState.value = BluetoothConnectionState.connected; + _updateLastConnectedDeviceInfo(); + + return device; + } catch (e) { + Logger.error('查找系统已连接设备失败: $e'); + return null; + } } - /// 监控系统连接状态(针对经典蓝牙设备) - void _monitorSystemConnectionState(BluetoothDevice device) { - Logger.info('设置设备 ${device.platformName} 的系统连接状态监听'); + /// 查找音频设备 + BluetoothDevice? _findAudioDevice(List devices) { + if (devices.isEmpty) return null; - // 使用定时器定期检查系统连接状态 - Timer.periodic(const Duration(seconds: 5), (timer) async { - // 如果设备已变更或服务关闭,停止监控 - if (_selectedDevice.value == null || - _selectedDevice.value!.remoteId.str != device.remoteId.str) { - timer.cancel(); - return; - } - - try { - await _refreshSystemDevices(); - - // 检查设备是否仍在系统连接列表中 - bool stillConnected = _connectedDevices.any( - (d) => d.remoteId.str == device.remoteId.str - ); - - if (stillConnected && _connectionState.value != BluetoothConnectionState.connected) { - Logger.info('设备 ${device.platformName} 在系统连接列表中,更新为已连接状态'); - _connectionState.value = BluetoothConnectionState.connected; - _updateLastConnectedDeviceInfo(); - } else if (!stillConnected && _connectionState.value == BluetoothConnectionState.connected) { - Logger.info('设备 ${device.platformName} 不在系统连接列表中,更新为断开状态'); - _connectionState.value = BluetoothConnectionState.disconnected; - } - } catch (e) { - Logger.error('监控系统连接状态失败: $e'); + // 音频设备的可能名称关键词 + final List audioKeywords = [ + 'headphone', 'earphone', 'earbuds', 'headset', + 'speaker', 'audio', '耳机', '音箱', '音响' + ]; + + // 尝试匹配音频设备名称 + for (BluetoothDevice device in devices) { + String name = device.platformName.toLowerCase(); + if (audioKeywords.any((keyword) => name.contains(keyword))) { + return device; } - }); + } + + return null; + } + + /// 确定设备类型 + void _determineDeviceType(BluetoothDevice device) { + final String deviceName = device.platformName.toLowerCase(); + + // 音频设备关键词 + final List audioKeywords = [ + 'headphone', 'earphone', 'earbuds', 'headset', + 'speaker', 'audio', '耳机', '音箱', '音响' + ]; + + bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword)); + bool inConnectedList = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str); + + if (hasAudioKeyword || inConnectedList) { + _deviceType.value = DeviceType.classicAudio; + } else { + _deviceType.value = DeviceType.ble; + } } /// 获取已连接设备的信息 @@ -603,9 +575,6 @@ class BluetoothService extends GetxService { 'deviceType': _deviceType.value.toString().split('.').last }; - // 更新最后连接的设备信息 - _lastConnectedDeviceInfo.value = deviceInfo; - return deviceInfo; } @@ -616,123 +585,17 @@ class BluetoothService extends GetxService { } } - /// 一个综合方法,查找且尝试使用系统已连接的设备 - /// 针对经典蓝牙耳机优化,避免使用BLE连接 - Future findConnectedSystemDevice() async { - try { - // 先刷新系统设备列表 - await _refreshSystemDevices(); - - // 1. 检查当前是否有已选择且已连接的设备 - if (_selectedDevice.value != null && - _connectionState.value == BluetoothConnectionState.connected) { - Logger.info('使用当前已连接设备: ${_selectedDevice.value!.platformName}'); - return _selectedDevice.value; - } - - // 2. 检查系统已连接设备列表 - if (_connectedDevices.isNotEmpty) { - // 优先选择可能是音频设备的 - BluetoothDevice audioDevice = _connectedDevices.firstWhere( - (device) { - final name = device.platformName.toLowerCase(); - return name.contains('headphone') || - name.contains('earphone') || - name.contains('earbuds') || - name.contains('headset') || - name.contains('speaker') || - name.contains('耳机') || - name.contains('音箱'); - }, - orElse: () => _connectedDevices.first - ); - - Logger.info('在系统已连接设备中找到设备: ${audioDevice.platformName}'); - - // 设置为当前设备并更新状态 - _selectedDevice.value = audioDevice; - _determineDeviceType(audioDevice); - _connectionState.value = BluetoothConnectionState.connected; - _monitorSystemConnectionState(audioDevice); - _updateLastConnectedDeviceInfo(); - - return audioDevice; - } - - // 3. 如果没有已连接设备,检查已配对设备 - return null; - } catch (e) { - Logger.error('查找系统已连接设备失败: $e'); - return null; - } - } - - /// 获取设备电量 - Future getBatteryLevel() async { - if (_selectedDevice.value == null || - _connectionState.value != BluetoothConnectionState.connected) { - return null; - } - - try { - // 对于BLE设备,尝试读取电池服务 - if (_deviceType.value == DeviceType.ble || _deviceType.value == DeviceType.dual) { - // 发现设备服务 - List services = await _selectedDevice.value!.discoverServices(); - - // 查找电池服务 - for (var service in services) { - if (service.uuid.toString().toLowerCase() == BATTERY_SERVICE_UUID) { - // 查找电池特征 - for (var characteristic in service.characteristics) { - if (characteristic.uuid.toString().toLowerCase() == BATTERY_CHARACTERISTIC_UUID) { - // 读取电池电量 - List value = await characteristic.read(); - if (value.isNotEmpty) { - return value[0]; // 电量百分比 - } - } - } - } - } - } - - // 对于经典蓝牙设备,可能需要平台特定的方法 - if (_deviceType.value == DeviceType.classicAudio) { - // 这里需要使用平台通道或其他插件获取经典蓝牙设备电量 - // 目前根据设备名称特征尝试估计电量 - // 部分设备会在名称后附加电量信息,如"XXX耳机(80%)" - if (_selectedDevice.value != null) { - final deviceName = _selectedDevice.value!.platformName; - final percentRegex = RegExp(r'(\d+)%'); - final match = percentRegex.firstMatch(deviceName); - if (match != null && match.groupCount >= 1) { - final percentStr = match.group(1); - if (percentStr != null) { - final percent = int.tryParse(percentStr); - if (percent != null && percent >= 0 && percent <= 100) { - return percent; - } - } - } - } - Logger.info('无法从设备名称中提取电量信息'); - } - - // 如果以上方法都无法获取电量,返回null - return null; - } catch (e) { - Logger.error('获取设备电量失败: $e'); - return null; - } - } - + /// 清理资源 @override void onClose() { + // 取消所有订阅 _stateSubscription?.cancel(); _scanSubscription?.cancel(); _connectionSubscription?.cancel(); + + // 断开设备连接 disconnectFromDevice(); + super.onClose(); } } \ 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 45ca76b87..6baf4cce7 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -7,22 +7,30 @@ import '../../../core/utils/logger.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:flutter/material.dart'; -enum SearchStatus { - searching, // 正在搜索设备 - found, // 找到设备 - failed, // 搜索失败 - noPermission, // 缺少权限 - needsSystemPairing // 需要在系统设置中配对 +enum PairingStatus { + initial, // 初始状态 + requesting, // 请求权限中 + scanning, // 扫描设备中 + connecting, // 连接设备中 + found, // 找到并连接设备 + noPermission, // 缺少权限 + bluetoothOff, // 蓝牙未开启 + needsSystemPairing, // 需要在系统设置中配对 + failed // 操作失败 } class PairingController extends GetxController { - // 搜索状态 - final _searchStatus = SearchStatus.searching.obs; - SearchStatus get searchStatus => _searchStatus.value; + // 配对状态 + final _pairingStatus = PairingStatus.initial.obs; + PairingStatus get pairingStatus => _pairingStatus.value; - // 搜索动画的点 - final _searchDots = ''.obs; - String get searchDots => _searchDots.value; + // 状态描述(用于UI显示) + final _statusDescription = ''.obs; + String get statusDescription => _statusDescription.value; + + // 加载动画的点 + final _animationDots = ''.obs; + String get animationDots => _animationDots.value; // 动画定时器 Timer? _animationTimer; @@ -40,7 +48,7 @@ class PairingController extends GetxController { bool get isBluetoothAvailable => _isBluetoothAvailable.value; bool get isBluetoothOn => _isBluetoothOn.value; - // 错误消息 + // 错误消息(包含解决方案提示) final _errorMessage = ''.obs; String get errorMessage => _errorMessage.value; @@ -52,236 +60,333 @@ class PairingController extends GetxController { final _pairedDevices = [].obs; List get pairedDevices => _pairedDevices; - // 重试计数 - int _retryCount = 0; + // 重试计数(分别为不同操作设置) + int _initRetryCount = 0; + int _scanRetryCount = 0; + int _connectRetryCount = 0; static const int MAX_RETRY_COUNT = 3; @override void onInit() { super.onInit(); - // 启动搜索动画 - _startSearchingAnimation(); - - // 初始化蓝牙服务 - _initBluetoothService(); + _initializeProcess(); } @override void onClose() { - _stopSearchingAnimation(); + _stopAnimation(); + _bluetoothService.stopScan(); super.onClose(); } - // 初始化蓝牙服务 - Future _initBluetoothService() async { + // 初始化流程 + Future _initializeProcess() async { + // 启动动画 + _startAnimation(); + _updateStatus(PairingStatus.initial, '正在初始化...'); + + try { + // 获取或初始化蓝牙服务 + await _getBluetoothService(); + + // 先检查权限,再处理后续流程 + await _checkAndRequestPermissions(); + } catch (e) { + Logger.error('初始化过程失败: $e'); + _updateStatus(PairingStatus.failed, '初始化失败'); + _setError('初始化蓝牙服务失败,请重试', '可能是蓝牙服务暂时不可用'); + } + } + + // 获取蓝牙服务 + Future _getBluetoothService() async { try { - // 获取蓝牙服务实例 + // 检查是否已注册 if (Get.isRegistered()) { _bluetoothService = Get.find(); - // 如果蓝牙服务已初始化,直接开始查找设备 - if (_bluetoothService.isInitialized) { - _setupBluetoothListeners(); - } else { - // 等待蓝牙服务初始化完成 - ever(_bluetoothService.isInitializedObs, (bool initialized) { - if (initialized) { - _setupBluetoothListeners(); - } - }); + // 如果服务未初始化,等待初始化完成 + if (!_bluetoothService.isInitialized) { + // 监听初始化状态变化 + await _waitForInitialization(); } } else { - // 初始化蓝牙服务 + // 初始化并注册服务 _bluetoothService = await Get.put(BluetoothService()).init(); - _setupBluetoothListeners(); } - } catch (e) { - Logger.error('初始化蓝牙服务失败: $e'); - _errorMessage.value = '初始化蓝牙服务失败'; - _searchStatus.value = SearchStatus.failed; - _stopSearchingAnimation(); - // 重试初始化 - _retryInitialization(); + // 设置蓝牙状态监听 + _setupBluetoothListeners(); + } catch (e) { + Logger.error('获取蓝牙服务失败: $e'); + throw Exception('获取蓝牙服务失败: $e'); + } + } + + // 等待蓝牙服务初始化完成 + Future _waitForInitialization() async { + // 使用 Completer 将事件监听转换为 Future + final Completer completer = Completer(); + late final Worker worker; + + // 一次性监听 + worker = ever(_bluetoothService.isInitializedObs, (bool initialized) { + if (initialized) { + completer.complete(); + worker.dispose(); // 完成后取消监听 + } + }); + + // 如果已经初始化,直接完成 + if (_bluetoothService.isInitialized) { + completer.complete(); + worker.dispose(); } + + // 设置超时 + return completer.future.timeout( + const Duration(seconds: 10), + onTimeout: () { + worker.dispose(); + throw TimeoutException('等待蓝牙服务初始化超时'); + }, + ); } - // 设置蓝牙监听器 + // 设置蓝牙状态监听 void _setupBluetoothListeners() { - // 监听蓝牙状态变化 + // 监听蓝牙可用性 + _isBluetoothAvailable.value = _bluetoothService.isAvailable; ever(_bluetoothService.isAvailableObs, (bool available) { _isBluetoothAvailable.value = available; + + if (!available) { + _updateStatus(PairingStatus.failed, '蓝牙不可用'); + _setError('蓝牙不可用', '请检查设备是否支持蓝牙功能'); + } }); + // 监听蓝牙开关状态 + _isBluetoothOn.value = _bluetoothService.isOn; ever(_bluetoothService.isOnObs, (bool on) { _isBluetoothOn.value = on; - // 如果蓝牙开启,开始查找设备 + // 蓝牙状态改变时处理 if (on) { - _findConnectedDevices(); + if (_pairingStatus.value == PairingStatus.bluetoothOff) { + // 如果蓝牙刚被打开,继续配对流程 + _continueAfterBluetoothOn(); + } } else { - _errorMessage.value = '请打开蓝牙以搜索设备'; - _searchStatus.value = SearchStatus.failed; - _stopSearchingAnimation(); + // 蓝牙关闭,更新状态 + _updateStatus(PairingStatus.bluetoothOff, '蓝牙已关闭'); + _setError('蓝牙已关闭', '请开启蓝牙后再试'); } }); + } + + // 检查并请求权限 + Future _checkAndRequestPermissions() async { + _updateStatus(PairingStatus.requesting, '正在请求权限...'); - // 如果蓝牙已经开启,直接开始查找 - if (_bluetoothService.isOn) { - _isBluetoothOn.value = true; - _findConnectedDevices(); - } else { - _errorMessage.value = '请打开蓝牙以搜索设备'; + try { + bool hasPermissions = await _bluetoothService.requestPermissions(); + + if (!hasPermissions) { + // 权限被拒绝 + _handlePermissionDenied(); + return; + } + + // 权限已授予,检查蓝牙状态 + if (!_bluetoothService.isOn) { + _updateStatus(PairingStatus.bluetoothOff, '蓝牙已关闭'); + _setError('蓝牙已关闭', '请开启蓝牙后再试'); + return; + } + + // 开始查找设备 + _startFindingDevices(); + } catch (e) { + Logger.error('权限请求失败: $e'); + _updateStatus(PairingStatus.noPermission, '权限请求失败'); + _setError('权限请求失败', '请手动授予蓝牙和位置权限'); } } - // 重试初始化 - void _retryInitialization() { - if (_retryCount < MAX_RETRY_COUNT) { - _retryCount++; - Logger.info('重试初始化蓝牙服务 (${_retryCount}/${MAX_RETRY_COUNT})'); + // 处理权限被拒绝的情况 + void _handlePermissionDenied() async { + // 检查是否永久拒绝 + bool isPermanentlyDenied = false; + + if (GetPlatform.isAndroid) { + PermissionStatus bluetoothScan = await Permission.bluetoothScan.status; + PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.status; + PermissionStatus location = await Permission.location.status; - Future.delayed(const Duration(seconds: 2), () { - _initBluetoothService(); - }); + // 如果任一权限被永久拒绝 + isPermanentlyDenied = + bluetoothScan.isPermanentlyDenied || + bluetoothConnect.isPermanentlyDenied || + location.isPermanentlyDenied; + } + + _updateStatus(PairingStatus.noPermission, '缺少必要权限'); + + if (isPermanentlyDenied) { + _setError('缺少必要权限', '您已永久拒绝权限,请前往系统设置中手动开启'); + // 显示前往设置的提示 + _showGoToSettingsDialog(); + } else { + _setError('缺少必要权限', '请授予蓝牙和位置权限以继续'); } } - // 启动搜索动画 - void _startSearchingAnimation() { - _stopSearchingAnimation(); // 清除已有的定时器 - - int count = 0; - _animationTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { - count = (count + 1) % 4; - _searchDots.value = '.' * count; - }); + // 显示前往设置的对话框 + void _showGoToSettingsDialog() { + Get.dialog( + AlertDialog( + title: const Text('需要权限'), + content: const Text('蓝牙搜索需要特定权限,请前往系统设置开启'), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('稍后'), + ), + TextButton( + onPressed: () { + Get.back(); + goToAppSettings(); + }, + child: const Text('前往设置'), + ), + ], + ), + barrierDismissible: false, + ); } - // 停止搜索动画 - void _stopSearchingAnimation() { - _animationTimer?.cancel(); - _animationTimer = null; - _searchDots.value = ''; + // 蓝牙开启后继续流程 + void _continueAfterBluetoothOn() { + if (_bluetoothService.permissionsGranted) { + _startFindingDevices(); + } else { + _checkAndRequestPermissions(); + } } - - // 请求必要权限 - Future _requestPermissions() async { - try { - return await _bluetoothService.requestPermissions(); - } catch (e) { - Logger.error('请求权限失败: $e'); - return false; + + // 开始查找设备 + Future _startFindingDevices() async { + // 先检查上次连接的设备 + if (_checkLastConnectedDevice()) { + return; } + + // 查找系统已连接的设备 + await _findSystemConnectedDevices(); } - // 打开应用设置 - void goToAppSettings() async { - try { - await openAppSettings(); - } catch (e) { - Logger.error('打开应用设置失败: $e'); - Get.snackbar( - '提示', - '无法打开应用设置,请手动打开系统设置授予权限', - snackPosition: SnackPosition.TOP, - backgroundColor: Colors.red[100], - colorText: Colors.red[800], - duration: const Duration(seconds: 3), - ); + // 检查上次连接的设备 + bool _checkLastConnectedDevice() { + // 检查是否有上次连接的设备信息 + if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty && + _bluetoothService.lastConnectedDeviceInfo['connected'] == true) { + Logger.info('使用上次连接的设备信息'); + _deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo; + _onDeviceConnected(); + return true; } + return false; } - // 查找已连接的设备 - Future _findConnectedDevices() async { + // 查找系统已连接的设备 + Future _findSystemConnectedDevices() async { + _updateStatus(PairingStatus.scanning, '正在查找已连接设备...'); + try { - _searchStatus.value = SearchStatus.searching; - - // 检查权限 - bool hasPermissions = await _requestPermissions(); - if (!hasPermissions) { - Logger.warning('没有足够的蓝牙权限'); - _searchStatus.value = SearchStatus.noPermission; - _stopSearchingAnimation(); - return; - } - - // 1. 先检查是否有上次连接的设备信息 - if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty && - _bluetoothService.lastConnectedDeviceInfo['connected'] == true) { - Logger.info('使用上次连接的设备信息'); - _deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo; - _onDeviceFound(); - return; - } - - // 2. 查找系统已连接的设备 - Logger.info('查找系统已连接的设备'); + // 查找系统已连接的设备 BluetoothDevice? device = await _bluetoothService.findConnectedSystemDevice() - .timeout(const Duration(seconds: 5), onTimeout: () { - Logger.error('获取已连接设备超时'); - return null; - }); + .timeout(const Duration(seconds: 10), onTimeout: () => null); if (device != null) { Logger.info('找到系统已连接设备'); _deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); - _onDeviceFound(); + _onDeviceConnected(); return; } - // 3. 如果没有找到已连接设备,提示用户选择 - // 获取已配对设备列表 - _pairedDevices.value = _bluetoothService.systemPairedDevices; - - // 如果有已配对的设备,直接尝试连接第一个 - if (_pairedDevices.isNotEmpty) { - Logger.info('找到已配对设备,尝试连接'); - - // 尝试连接第一个配对设备 - await tryConnectDevice(_pairedDevices.first); - } else { - // 如果没有已配对设备,开始扫描附近设备 - await _scanForDevices(); - } + // 如果没有找到已连接设备,检查已配对设备 + await _checkPairedDevices(); } catch (e) { - Logger.error('查找设备失败: $e'); - _errorMessage.value = '查找设备失败'; - _searchStatus.value = SearchStatus.failed; - _stopSearchingAnimation(); - - // 重试查找设备 - _retryFindDevices(); + Logger.error('查找系统连接设备失败: $e'); + // 继续查找已配对设备,不立即失败 + await _checkPairedDevices(); } } - // 重试查找设备 - void _retryFindDevices() { - if (_retryCount < MAX_RETRY_COUNT) { - _retryCount++; - Logger.info('重试查找设备 (${_retryCount}/${MAX_RETRY_COUNT})'); + // 检查已配对设备 + Future _checkPairedDevices() async { + _updateStatus(PairingStatus.scanning, '正在检查已配对设备...'); + + // 获取已配对设备列表 + _pairedDevices.value = _bluetoothService.systemPairedDevices; + + // 如果有已配对的设备,尝试连接 + if (_pairedDevices.isNotEmpty) { + Logger.info('找到${_pairedDevices.length}个已配对设备,尝试连接'); - Future.delayed(const Duration(seconds: 2), () { - _findConnectedDevices(); - }); + // 根据设备名称过滤可能的音频设备 + BluetoothDevice deviceToConnect = _findBestAudioDevice(_pairedDevices) ?? _pairedDevices.first; + + await _connectToDevice(deviceToConnect); + } else { + // 如果没有已配对设备,开始扫描附近设备 + await _scanForDevices(); + } + } + + // 在设备列表中查找最可能的音频设备 + BluetoothDevice? _findBestAudioDevice(List devices) { + if (devices.isEmpty) return null; + + for (var device in devices) { + String name = device.platformName.toLowerCase(); + if (name.contains('headphone') || + name.contains('earphone') || + name.contains('earbuds') || + name.contains('headset') || + name.contains('speaker') || + name.contains('audio') || + name.contains('耳机') || + name.contains('音箱')) { + return device; + } } + return null; } // 扫描附近的蓝牙设备 Future _scanForDevices() async { + _updateStatus(PairingStatus.scanning, '正在扫描附近设备...'); + _scanRetryCount = 0; + try { - Logger.info('开始扫描附近设备'); - _searchStatus.value = SearchStatus.searching; - _startSearchingAnimation(); + // 确保先停止之前的扫描 + await _bluetoothService.stopScan(); // 开始扫描 List results = await _bluetoothService.startScan( - timeout: const Duration(seconds: 10) + timeout: const Duration(seconds: 12) ); _scannedDevices.value = results; - // 过滤出可能是耳机的设备 + if (results.isEmpty) { + _handleNoDevicesFound(); + return; + } + + // 过滤可能的音频设备 List audioDevices = results.where((result) { String name = result.device.platformName.toLowerCase(); return name.contains('headphone') || @@ -294,141 +399,169 @@ class PairingController extends GetxController { name.contains('音箱'); }).toList(); + // 按信号强度排序 if (audioDevices.isNotEmpty) { - // 按信号强度排序 audioDevices.sort((a, b) => b.rssi.compareTo(a.rssi)); - Logger.info('找到可能的音频设备'); + Logger.info('找到${audioDevices.length}个可能的音频设备'); - // 直接尝试连接信号最强的音频设备 - await tryConnectDevice(audioDevices.first.device); + // 连接信号最强的音频设备 + await _connectToDevice(audioDevices.first.device); } else if (results.isNotEmpty) { - // 如果没有明确的音频设备但有其他设备,尝试连接信号最强的设备 + // 如果没有明确的音频设备但有其他设备,按信号排序 results.sort((a, b) => b.rssi.compareTo(a.rssi)); Logger.info('未找到明确的音频设备,尝试连接信号最强的设备'); - await tryConnectDevice(results.first.device); - } else { - // 未找到任何设备 - _errorMessage.value = '未找到蓝牙设备,请确保设备已开启并在附近'; - _searchStatus.value = SearchStatus.failed; - _stopSearchingAnimation(); - - // 重试扫描 - _retryScan(); + await _connectToDevice(results.first.device); } } catch (e) { Logger.error('扫描设备失败: $e'); - _errorMessage.value = '扫描设备失败'; - _searchStatus.value = SearchStatus.failed; - _stopSearchingAnimation(); - - // 重试扫描 - _retryScan(); + _updateStatus(PairingStatus.failed, '扫描失败'); + _setError('扫描设备失败', '请确保蓝牙已开启并重试'); } } - // 重试扫描 - void _retryScan() { - if (_retryCount < MAX_RETRY_COUNT) { - _retryCount++; - Logger.info('重试扫描设备 (${_retryCount}/${MAX_RETRY_COUNT})'); + // 处理未找到设备的情况 + void _handleNoDevicesFound() { + if (_scanRetryCount < MAX_RETRY_COUNT) { + _scanRetryCount++; + Logger.info('未找到设备,自动重试 (${_scanRetryCount}/${MAX_RETRY_COUNT})'); + _updateStatus(PairingStatus.scanning, '未找到设备,重试中...'); - Future.delayed(const Duration(seconds: 2), () { - _scanForDevices(); - }); + // 延迟2秒后重试 + Future.delayed(const Duration(seconds: 2), _scanForDevices); + } else { + _updateStatus(PairingStatus.failed, '未找到设备'); + _setError('未找到任何蓝牙设备', '请确保设备已开启并在附近,或点击刷新重试'); } } - // 尝试连接特定设备 - Future tryConnectDevice(BluetoothDevice device) async { + // 连接到指定设备 + Future _connectToDevice(BluetoothDevice device) async { + _updateStatus(PairingStatus.connecting, '正在连接到 "${device.platformName}"...'); + _connectRetryCount = 0; + try { - Logger.info('尝试连接设备'); - _searchStatus.value = SearchStatus.searching; - _startSearchingAnimation(); - _errorMessage.value = ''; + // 停止当前扫描 + await _bluetoothService.stopScan(); bool connected = await _bluetoothService.connectToDevice(device); if (connected) { - Logger.info('连接设备成功'); + Logger.info('成功连接到设备: ${device.platformName}'); _deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); - _onDeviceFound(); + _onDeviceConnected(); } else { - Logger.error('连接设备失败'); - - // 检查设备类型 - if (_bluetoothService.deviceType == DeviceType.classicAudio) { - // 对于经典蓝牙音频设备,提示用户在系统设置中配对 - _searchStatus.value = SearchStatus.needsSystemPairing; - _errorMessage.value = '这是音频设备,需要在系统设置中配对并连接'; - } else { - // 其他类型设备连接失败 - _searchStatus.value = SearchStatus.failed; - _errorMessage.value = '连接设备失败,请重试或检查设备状态'; - } - _stopSearchingAnimation(); - - // 重试连接 - _retryConnect(device); + _handleConnectionFailed(device); } } catch (e) { Logger.error('连接设备失败: $e'); - _errorMessage.value = '连接设备失败'; - _searchStatus.value = SearchStatus.failed; - _stopSearchingAnimation(); - - // 重试连接 - _retryConnect(device); + _handleConnectionFailed(device); } } - // 重试连接 - void _retryConnect(BluetoothDevice device) { - if (_retryCount < MAX_RETRY_COUNT) { - _retryCount++; - Logger.info('重试连接设备 (${_retryCount}/${MAX_RETRY_COUNT})'); + // 处理连接失败 + void _handleConnectionFailed(BluetoothDevice device) { + // 检查设备类型 + if (_bluetoothService.deviceType == DeviceType.classicAudio) { + // 经典蓝牙音频设备需要在系统设置中配对 + _updateStatus(PairingStatus.needsSystemPairing, '需要系统配对'); + _setError('这是音频设备,需要在系统设置中配对', '请前往系统蓝牙设置完成配对'); + } else if (_connectRetryCount < MAX_RETRY_COUNT) { + // 自动重试连接 + _connectRetryCount++; + Logger.info('连接失败,自动重试 (${_connectRetryCount}/${MAX_RETRY_COUNT})'); + _updateStatus(PairingStatus.connecting, '连接失败,重试中...'); - Future.delayed(const Duration(seconds: 2), () { - tryConnectDevice(device); - }); + // 延迟2秒后重试 + Future.delayed(const Duration(seconds: 2), () => _connectToDevice(device)); + } else { + // 超过重试次数 + _updateStatus(PairingStatus.failed, '连接失败'); + _setError('连接设备失败', '请确保设备已开启且处于配对模式,或点击刷新重试'); } } - // 设备找到并连接成功后的处理 - void _onDeviceFound() { - _searchStatus.value = SearchStatus.found; - _stopSearchingAnimation(); + // 设备连接成功 + void _onDeviceConnected() { + _updateStatus(PairingStatus.found, '设备连接成功'); - // 更新日志并延迟跳转到主页 - Logger.info('设备连接成功,准备跳转到主页'); + // 延迟跳转到主页 Timer(const Duration(seconds: 1), () { Get.offAllNamed(Routes.home, arguments: _deviceInfo.value); }); } - // 手动刷新/重新搜索 + // 更新状态(内部方法) + void _updateStatus(PairingStatus status, String description) { + _pairingStatus.value = status; + _statusDescription.value = description; + + // 根据状态自动控制动画 + if ([PairingStatus.initial, PairingStatus.requesting, PairingStatus.scanning, PairingStatus.connecting].contains(status)) { + _startAnimation(); + } else { + _stopAnimation(); + } + } + + // 设置错误信息 + void _setError(String message, String solution) { + _errorMessage.value = '$message\n$solution'; + } + + // 动画控制 + void _startAnimation() { + _stopAnimation(); // 确保先停止已有动画 + + int count = 0; + _animationTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { + count = (count + 1) % 4; + _animationDots.value = '.' * count; + }); + } + + void _stopAnimation() { + _animationTimer?.cancel(); + _animationTimer = null; + _animationDots.value = ''; + } + + // 公开方法 + + // 刷新搜索 void refreshSearch() { - // 停止当前扫描(如果有) + // 停止当前动画和扫描 + _stopAnimation(); _bluetoothService.stopScan(); // 重置重试计数 - _retryCount = 0; + _scanRetryCount = 0; + _connectRetryCount = 0; - // 更新UI状态 - _startSearchingAnimation(); - _searchStatus.value = SearchStatus.searching; - _errorMessage.value = ''; - - // 重新开始查找过程 - _findConnectedDevices(); + // 重新开始流程 + _initializeProcess(); + } + + // 打开应用设置 + void goToAppSettings() async { + try { + await openAppSettings(); + } catch (e) { + Logger.error('打开应用设置失败: $e'); + Get.snackbar( + '提示', + '无法自动打开设置,请手动前往系统设置授予权限', + snackPosition: SnackPosition.TOP, + backgroundColor: Colors.red[100], + colorText: Colors.red[800], + duration: const Duration(seconds: 3), + ); + } } // 跳转到系统蓝牙设置 void openSystemBluetoothSettings() async { try { - Logger.info('尝试打开系统蓝牙设置'); - - // 不同平台需要不同的处理 if (GetPlatform.isAndroid) { await openAppSettings(); } else { @@ -443,10 +576,10 @@ class PairingController extends GetxController { ); } } catch (e) { - Logger.error('打开系统蓝牙设置失败: $e'); + Logger.error('打开系统设置失败: $e'); Get.snackbar( '提示', - '无法打开系统设置,请手动打开蓝牙设置并配对设备', + '无法自动打开设置,请手动前往系统蓝牙设置', snackPosition: SnackPosition.TOP, backgroundColor: Colors.red[100], colorText: Colors.red[800], @@ -455,13 +588,18 @@ class PairingController extends GetxController { } } - // 稍后再说 + // 跳过连接 void skipConnecting() { - // 停止当前扫描 + // 停止扫描和动画 _bluetoothService.stopScan(); - _stopSearchingAnimation(); + _stopAnimation(); // 直接跳转到主页 Get.offAllNamed(Routes.home); } + + // 手动连接特定设备 + void connectToDevice(BluetoothDevice device) { + _connectToDevice(device); + } } \ No newline at end of file diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index b6814481f..40fd7e36c 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -171,12 +171,15 @@ class PairingView extends GetView { // 搜索状态 Widget _buildSearchStatus(bool isDarkMode) { return Obx(() { - switch (controller.searchStatus) { - case SearchStatus.searching: + switch (controller.pairingStatus) { + case PairingStatus.initial: + case PairingStatus.requesting: + case PairingStatus.scanning: + case PairingStatus.connecting: return Column( children: [ Text( - '寻找设备中${controller.searchDots}', + '${controller.statusDescription}${controller.animationDots}', style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, @@ -193,7 +196,7 @@ class PairingView extends GetView { ), ], ); - case SearchStatus.found: + case PairingStatus.found: return Column( children: [ Row( @@ -221,7 +224,7 @@ class PairingView extends GetView { ), ], ); - case SearchStatus.noPermission: + case PairingStatus.noPermission: return Column( children: [ Row( @@ -263,7 +266,36 @@ class PairingView extends GetView { ), ], ); - case SearchStatus.failed: + case PairingStatus.bluetoothOff: + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.bluetooth_disabled, color: Colors.orange, size: 20.sp), + SizedBox(width: 8.w), + Text( + '蓝牙已关闭', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: Colors.orange, + ), + ), + ], + ), + SizedBox(height: 8.h), + Text( + controller.errorMessage, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white70 : Colors.black54, + ), + textAlign: TextAlign.center, + ), + ], + ); + case PairingStatus.failed: return Column( children: [ Row( @@ -272,7 +304,7 @@ class PairingView extends GetView { Icon(Icons.error, color: Colors.red, size: 20.sp), SizedBox(width: 8.w), Text( - '未找到设备', + controller.statusDescription.isEmpty ? '未找到设备' : controller.statusDescription, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.w600, @@ -293,7 +325,7 @@ class PairingView extends GetView { ), ], ); - case SearchStatus.needsSystemPairing: + case PairingStatus.needsSystemPairing: return Column( children: [ Row( diff --git a/pubspec.yaml b/pubspec.yaml index 4d6a5b4a4..89598e4a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,7 @@ dependencies: image_picker: ^1.1.2 audio_waveforms: ^1.3.0 font_awesome_flutter: ^10.8.0 + lottie: ^3.3.1 dev_dependencies: flutter_test: