|
|
@ -7,22 +7,30 @@ import '../../../core/utils/logger.dart'; |
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
import 'package:permission_handler/permission_handler.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
import 'package:flutter/material.dart'; |
|
|
|
|
|
|
|
|
enum SearchStatus { |
|
|
enum PairingStatus { |
|
|
searching, // 正在搜索设备 |
|
|
initial, // 初始状态 |
|
|
found, // 找到设备 |
|
|
requesting, // 请求权限中 |
|
|
failed, // 搜索失败 |
|
|
scanning, // 扫描设备中 |
|
|
noPermission, // 缺少权限 |
|
|
connecting, // 连接设备中 |
|
|
needsSystemPairing // 需要在系统设置中配对 |
|
|
found, // 找到并连接设备 |
|
|
|
|
|
noPermission, // 缺少权限 |
|
|
|
|
|
bluetoothOff, // 蓝牙未开启 |
|
|
|
|
|
needsSystemPairing, // 需要在系统设置中配对 |
|
|
|
|
|
failed // 操作失败 |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class PairingController extends GetxController { |
|
|
class PairingController extends GetxController { |
|
|
// 搜索状态 |
|
|
// 配对状态 |
|
|
final _searchStatus = SearchStatus.searching.obs; |
|
|
final _pairingStatus = PairingStatus.initial.obs; |
|
|
SearchStatus get searchStatus => _searchStatus.value; |
|
|
PairingStatus get pairingStatus => _pairingStatus.value; |
|
|
|
|
|
|
|
|
// 搜索动画的点 |
|
|
// 状态描述(用于UI显示) |
|
|
final _searchDots = ''.obs; |
|
|
final _statusDescription = ''.obs; |
|
|
String get searchDots => _searchDots.value; |
|
|
String get statusDescription => _statusDescription.value; |
|
|
|
|
|
|
|
|
|
|
|
// 加载动画的点 |
|
|
|
|
|
final _animationDots = ''.obs; |
|
|
|
|
|
String get animationDots => _animationDots.value; |
|
|
|
|
|
|
|
|
// 动画定时器 |
|
|
// 动画定时器 |
|
|
Timer? _animationTimer; |
|
|
Timer? _animationTimer; |
|
|
@ -40,7 +48,7 @@ class PairingController extends GetxController { |
|
|
bool get isBluetoothAvailable => _isBluetoothAvailable.value; |
|
|
bool get isBluetoothAvailable => _isBluetoothAvailable.value; |
|
|
bool get isBluetoothOn => _isBluetoothOn.value; |
|
|
bool get isBluetoothOn => _isBluetoothOn.value; |
|
|
|
|
|
|
|
|
// 错误消息 |
|
|
// 错误消息(包含解决方案提示) |
|
|
final _errorMessage = ''.obs; |
|
|
final _errorMessage = ''.obs; |
|
|
String get errorMessage => _errorMessage.value; |
|
|
String get errorMessage => _errorMessage.value; |
|
|
|
|
|
|
|
|
@ -52,236 +60,333 @@ class PairingController extends GetxController { |
|
|
final _pairedDevices = <BluetoothDevice>[].obs; |
|
|
final _pairedDevices = <BluetoothDevice>[].obs; |
|
|
List<BluetoothDevice> get pairedDevices => _pairedDevices; |
|
|
List<BluetoothDevice> get pairedDevices => _pairedDevices; |
|
|
|
|
|
|
|
|
// 重试计数 |
|
|
// 重试计数(分别为不同操作设置) |
|
|
int _retryCount = 0; |
|
|
int _initRetryCount = 0; |
|
|
|
|
|
int _scanRetryCount = 0; |
|
|
|
|
|
int _connectRetryCount = 0; |
|
|
static const int MAX_RETRY_COUNT = 3; |
|
|
static const int MAX_RETRY_COUNT = 3; |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
void onInit() { |
|
|
void onInit() { |
|
|
super.onInit(); |
|
|
super.onInit(); |
|
|
// 启动搜索动画 |
|
|
_initializeProcess(); |
|
|
_startSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 初始化蓝牙服务 |
|
|
|
|
|
_initBluetoothService(); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@override |
|
|
@override |
|
|
void onClose() { |
|
|
void onClose() { |
|
|
_stopSearchingAnimation(); |
|
|
_stopAnimation(); |
|
|
|
|
|
_bluetoothService.stopScan(); |
|
|
super.onClose(); |
|
|
super.onClose(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 初始化蓝牙服务 |
|
|
// 初始化流程 |
|
|
Future<void> _initBluetoothService() async { |
|
|
Future<void> _initializeProcess() async { |
|
|
|
|
|
// 启动动画 |
|
|
|
|
|
_startAnimation(); |
|
|
|
|
|
_updateStatus(PairingStatus.initial, '正在初始化...'); |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 获取蓝牙服务实例 |
|
|
// 获取或初始化蓝牙服务 |
|
|
|
|
|
await _getBluetoothService(); |
|
|
|
|
|
|
|
|
|
|
|
// 先检查权限,再处理后续流程 |
|
|
|
|
|
await _checkAndRequestPermissions(); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('初始化过程失败: $e'); |
|
|
|
|
|
_updateStatus(PairingStatus.failed, '初始化失败'); |
|
|
|
|
|
_setError('初始化蓝牙服务失败,请重试', '可能是蓝牙服务暂时不可用'); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 获取蓝牙服务 |
|
|
|
|
|
Future<void> _getBluetoothService() async { |
|
|
|
|
|
try { |
|
|
|
|
|
// 检查是否已注册 |
|
|
if (Get.isRegistered<BluetoothService>()) { |
|
|
if (Get.isRegistered<BluetoothService>()) { |
|
|
_bluetoothService = Get.find<BluetoothService>(); |
|
|
_bluetoothService = Get.find<BluetoothService>(); |
|
|
|
|
|
|
|
|
// 如果蓝牙服务已初始化,直接开始查找设备 |
|
|
// 如果服务未初始化,等待初始化完成 |
|
|
if (_bluetoothService.isInitialized) { |
|
|
if (!_bluetoothService.isInitialized) { |
|
|
_setupBluetoothListeners(); |
|
|
// 监听初始化状态变化 |
|
|
} else { |
|
|
await _waitForInitialization(); |
|
|
// 等待蓝牙服务初始化完成 |
|
|
|
|
|
ever(_bluetoothService.isInitializedObs, (bool initialized) { |
|
|
|
|
|
if (initialized) { |
|
|
|
|
|
_setupBluetoothListeners(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
// 初始化蓝牙服务 |
|
|
// 初始化并注册服务 |
|
|
_bluetoothService = await Get.put(BluetoothService()).init(); |
|
|
_bluetoothService = await Get.put(BluetoothService()).init(); |
|
|
_setupBluetoothListeners(); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置蓝牙状态监听 |
|
|
|
|
|
_setupBluetoothListeners(); |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('初始化蓝牙服务失败: $e'); |
|
|
Logger.error('获取蓝牙服务失败: $e'); |
|
|
_errorMessage.value = '初始化蓝牙服务失败'; |
|
|
throw Exception('获取蓝牙服务失败: $e'); |
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
} |
|
|
_stopSearchingAnimation(); |
|
|
} |
|
|
|
|
|
|
|
|
// 重试初始化 |
|
|
// 等待蓝牙服务初始化完成 |
|
|
_retryInitialization(); |
|
|
Future<void> _waitForInitialization() async { |
|
|
|
|
|
// 使用 Completer 将事件监听转换为 Future |
|
|
|
|
|
final Completer<void> completer = Completer<void>(); |
|
|
|
|
|
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() { |
|
|
void _setupBluetoothListeners() { |
|
|
// 监听蓝牙状态变化 |
|
|
// 监听蓝牙可用性 |
|
|
|
|
|
_isBluetoothAvailable.value = _bluetoothService.isAvailable; |
|
|
ever(_bluetoothService.isAvailableObs, (bool available) { |
|
|
ever(_bluetoothService.isAvailableObs, (bool available) { |
|
|
_isBluetoothAvailable.value = available; |
|
|
_isBluetoothAvailable.value = available; |
|
|
|
|
|
|
|
|
|
|
|
if (!available) { |
|
|
|
|
|
_updateStatus(PairingStatus.failed, '蓝牙不可用'); |
|
|
|
|
|
_setError('蓝牙不可用', '请检查设备是否支持蓝牙功能'); |
|
|
|
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 监听蓝牙开关状态 |
|
|
|
|
|
_isBluetoothOn.value = _bluetoothService.isOn; |
|
|
ever(_bluetoothService.isOnObs, (bool on) { |
|
|
ever(_bluetoothService.isOnObs, (bool on) { |
|
|
_isBluetoothOn.value = on; |
|
|
_isBluetoothOn.value = on; |
|
|
|
|
|
|
|
|
// 如果蓝牙开启,开始查找设备 |
|
|
// 蓝牙状态改变时处理 |
|
|
if (on) { |
|
|
if (on) { |
|
|
_findConnectedDevices(); |
|
|
if (_pairingStatus.value == PairingStatus.bluetoothOff) { |
|
|
|
|
|
// 如果蓝牙刚被打开,继续配对流程 |
|
|
|
|
|
_continueAfterBluetoothOn(); |
|
|
|
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
_errorMessage.value = '请打开蓝牙以搜索设备'; |
|
|
// 蓝牙关闭,更新状态 |
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
_updateStatus(PairingStatus.bluetoothOff, '蓝牙已关闭'); |
|
|
_stopSearchingAnimation(); |
|
|
_setError('蓝牙已关闭', '请开启蓝牙后再试'); |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 如果蓝牙已经开启,直接开始查找 |
|
|
|
|
|
if (_bluetoothService.isOn) { |
|
|
|
|
|
_isBluetoothOn.value = true; |
|
|
|
|
|
_findConnectedDevices(); |
|
|
|
|
|
} else { |
|
|
|
|
|
_errorMessage.value = '请打开蓝牙以搜索设备'; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重试初始化 |
|
|
// 检查并请求权限 |
|
|
void _retryInitialization() { |
|
|
Future<void> _checkAndRequestPermissions() async { |
|
|
if (_retryCount < MAX_RETRY_COUNT) { |
|
|
_updateStatus(PairingStatus.requesting, '正在请求权限...'); |
|
|
_retryCount++; |
|
|
|
|
|
Logger.info('重试初始化蓝牙服务 (${_retryCount}/${MAX_RETRY_COUNT})'); |
|
|
try { |
|
|
|
|
|
bool hasPermissions = await _bluetoothService.requestPermissions(); |
|
|
|
|
|
|
|
|
|
|
|
if (!hasPermissions) { |
|
|
|
|
|
// 权限被拒绝 |
|
|
|
|
|
_handlePermissionDenied(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 权限已授予,检查蓝牙状态 |
|
|
|
|
|
if (!_bluetoothService.isOn) { |
|
|
|
|
|
_updateStatus(PairingStatus.bluetoothOff, '蓝牙已关闭'); |
|
|
|
|
|
_setError('蓝牙已关闭', '请开启蓝牙后再试'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
Future.delayed(const Duration(seconds: 2), () { |
|
|
// 开始查找设备 |
|
|
_initBluetoothService(); |
|
|
_startFindingDevices(); |
|
|
}); |
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('权限请求失败: $e'); |
|
|
|
|
|
_updateStatus(PairingStatus.noPermission, '权限请求失败'); |
|
|
|
|
|
_setError('权限请求失败', '请手动授予蓝牙和位置权限'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 启动搜索动画 |
|
|
// 处理权限被拒绝的情况 |
|
|
void _startSearchingAnimation() { |
|
|
void _handlePermissionDenied() async { |
|
|
_stopSearchingAnimation(); // 清除已有的定时器 |
|
|
// 检查是否永久拒绝 |
|
|
|
|
|
bool isPermanentlyDenied = false; |
|
|
|
|
|
|
|
|
|
|
|
if (GetPlatform.isAndroid) { |
|
|
|
|
|
PermissionStatus bluetoothScan = await Permission.bluetoothScan.status; |
|
|
|
|
|
PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.status; |
|
|
|
|
|
PermissionStatus location = await Permission.location.status; |
|
|
|
|
|
|
|
|
|
|
|
// 如果任一权限被永久拒绝 |
|
|
|
|
|
isPermanentlyDenied = |
|
|
|
|
|
bluetoothScan.isPermanentlyDenied || |
|
|
|
|
|
bluetoothConnect.isPermanentlyDenied || |
|
|
|
|
|
location.isPermanentlyDenied; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
int count = 0; |
|
|
_updateStatus(PairingStatus.noPermission, '缺少必要权限'); |
|
|
_animationTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) { |
|
|
|
|
|
count = (count + 1) % 4; |
|
|
if (isPermanentlyDenied) { |
|
|
_searchDots.value = '.' * count; |
|
|
_setError('缺少必要权限', '您已永久拒绝权限,请前往系统设置中手动开启'); |
|
|
}); |
|
|
// 显示前往设置的提示 |
|
|
|
|
|
_showGoToSettingsDialog(); |
|
|
|
|
|
} else { |
|
|
|
|
|
_setError('缺少必要权限', '请授予蓝牙和位置权限以继续'); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 停止搜索动画 |
|
|
// 显示前往设置的对话框 |
|
|
void _stopSearchingAnimation() { |
|
|
void _showGoToSettingsDialog() { |
|
|
_animationTimer?.cancel(); |
|
|
Get.dialog( |
|
|
_animationTimer = null; |
|
|
AlertDialog( |
|
|
_searchDots.value = ''; |
|
|
title: const Text('需要权限'), |
|
|
|
|
|
content: const Text('蓝牙搜索需要特定权限,请前往系统设置开启'), |
|
|
|
|
|
actions: [ |
|
|
|
|
|
TextButton( |
|
|
|
|
|
onPressed: () => Get.back(), |
|
|
|
|
|
child: const Text('稍后'), |
|
|
|
|
|
), |
|
|
|
|
|
TextButton( |
|
|
|
|
|
onPressed: () { |
|
|
|
|
|
Get.back(); |
|
|
|
|
|
goToAppSettings(); |
|
|
|
|
|
}, |
|
|
|
|
|
child: const Text('前往设置'), |
|
|
|
|
|
), |
|
|
|
|
|
], |
|
|
|
|
|
), |
|
|
|
|
|
barrierDismissible: false, |
|
|
|
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 请求必要权限 |
|
|
// 蓝牙开启后继续流程 |
|
|
Future<bool> _requestPermissions() async { |
|
|
void _continueAfterBluetoothOn() { |
|
|
try { |
|
|
if (_bluetoothService.permissionsGranted) { |
|
|
return await _bluetoothService.requestPermissions(); |
|
|
_startFindingDevices(); |
|
|
} catch (e) { |
|
|
} else { |
|
|
Logger.error('请求权限失败: $e'); |
|
|
_checkAndRequestPermissions(); |
|
|
return false; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 打开应用设置 |
|
|
// 开始查找设备 |
|
|
void goToAppSettings() async { |
|
|
Future<void> _startFindingDevices() async { |
|
|
try { |
|
|
// 先检查上次连接的设备 |
|
|
await openAppSettings(); |
|
|
if (_checkLastConnectedDevice()) { |
|
|
} catch (e) { |
|
|
return; |
|
|
Logger.error('打开应用设置失败: $e'); |
|
|
|
|
|
Get.snackbar( |
|
|
|
|
|
'提示', |
|
|
|
|
|
'无法打开应用设置,请手动打开系统设置授予权限', |
|
|
|
|
|
snackPosition: SnackPosition.TOP, |
|
|
|
|
|
backgroundColor: Colors.red[100], |
|
|
|
|
|
colorText: Colors.red[800], |
|
|
|
|
|
duration: const Duration(seconds: 3), |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 查找已连接的设备 |
|
|
// 查找系统已连接的设备 |
|
|
Future<void> _findConnectedDevices() async { |
|
|
await _findSystemConnectedDevices(); |
|
|
try { |
|
|
} |
|
|
_searchStatus.value = SearchStatus.searching; |
|
|
|
|
|
|
|
|
|
|
|
// 检查权限 |
|
|
// 检查上次连接的设备 |
|
|
bool hasPermissions = await _requestPermissions(); |
|
|
bool _checkLastConnectedDevice() { |
|
|
if (!hasPermissions) { |
|
|
// 检查是否有上次连接的设备信息 |
|
|
Logger.warning('没有足够的蓝牙权限'); |
|
|
if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty && |
|
|
_searchStatus.value = SearchStatus.noPermission; |
|
|
_bluetoothService.lastConnectedDeviceInfo['connected'] == true) { |
|
|
_stopSearchingAnimation(); |
|
|
Logger.info('使用上次连接的设备信息'); |
|
|
return; |
|
|
_deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo; |
|
|
} |
|
|
_onDeviceConnected(); |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 1. 先检查是否有上次连接的设备信息 |
|
|
// 查找系统已连接的设备 |
|
|
if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty && |
|
|
Future<void> _findSystemConnectedDevices() async { |
|
|
_bluetoothService.lastConnectedDeviceInfo['connected'] == true) { |
|
|
_updateStatus(PairingStatus.scanning, '正在查找已连接设备...'); |
|
|
Logger.info('使用上次连接的设备信息'); |
|
|
|
|
|
_deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo; |
|
|
|
|
|
_onDeviceFound(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 查找系统已连接的设备 |
|
|
try { |
|
|
Logger.info('查找系统已连接的设备'); |
|
|
// 查找系统已连接的设备 |
|
|
BluetoothDevice? device = await _bluetoothService.findConnectedSystemDevice() |
|
|
BluetoothDevice? device = await _bluetoothService.findConnectedSystemDevice() |
|
|
.timeout(const Duration(seconds: 5), onTimeout: () { |
|
|
.timeout(const Duration(seconds: 10), onTimeout: () => null); |
|
|
Logger.error('获取已连接设备超时'); |
|
|
|
|
|
return null; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (device != null) { |
|
|
if (device != null) { |
|
|
Logger.info('找到系统已连接设备'); |
|
|
Logger.info('找到系统已连接设备'); |
|
|
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); |
|
|
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); |
|
|
_onDeviceFound(); |
|
|
_onDeviceConnected(); |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 3. 如果没有找到已连接设备,提示用户选择 |
|
|
// 如果没有找到已连接设备,检查已配对设备 |
|
|
// 获取已配对设备列表 |
|
|
await _checkPairedDevices(); |
|
|
_pairedDevices.value = _bluetoothService.systemPairedDevices; |
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('查找系统连接设备失败: $e'); |
|
|
|
|
|
// 继续查找已配对设备,不立即失败 |
|
|
|
|
|
await _checkPairedDevices(); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 如果有已配对的设备,直接尝试连接第一个 |
|
|
// 检查已配对设备 |
|
|
if (_pairedDevices.isNotEmpty) { |
|
|
Future<void> _checkPairedDevices() async { |
|
|
Logger.info('找到已配对设备,尝试连接'); |
|
|
_updateStatus(PairingStatus.scanning, '正在检查已配对设备...'); |
|
|
|
|
|
|
|
|
// 尝试连接第一个配对设备 |
|
|
// 获取已配对设备列表 |
|
|
await tryConnectDevice(_pairedDevices.first); |
|
|
_pairedDevices.value = _bluetoothService.systemPairedDevices; |
|
|
} else { |
|
|
|
|
|
// 如果没有已配对设备,开始扫描附近设备 |
|
|
// 如果有已配对的设备,尝试连接 |
|
|
await _scanForDevices(); |
|
|
if (_pairedDevices.isNotEmpty) { |
|
|
} |
|
|
Logger.info('找到${_pairedDevices.length}个已配对设备,尝试连接'); |
|
|
} catch (e) { |
|
|
|
|
|
Logger.error('查找设备失败: $e'); |
|
|
|
|
|
_errorMessage.value = '查找设备失败'; |
|
|
|
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
|
|
|
_stopSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 重试查找设备 |
|
|
// 根据设备名称过滤可能的音频设备 |
|
|
_retryFindDevices(); |
|
|
BluetoothDevice deviceToConnect = _findBestAudioDevice(_pairedDevices) ?? _pairedDevices.first; |
|
|
|
|
|
|
|
|
|
|
|
await _connectToDevice(deviceToConnect); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果没有已配对设备,开始扫描附近设备 |
|
|
|
|
|
await _scanForDevices(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重试查找设备 |
|
|
// 在设备列表中查找最可能的音频设备 |
|
|
void _retryFindDevices() { |
|
|
BluetoothDevice? _findBestAudioDevice(List<BluetoothDevice> devices) { |
|
|
if (_retryCount < MAX_RETRY_COUNT) { |
|
|
if (devices.isEmpty) return null; |
|
|
_retryCount++; |
|
|
|
|
|
Logger.info('重试查找设备 (${_retryCount}/${MAX_RETRY_COUNT})'); |
|
|
for (var device in devices) { |
|
|
|
|
|
String name = device.platformName.toLowerCase(); |
|
|
Future.delayed(const Duration(seconds: 2), () { |
|
|
if (name.contains('headphone') || |
|
|
_findConnectedDevices(); |
|
|
name.contains('earphone') || |
|
|
}); |
|
|
name.contains('earbuds') || |
|
|
|
|
|
name.contains('headset') || |
|
|
|
|
|
name.contains('speaker') || |
|
|
|
|
|
name.contains('audio') || |
|
|
|
|
|
name.contains('耳机') || |
|
|
|
|
|
name.contains('音箱')) { |
|
|
|
|
|
return device; |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return null; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 扫描附近的蓝牙设备 |
|
|
// 扫描附近的蓝牙设备 |
|
|
Future<void> _scanForDevices() async { |
|
|
Future<void> _scanForDevices() async { |
|
|
|
|
|
_updateStatus(PairingStatus.scanning, '正在扫描附近设备...'); |
|
|
|
|
|
_scanRetryCount = 0; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
Logger.info('开始扫描附近设备'); |
|
|
// 确保先停止之前的扫描 |
|
|
_searchStatus.value = SearchStatus.searching; |
|
|
await _bluetoothService.stopScan(); |
|
|
_startSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 开始扫描 |
|
|
// 开始扫描 |
|
|
List<ScanResult> results = await _bluetoothService.startScan( |
|
|
List<ScanResult> results = await _bluetoothService.startScan( |
|
|
timeout: const Duration(seconds: 10) |
|
|
timeout: const Duration(seconds: 12) |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
_scannedDevices.value = results; |
|
|
_scannedDevices.value = results; |
|
|
|
|
|
|
|
|
// 过滤出可能是耳机的设备 |
|
|
if (results.isEmpty) { |
|
|
|
|
|
_handleNoDevicesFound(); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 过滤可能的音频设备 |
|
|
List<ScanResult> audioDevices = results.where((result) { |
|
|
List<ScanResult> audioDevices = results.where((result) { |
|
|
String name = result.device.platformName.toLowerCase(); |
|
|
String name = result.device.platformName.toLowerCase(); |
|
|
return name.contains('headphone') || |
|
|
return name.contains('headphone') || |
|
|
@ -294,141 +399,169 @@ class PairingController extends GetxController { |
|
|
name.contains('音箱'); |
|
|
name.contains('音箱'); |
|
|
}).toList(); |
|
|
}).toList(); |
|
|
|
|
|
|
|
|
|
|
|
// 按信号强度排序 |
|
|
if (audioDevices.isNotEmpty) { |
|
|
if (audioDevices.isNotEmpty) { |
|
|
// 按信号强度排序 |
|
|
|
|
|
audioDevices.sort((a, b) => b.rssi.compareTo(a.rssi)); |
|
|
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) { |
|
|
} else if (results.isNotEmpty) { |
|
|
// 如果没有明确的音频设备但有其他设备,尝试连接信号最强的设备 |
|
|
// 如果没有明确的音频设备但有其他设备,按信号排序 |
|
|
results.sort((a, b) => b.rssi.compareTo(a.rssi)); |
|
|
results.sort((a, b) => b.rssi.compareTo(a.rssi)); |
|
|
Logger.info('未找到明确的音频设备,尝试连接信号最强的设备'); |
|
|
Logger.info('未找到明确的音频设备,尝试连接信号最强的设备'); |
|
|
|
|
|
|
|
|
await tryConnectDevice(results.first.device); |
|
|
await _connectToDevice(results.first.device); |
|
|
} else { |
|
|
|
|
|
// 未找到任何设备 |
|
|
|
|
|
_errorMessage.value = '未找到蓝牙设备,请确保设备已开启并在附近'; |
|
|
|
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
|
|
|
_stopSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 重试扫描 |
|
|
|
|
|
_retryScan(); |
|
|
|
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('扫描设备失败: $e'); |
|
|
Logger.error('扫描设备失败: $e'); |
|
|
_errorMessage.value = '扫描设备失败'; |
|
|
_updateStatus(PairingStatus.failed, '扫描失败'); |
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
_setError('扫描设备失败', '请确保蓝牙已开启并重试'); |
|
|
_stopSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 重试扫描 |
|
|
|
|
|
_retryScan(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重试扫描 |
|
|
// 处理未找到设备的情况 |
|
|
void _retryScan() { |
|
|
void _handleNoDevicesFound() { |
|
|
if (_retryCount < MAX_RETRY_COUNT) { |
|
|
if (_scanRetryCount < MAX_RETRY_COUNT) { |
|
|
_retryCount++; |
|
|
_scanRetryCount++; |
|
|
Logger.info('重试扫描设备 (${_retryCount}/${MAX_RETRY_COUNT})'); |
|
|
Logger.info('未找到设备,自动重试 (${_scanRetryCount}/${MAX_RETRY_COUNT})'); |
|
|
|
|
|
_updateStatus(PairingStatus.scanning, '未找到设备,重试中...'); |
|
|
|
|
|
|
|
|
Future.delayed(const Duration(seconds: 2), () { |
|
|
// 延迟2秒后重试 |
|
|
_scanForDevices(); |
|
|
Future.delayed(const Duration(seconds: 2), _scanForDevices); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
_updateStatus(PairingStatus.failed, '未找到设备'); |
|
|
|
|
|
_setError('未找到任何蓝牙设备', '请确保设备已开启并在附近,或点击刷新重试'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 尝试连接特定设备 |
|
|
// 连接到指定设备 |
|
|
Future<void> tryConnectDevice(BluetoothDevice device) async { |
|
|
Future<void> _connectToDevice(BluetoothDevice device) async { |
|
|
|
|
|
_updateStatus(PairingStatus.connecting, '正在连接到 "${device.platformName}"...'); |
|
|
|
|
|
_connectRetryCount = 0; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
Logger.info('尝试连接设备'); |
|
|
// 停止当前扫描 |
|
|
_searchStatus.value = SearchStatus.searching; |
|
|
await _bluetoothService.stopScan(); |
|
|
_startSearchingAnimation(); |
|
|
|
|
|
_errorMessage.value = ''; |
|
|
|
|
|
|
|
|
|
|
|
bool connected = await _bluetoothService.connectToDevice(device); |
|
|
bool connected = await _bluetoothService.connectToDevice(device); |
|
|
|
|
|
|
|
|
if (connected) { |
|
|
if (connected) { |
|
|
Logger.info('连接设备成功'); |
|
|
Logger.info('成功连接到设备: ${device.platformName}'); |
|
|
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); |
|
|
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo(); |
|
|
_onDeviceFound(); |
|
|
_onDeviceConnected(); |
|
|
} else { |
|
|
} else { |
|
|
Logger.error('连接设备失败'); |
|
|
_handleConnectionFailed(device); |
|
|
|
|
|
|
|
|
// 检查设备类型 |
|
|
|
|
|
if (_bluetoothService.deviceType == DeviceType.classicAudio) { |
|
|
|
|
|
// 对于经典蓝牙音频设备,提示用户在系统设置中配对 |
|
|
|
|
|
_searchStatus.value = SearchStatus.needsSystemPairing; |
|
|
|
|
|
_errorMessage.value = '这是音频设备,需要在系统设置中配对并连接'; |
|
|
|
|
|
} else { |
|
|
|
|
|
// 其他类型设备连接失败 |
|
|
|
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
|
|
|
_errorMessage.value = '连接设备失败,请重试或检查设备状态'; |
|
|
|
|
|
} |
|
|
|
|
|
_stopSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 重试连接 |
|
|
|
|
|
_retryConnect(device); |
|
|
|
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('连接设备失败: $e'); |
|
|
Logger.error('连接设备失败: $e'); |
|
|
_errorMessage.value = '连接设备失败'; |
|
|
_handleConnectionFailed(device); |
|
|
_searchStatus.value = SearchStatus.failed; |
|
|
|
|
|
_stopSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 重试连接 |
|
|
|
|
|
_retryConnect(device); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重试连接 |
|
|
// 处理连接失败 |
|
|
void _retryConnect(BluetoothDevice device) { |
|
|
void _handleConnectionFailed(BluetoothDevice device) { |
|
|
if (_retryCount < MAX_RETRY_COUNT) { |
|
|
// 检查设备类型 |
|
|
_retryCount++; |
|
|
if (_bluetoothService.deviceType == DeviceType.classicAudio) { |
|
|
Logger.info('重试连接设备 (${_retryCount}/${MAX_RETRY_COUNT})'); |
|
|
// 经典蓝牙音频设备需要在系统设置中配对 |
|
|
|
|
|
_updateStatus(PairingStatus.needsSystemPairing, '需要系统配对'); |
|
|
Future.delayed(const Duration(seconds: 2), () { |
|
|
_setError('这是音频设备,需要在系统设置中配对', '请前往系统蓝牙设置完成配对'); |
|
|
tryConnectDevice(device); |
|
|
} else if (_connectRetryCount < MAX_RETRY_COUNT) { |
|
|
}); |
|
|
// 自动重试连接 |
|
|
|
|
|
_connectRetryCount++; |
|
|
|
|
|
Logger.info('连接失败,自动重试 (${_connectRetryCount}/${MAX_RETRY_COUNT})'); |
|
|
|
|
|
_updateStatus(PairingStatus.connecting, '连接失败,重试中...'); |
|
|
|
|
|
|
|
|
|
|
|
// 延迟2秒后重试 |
|
|
|
|
|
Future.delayed(const Duration(seconds: 2), () => _connectToDevice(device)); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 超过重试次数 |
|
|
|
|
|
_updateStatus(PairingStatus.failed, '连接失败'); |
|
|
|
|
|
_setError('连接设备失败', '请确保设备已开启且处于配对模式,或点击刷新重试'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 设备找到并连接成功后的处理 |
|
|
// 设备连接成功 |
|
|
void _onDeviceFound() { |
|
|
void _onDeviceConnected() { |
|
|
_searchStatus.value = SearchStatus.found; |
|
|
_updateStatus(PairingStatus.found, '设备连接成功'); |
|
|
_stopSearchingAnimation(); |
|
|
|
|
|
|
|
|
|
|
|
// 更新日志并延迟跳转到主页 |
|
|
// 延迟跳转到主页 |
|
|
Logger.info('设备连接成功,准备跳转到主页'); |
|
|
|
|
|
Timer(const Duration(seconds: 1), () { |
|
|
Timer(const Duration(seconds: 1), () { |
|
|
Get.offAllNamed(Routes.home, arguments: _deviceInfo.value); |
|
|
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() { |
|
|
void refreshSearch() { |
|
|
// 停止当前扫描(如果有) |
|
|
// 停止当前动画和扫描 |
|
|
|
|
|
_stopAnimation(); |
|
|
_bluetoothService.stopScan(); |
|
|
_bluetoothService.stopScan(); |
|
|
|
|
|
|
|
|
// 重置重试计数 |
|
|
// 重置重试计数 |
|
|
_retryCount = 0; |
|
|
_scanRetryCount = 0; |
|
|
|
|
|
_connectRetryCount = 0; |
|
|
|
|
|
|
|
|
// 更新UI状态 |
|
|
// 重新开始流程 |
|
|
_startSearchingAnimation(); |
|
|
_initializeProcess(); |
|
|
_searchStatus.value = SearchStatus.searching; |
|
|
} |
|
|
_errorMessage.value = ''; |
|
|
|
|
|
|
|
|
|
|
|
// 重新开始查找过程 |
|
|
// 打开应用设置 |
|
|
_findConnectedDevices(); |
|
|
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 { |
|
|
void openSystemBluetoothSettings() async { |
|
|
try { |
|
|
try { |
|
|
Logger.info('尝试打开系统蓝牙设置'); |
|
|
|
|
|
|
|
|
|
|
|
// 不同平台需要不同的处理 |
|
|
|
|
|
if (GetPlatform.isAndroid) { |
|
|
if (GetPlatform.isAndroid) { |
|
|
await openAppSettings(); |
|
|
await openAppSettings(); |
|
|
} else { |
|
|
} else { |
|
|
@ -443,10 +576,10 @@ class PairingController extends GetxController { |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
} catch (e) { |
|
|
} catch (e) { |
|
|
Logger.error('打开系统蓝牙设置失败: $e'); |
|
|
Logger.error('打开系统设置失败: $e'); |
|
|
Get.snackbar( |
|
|
Get.snackbar( |
|
|
'提示', |
|
|
'提示', |
|
|
'无法打开系统设置,请手动打开蓝牙设置并配对设备', |
|
|
'无法自动打开设置,请手动前往系统蓝牙设置', |
|
|
snackPosition: SnackPosition.TOP, |
|
|
snackPosition: SnackPosition.TOP, |
|
|
backgroundColor: Colors.red[100], |
|
|
backgroundColor: Colors.red[100], |
|
|
colorText: Colors.red[800], |
|
|
colorText: Colors.red[800], |
|
|
@ -455,13 +588,18 @@ class PairingController extends GetxController { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 稍后再说 |
|
|
// 跳过连接 |
|
|
void skipConnecting() { |
|
|
void skipConnecting() { |
|
|
// 停止当前扫描 |
|
|
// 停止扫描和动画 |
|
|
_bluetoothService.stopScan(); |
|
|
_bluetoothService.stopScan(); |
|
|
_stopSearchingAnimation(); |
|
|
_stopAnimation(); |
|
|
|
|
|
|
|
|
// 直接跳转到主页 |
|
|
// 直接跳转到主页 |
|
|
Get.offAllNamed(Routes.home); |
|
|
Get.offAllNamed(Routes.home); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 手动连接特定设备 |
|
|
|
|
|
void connectToDevice(BluetoothDevice device) { |
|
|
|
|
|
_connectToDevice(device); |
|
|
|
|
|
} |
|
|
} |
|
|
} |