You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
467 lines
14 KiB
467 lines
14 KiB
import 'dart:async';
|
|
import 'package:get/get.dart';
|
|
import '../../../core/routes/app_routes.dart';
|
|
import '../../../data/services/bluetooth_service.dart';
|
|
import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService;
|
|
import '../../../core/utils/logger.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
enum SearchStatus {
|
|
searching, // 正在搜索设备
|
|
found, // 找到设备
|
|
failed, // 搜索失败
|
|
noPermission, // 缺少权限
|
|
needsSystemPairing // 需要在系统设置中配对
|
|
}
|
|
|
|
class PairingController extends GetxController {
|
|
// 搜索状态
|
|
final _searchStatus = SearchStatus.searching.obs;
|
|
SearchStatus get searchStatus => _searchStatus.value;
|
|
|
|
// 搜索动画的点
|
|
final _searchDots = ''.obs;
|
|
String get searchDots => _searchDots.value;
|
|
|
|
// 动画定时器
|
|
Timer? _animationTimer;
|
|
|
|
// 蓝牙服务
|
|
late BluetoothService _bluetoothService;
|
|
|
|
// 设备信息
|
|
final _deviceInfo = Rx<Map<String, dynamic>>({});
|
|
Map<String, dynamic> get deviceInfo => _deviceInfo.value;
|
|
|
|
// 蓝牙状态
|
|
final _isBluetoothAvailable = false.obs;
|
|
final _isBluetoothOn = false.obs;
|
|
bool get isBluetoothAvailable => _isBluetoothAvailable.value;
|
|
bool get isBluetoothOn => _isBluetoothOn.value;
|
|
|
|
// 错误消息
|
|
final _errorMessage = ''.obs;
|
|
String get errorMessage => _errorMessage.value;
|
|
|
|
// 扫描到的设备列表
|
|
final _scannedDevices = <ScanResult>[].obs;
|
|
List<ScanResult> get scannedDevices => _scannedDevices;
|
|
|
|
// 已配对的设备列表
|
|
final _pairedDevices = <BluetoothDevice>[].obs;
|
|
List<BluetoothDevice> get pairedDevices => _pairedDevices;
|
|
|
|
// 重试计数
|
|
int _retryCount = 0;
|
|
static const int MAX_RETRY_COUNT = 3;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
// 启动搜索动画
|
|
_startSearchingAnimation();
|
|
|
|
// 初始化蓝牙服务
|
|
_initBluetoothService();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_stopSearchingAnimation();
|
|
super.onClose();
|
|
}
|
|
|
|
// 初始化蓝牙服务
|
|
Future<void> _initBluetoothService() async {
|
|
try {
|
|
// 获取蓝牙服务实例
|
|
if (Get.isRegistered<BluetoothService>()) {
|
|
_bluetoothService = Get.find<BluetoothService>();
|
|
|
|
// 如果蓝牙服务已初始化,直接开始查找设备
|
|
if (_bluetoothService.isInitialized) {
|
|
_setupBluetoothListeners();
|
|
} else {
|
|
// 等待蓝牙服务初始化完成
|
|
ever(_bluetoothService.isInitializedObs, (bool initialized) {
|
|
if (initialized) {
|
|
_setupBluetoothListeners();
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
// 初始化蓝牙服务
|
|
_bluetoothService = await Get.put(BluetoothService()).init();
|
|
_setupBluetoothListeners();
|
|
}
|
|
} catch (e) {
|
|
Logger.error('初始化蓝牙服务失败: $e');
|
|
_errorMessage.value = '初始化蓝牙服务失败';
|
|
_searchStatus.value = SearchStatus.failed;
|
|
_stopSearchingAnimation();
|
|
|
|
// 重试初始化
|
|
_retryInitialization();
|
|
}
|
|
}
|
|
|
|
// 设置蓝牙监听器
|
|
void _setupBluetoothListeners() {
|
|
// 监听蓝牙状态变化
|
|
ever(_bluetoothService.isAvailableObs, (bool available) {
|
|
_isBluetoothAvailable.value = available;
|
|
});
|
|
|
|
ever(_bluetoothService.isOnObs, (bool on) {
|
|
_isBluetoothOn.value = on;
|
|
|
|
// 如果蓝牙开启,开始查找设备
|
|
if (on) {
|
|
_findConnectedDevices();
|
|
} else {
|
|
_errorMessage.value = '请打开蓝牙以搜索设备';
|
|
_searchStatus.value = SearchStatus.failed;
|
|
_stopSearchingAnimation();
|
|
}
|
|
});
|
|
|
|
// 如果蓝牙已经开启,直接开始查找
|
|
if (_bluetoothService.isOn) {
|
|
_isBluetoothOn.value = true;
|
|
_findConnectedDevices();
|
|
} else {
|
|
_errorMessage.value = '请打开蓝牙以搜索设备';
|
|
}
|
|
}
|
|
|
|
// 重试初始化
|
|
void _retryInitialization() {
|
|
if (_retryCount < MAX_RETRY_COUNT) {
|
|
_retryCount++;
|
|
Logger.info('重试初始化蓝牙服务 (${_retryCount}/${MAX_RETRY_COUNT})');
|
|
|
|
Future.delayed(const Duration(seconds: 2), () {
|
|
_initBluetoothService();
|
|
});
|
|
}
|
|
}
|
|
|
|
// 启动搜索动画
|
|
void _startSearchingAnimation() {
|
|
_stopSearchingAnimation(); // 清除已有的定时器
|
|
|
|
int count = 0;
|
|
_animationTimer = Timer.periodic(const Duration(milliseconds: 500), (timer) {
|
|
count = (count + 1) % 4;
|
|
_searchDots.value = '.' * count;
|
|
});
|
|
}
|
|
|
|
// 停止搜索动画
|
|
void _stopSearchingAnimation() {
|
|
_animationTimer?.cancel();
|
|
_animationTimer = null;
|
|
_searchDots.value = '';
|
|
}
|
|
|
|
// 请求必要权限
|
|
Future<bool> _requestPermissions() async {
|
|
try {
|
|
return await _bluetoothService.requestPermissions();
|
|
} catch (e) {
|
|
Logger.error('请求权限失败: $e');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 打开应用设置
|
|
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),
|
|
);
|
|
}
|
|
}
|
|
|
|
// 查找已连接的设备
|
|
Future<void> _findConnectedDevices() async {
|
|
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;
|
|
});
|
|
|
|
if (device != null) {
|
|
Logger.info('找到系统已连接设备');
|
|
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo();
|
|
_onDeviceFound();
|
|
return;
|
|
}
|
|
|
|
// 3. 如果没有找到已连接设备,提示用户选择
|
|
// 获取已配对设备列表
|
|
_pairedDevices.value = _bluetoothService.systemPairedDevices;
|
|
|
|
// 如果有已配对的设备,直接尝试连接第一个
|
|
if (_pairedDevices.isNotEmpty) {
|
|
Logger.info('找到已配对设备,尝试连接');
|
|
|
|
// 尝试连接第一个配对设备
|
|
await tryConnectDevice(_pairedDevices.first);
|
|
} else {
|
|
// 如果没有已配对设备,开始扫描附近设备
|
|
await _scanForDevices();
|
|
}
|
|
} catch (e) {
|
|
Logger.error('查找设备失败: $e');
|
|
_errorMessage.value = '查找设备失败';
|
|
_searchStatus.value = SearchStatus.failed;
|
|
_stopSearchingAnimation();
|
|
|
|
// 重试查找设备
|
|
_retryFindDevices();
|
|
}
|
|
}
|
|
|
|
// 重试查找设备
|
|
void _retryFindDevices() {
|
|
if (_retryCount < MAX_RETRY_COUNT) {
|
|
_retryCount++;
|
|
Logger.info('重试查找设备 (${_retryCount}/${MAX_RETRY_COUNT})');
|
|
|
|
Future.delayed(const Duration(seconds: 2), () {
|
|
_findConnectedDevices();
|
|
});
|
|
}
|
|
}
|
|
|
|
// 扫描附近的蓝牙设备
|
|
Future<void> _scanForDevices() async {
|
|
try {
|
|
Logger.info('开始扫描附近设备');
|
|
_searchStatus.value = SearchStatus.searching;
|
|
_startSearchingAnimation();
|
|
|
|
// 开始扫描
|
|
List<ScanResult> results = await _bluetoothService.startScan(
|
|
timeout: const Duration(seconds: 10)
|
|
);
|
|
|
|
_scannedDevices.value = results;
|
|
|
|
// 过滤出可能是耳机的设备
|
|
List<ScanResult> audioDevices = results.where((result) {
|
|
String name = result.device.platformName.toLowerCase();
|
|
return name.contains('headphone') ||
|
|
name.contains('earphone') ||
|
|
name.contains('earbuds') ||
|
|
name.contains('headset') ||
|
|
name.contains('speaker') ||
|
|
name.contains('audio') ||
|
|
name.contains('耳机') ||
|
|
name.contains('音箱');
|
|
}).toList();
|
|
|
|
if (audioDevices.isNotEmpty) {
|
|
// 按信号强度排序
|
|
audioDevices.sort((a, b) => b.rssi.compareTo(a.rssi));
|
|
Logger.info('找到可能的音频设备');
|
|
|
|
// 直接尝试连接信号最强的音频设备
|
|
await tryConnectDevice(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();
|
|
}
|
|
} catch (e) {
|
|
Logger.error('扫描设备失败: $e');
|
|
_errorMessage.value = '扫描设备失败';
|
|
_searchStatus.value = SearchStatus.failed;
|
|
_stopSearchingAnimation();
|
|
|
|
// 重试扫描
|
|
_retryScan();
|
|
}
|
|
}
|
|
|
|
// 重试扫描
|
|
void _retryScan() {
|
|
if (_retryCount < MAX_RETRY_COUNT) {
|
|
_retryCount++;
|
|
Logger.info('重试扫描设备 (${_retryCount}/${MAX_RETRY_COUNT})');
|
|
|
|
Future.delayed(const Duration(seconds: 2), () {
|
|
_scanForDevices();
|
|
});
|
|
}
|
|
}
|
|
|
|
// 尝试连接特定设备
|
|
Future<void> tryConnectDevice(BluetoothDevice device) async {
|
|
try {
|
|
Logger.info('尝试连接设备');
|
|
_searchStatus.value = SearchStatus.searching;
|
|
_startSearchingAnimation();
|
|
_errorMessage.value = '';
|
|
|
|
bool connected = await _bluetoothService.connectToDevice(device);
|
|
|
|
if (connected) {
|
|
Logger.info('连接设备成功');
|
|
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo();
|
|
_onDeviceFound();
|
|
} else {
|
|
Logger.error('连接设备失败');
|
|
|
|
// 检查设备类型
|
|
if (_bluetoothService.deviceType == DeviceType.classicAudio) {
|
|
// 对于经典蓝牙音频设备,提示用户在系统设置中配对
|
|
_searchStatus.value = SearchStatus.needsSystemPairing;
|
|
_errorMessage.value = '这是音频设备,需要在系统设置中配对并连接';
|
|
} else {
|
|
// 其他类型设备连接失败
|
|
_searchStatus.value = SearchStatus.failed;
|
|
_errorMessage.value = '连接设备失败,请重试或检查设备状态';
|
|
}
|
|
_stopSearchingAnimation();
|
|
|
|
// 重试连接
|
|
_retryConnect(device);
|
|
}
|
|
} catch (e) {
|
|
Logger.error('连接设备失败: $e');
|
|
_errorMessage.value = '连接设备失败';
|
|
_searchStatus.value = SearchStatus.failed;
|
|
_stopSearchingAnimation();
|
|
|
|
// 重试连接
|
|
_retryConnect(device);
|
|
}
|
|
}
|
|
|
|
// 重试连接
|
|
void _retryConnect(BluetoothDevice device) {
|
|
if (_retryCount < MAX_RETRY_COUNT) {
|
|
_retryCount++;
|
|
Logger.info('重试连接设备 (${_retryCount}/${MAX_RETRY_COUNT})');
|
|
|
|
Future.delayed(const Duration(seconds: 2), () {
|
|
tryConnectDevice(device);
|
|
});
|
|
}
|
|
}
|
|
|
|
// 设备找到并连接成功后的处理
|
|
void _onDeviceFound() {
|
|
_searchStatus.value = SearchStatus.found;
|
|
_stopSearchingAnimation();
|
|
|
|
// 更新日志并延迟跳转到主页
|
|
Logger.info('设备连接成功,准备跳转到主页');
|
|
Timer(const Duration(seconds: 1), () {
|
|
Get.offAllNamed(Routes.home, arguments: _deviceInfo.value);
|
|
});
|
|
}
|
|
|
|
// 手动刷新/重新搜索
|
|
void refreshSearch() {
|
|
// 停止当前扫描(如果有)
|
|
_bluetoothService.stopScan();
|
|
|
|
// 重置重试计数
|
|
_retryCount = 0;
|
|
|
|
// 更新UI状态
|
|
_startSearchingAnimation();
|
|
_searchStatus.value = SearchStatus.searching;
|
|
_errorMessage.value = '';
|
|
|
|
// 重新开始查找过程
|
|
_findConnectedDevices();
|
|
}
|
|
|
|
// 跳转到系统蓝牙设置
|
|
void openSystemBluetoothSettings() async {
|
|
try {
|
|
Logger.info('尝试打开系统蓝牙设置');
|
|
|
|
// 不同平台需要不同的处理
|
|
if (GetPlatform.isAndroid) {
|
|
await openAppSettings();
|
|
} else {
|
|
// iOS不能直接打开蓝牙设置
|
|
Get.snackbar(
|
|
'提示',
|
|
'请手动打开系统设置并配对您的蓝牙设备',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.blue[100],
|
|
colorText: Colors.blue[800],
|
|
duration: const Duration(seconds: 3),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
Logger.error('打开系统蓝牙设置失败: $e');
|
|
Get.snackbar(
|
|
'提示',
|
|
'无法打开系统设置,请手动打开蓝牙设置并配对设备',
|
|
snackPosition: SnackPosition.TOP,
|
|
backgroundColor: Colors.red[100],
|
|
colorText: Colors.red[800],
|
|
duration: const Duration(seconds: 3),
|
|
);
|
|
}
|
|
}
|
|
|
|
// 稍后再说
|
|
void skipConnecting() {
|
|
// 停止当前扫描
|
|
_bluetoothService.stopScan();
|
|
_stopSearchingAnimation();
|
|
|
|
// 直接跳转到主页
|
|
Get.offAllNamed(Routes.home);
|
|
}
|
|
}
|