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.
626 lines
20 KiB
626 lines
20 KiB
import 'package:app_settings/app_settings.dart';
|
|
import 'package:get/get.dart';
|
|
import 'dart:async';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import '../../../routes/app_routes.dart';
|
|
import '../../../core/utils/logger.dart';
|
|
import 'dart:io';
|
|
import '../../../data/services/bluetooth_manager.dart';
|
|
import 'package:classic_bluetooth/classic_bluetooth.dart' show BluetoothDevice;
|
|
import '../../../data/services/ble_manager.dart';
|
|
import 'package:ble_service/ble_service.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// 配对控制器,负责检查蓝牙权限、状态,并处理配对流程
|
|
class PairingController extends GetxController {
|
|
static PairingController get to => Get.find();
|
|
|
|
// 状态标志
|
|
final isLoading = false.obs;
|
|
|
|
// 权限是否已授予
|
|
final _hasPermissions = false.obs;
|
|
bool get hasPermissions => _hasPermissions.value;
|
|
|
|
// 蓝牙状态
|
|
final _bluetoothState = 'unknown'.obs;
|
|
String get bluetoothState => _bluetoothState.value;
|
|
bool get isBluetoothOn => _bluetoothState.value == 'on';
|
|
|
|
// 状态描述(用于UI显示)
|
|
final statusDescription = 'initializing'.tr.obs;
|
|
|
|
// 按钮文本
|
|
final actionButtonText = '去系统设置'.obs;
|
|
|
|
// 是否正在跳转到主页
|
|
bool _isNavigatingToHome = false;
|
|
|
|
// 是否正在执行Companion关联流程
|
|
bool _isAssociating = false;
|
|
|
|
// 是否用户拒绝了关联
|
|
final _userRejectedAssociation = false.obs;
|
|
bool get userRejectedAssociation => _userRejectedAssociation.value;
|
|
|
|
// 当前连接设备信息
|
|
BluetoothDevice? get currentDevice => _bluetoothService.currentDevice;
|
|
|
|
// 是否已连接设备
|
|
bool get isDeviceConnected => currentDevice != null;
|
|
|
|
// 是否已完成Companion关联
|
|
final _companionAssociated = false.obs;
|
|
bool get isCompanionAssociated => _companionAssociated.value;
|
|
|
|
// 服务状态监听
|
|
Worker? _bluetoothStateWorker;
|
|
Worker? _deviceWorker;
|
|
|
|
// 蓝牙服务
|
|
final _bluetoothService = Get.find<BluetoothManager>();
|
|
|
|
// BLE服务
|
|
final _bleService = BleService();
|
|
|
|
// 日志tag
|
|
static const _tag = 'PairingController';
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_initialize();
|
|
}
|
|
|
|
@override
|
|
void onClose() {
|
|
_bluetoothStateWorker?.dispose();
|
|
_deviceWorker?.dispose();
|
|
super.onClose();
|
|
}
|
|
|
|
/// 初始化流程
|
|
Future<void> _initialize() async {
|
|
try {
|
|
isLoading.value = true;
|
|
statusDescription.value = 'checkingPermissions'.tr;
|
|
|
|
// 在iOS上,延迟一下再请求权限,避免多权限冲突
|
|
if (Platform.isIOS) {
|
|
await Future.delayed(Duration(milliseconds: 800));
|
|
}
|
|
|
|
// 检查并请求权限
|
|
if (!await _checkAndRequestPermissions()) {
|
|
statusDescription.value = 'permissionDenied'.tr;
|
|
actionButtonText.value = '去系统设置';
|
|
return;
|
|
}
|
|
|
|
// 设置服务状态监听
|
|
_setupServiceListeners();
|
|
|
|
// 检查蓝牙状态并刷新设备
|
|
await _checkBluetoothAndRefresh();
|
|
} catch (e) {
|
|
Logger.e(_tag, '初始化配对控制器失败: ${e.toString()}');
|
|
statusDescription.value = 'initializationFailure'.tr;
|
|
} finally {
|
|
isLoading.value = false;
|
|
_updateStatusDescription();
|
|
}
|
|
}
|
|
|
|
/// 检查蓝牙状态并刷新设备
|
|
Future<void> _checkBluetoothAndRefresh() async {
|
|
statusDescription.value = 'checkingBluetoothStatus'.tr;
|
|
final isEnabled = await _bluetoothService.isBluetoothEnabled();
|
|
_bluetoothState.value = isEnabled ? 'on' : 'off';
|
|
|
|
if (!isEnabled) {
|
|
statusDescription.value = 'bluetoothTurnedOff'.tr;
|
|
actionButtonText.value = '去系统设置';
|
|
return;
|
|
}
|
|
|
|
// 刷新连接设备并检查配对状态
|
|
statusDescription.value = 'searchingDevice'.tr;
|
|
await _bluetoothService.refreshDeviceConnection();
|
|
|
|
// 设备连接时检查配对状态
|
|
if (isDeviceConnected) {
|
|
await _checkDevicePairingStatus();
|
|
}
|
|
}
|
|
|
|
/// 检查设备配对状态
|
|
Future<void> _checkDevicePairingStatus() async {
|
|
if (!isDeviceConnected) return;
|
|
|
|
try {
|
|
final isSupported = await _bleService.isCompanionSupported();
|
|
if (!isSupported) {
|
|
Logger.i(_tag, '设备不支持Companion功能');
|
|
_companionAssociated.value = true; // 不支持则视为已配对
|
|
_userRejectedAssociation.value = false; // 重置拒绝状态
|
|
return;
|
|
}
|
|
|
|
final pairedMac = await _bleService.getPairedMacAddress();
|
|
if (pairedMac == null) {
|
|
// 没有配对记录,需要开始配对
|
|
Logger.i(_tag, '无配对记录,需要开始设备配对');
|
|
return;
|
|
}
|
|
|
|
Logger.i(_tag, '发现已关联的Companion设备: $pairedMac');
|
|
|
|
// 检查当前连接设备与已保存的设备是否一致
|
|
if (currentDevice!.address != pairedMac) {
|
|
// MAC地址不匹配
|
|
Logger.w(_tag, '当前连接的设备与已配对的DeepSound设备不符');
|
|
statusDescription.value = 'pleaseConnectPairedDevice'.tr;
|
|
return;
|
|
}
|
|
|
|
// 判断设备名称是否符合要求
|
|
if (!_isValidDeviceName(currentDevice!.name)) {
|
|
statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr;
|
|
return;
|
|
}
|
|
|
|
// 设备匹配且合规,标记为已配对
|
|
Logger.i(_tag, '当前设备与已配对设备匹配');
|
|
_companionAssociated.value = true;
|
|
_userRejectedAssociation.value = false; // 重置拒绝状态
|
|
await _bleService.registerBackgroundScan();
|
|
_autoNavigateToHome();
|
|
} catch (e) {
|
|
Logger.e(_tag, '检查设备配对状态失败: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
/// 检查设备名称是否合规
|
|
bool _isValidDeviceName(String deviceName) {
|
|
return deviceName.contains("DeapSound");
|
|
}
|
|
|
|
/// 检查并请求必要的蓝牙权限
|
|
Future<bool> _checkAndRequestPermissions() async {
|
|
Map<Permission, PermissionStatus> statuses;
|
|
List<Permission> permissionsToRequest = [];
|
|
|
|
if (Platform.isAndroid) {
|
|
permissionsToRequest.add(Permission.locationWhenInUse);
|
|
// permissionsToRequest.add(Permission.microphone);
|
|
if (await _getAndroidSdkVersion() >= 31) {
|
|
permissionsToRequest.add(Permission.bluetoothScan);
|
|
permissionsToRequest.add(Permission.bluetoothConnect);
|
|
}
|
|
} else if (Platform.isIOS) {
|
|
// iOS需要请求的权限
|
|
permissionsToRequest.add(Permission.bluetooth);
|
|
permissionsToRequest.add(Permission.location);
|
|
|
|
// 如果需要麦克风权限(例如语音功能)
|
|
// permissionsToRequest.add(Permission.microphone);
|
|
|
|
try {
|
|
// 先尝试请求权限
|
|
if (permissionsToRequest.isNotEmpty) {
|
|
statuses = await permissionsToRequest.request();
|
|
bool allGranted = true;
|
|
statuses.forEach((permission, status) {
|
|
if (!status.isGranted) {
|
|
allGranted = false;
|
|
Logger.w(_tag, 'iOS权限 ${permission.toString()} 未被授予: $status');
|
|
|
|
// 特殊处理蓝牙权限
|
|
if (permission == Permission.bluetooth) {
|
|
Logger.w(_tag, 'iOS蓝牙权限未授予,状态: $status');
|
|
}
|
|
}
|
|
});
|
|
|
|
// 如果有权限未授予,但不是永久拒绝,继续尝试
|
|
if (!allGranted) {
|
|
bool hasPermanentlyDenied = statuses.values.any((status) => status.isPermanentlyDenied);
|
|
if (hasPermanentlyDenied) {
|
|
// 有权限被永久拒绝,需要引导用户去设置
|
|
_showPermissionSettingsDialog('部分权限被永久拒绝,请在设置中启用');
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 直接尝试使用蓝牙功能,这会触发iOS原生权限请求
|
|
bool isEnabled = await _bluetoothService.isBluetoothEnabled();
|
|
Logger.i(_tag, 'iOS蓝牙状态检查: ${isEnabled ? "已开启" : "未开启"}');
|
|
|
|
// 检测AccessorySetupKit支持情况(iOS 18+特性)
|
|
bool companionSupported = await _bleService.isCompanionSupported();
|
|
Logger.i(_tag, 'iOS Companion支持检查: ${companionSupported ? "支持" : "不支持"}');
|
|
|
|
if (companionSupported) {
|
|
// 针对iOS 18+,尝试预热AccessorySetupKit框架
|
|
try {
|
|
// 此调用会触发AccessorySetupKit相关权限请求
|
|
final pairedMac = await _bleService.getPairedMacAddress();
|
|
Logger.i(_tag, '获取已配对设备MAC: ${pairedMac ?? "无"}');
|
|
} catch (e) {
|
|
Logger.w(_tag, 'iOS 18+ AccessorySetupKit初始化失败: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
// 在iOS上视为权限已授予
|
|
_hasPermissions.value = true;
|
|
return true;
|
|
} catch (e) {
|
|
Logger.e(_tag, "iOS蓝牙初始化失败: ${e.toString()}");
|
|
|
|
// 显示自定义对话框引导用户
|
|
_showPermissionSettingsDialog('需要开启蓝牙权限才能配对设备');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// 如果还有其他权限需要请求
|
|
if (permissionsToRequest.isNotEmpty) {
|
|
statuses = await permissionsToRequest.request();
|
|
|
|
bool allGranted = true;
|
|
statuses.forEach((permission, status) {
|
|
if (!status.isGranted) {
|
|
allGranted = false;
|
|
Logger.w(_tag, '权限 ${permission.toString()} 未被授予: $status');
|
|
}
|
|
});
|
|
|
|
if (!allGranted) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
_hasPermissions.value = true;
|
|
return true;
|
|
}
|
|
|
|
/// 显示权限设置对话框
|
|
void _showPermissionSettingsDialog(String message) {
|
|
Get.dialog(
|
|
AlertDialog(
|
|
title: Text('需要权限'),
|
|
content: Text(message),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Get.back(),
|
|
child: Text('稍后'),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Get.back();
|
|
openBluetoothSettings();
|
|
},
|
|
child: Text('去设置'),
|
|
),
|
|
],
|
|
),
|
|
barrierDismissible: false,
|
|
);
|
|
}
|
|
|
|
// 辅助方法获取Android SDK版本
|
|
Future<int> _getAndroidSdkVersion() async {
|
|
if (Platform.isAndroid) {
|
|
return 31; // 假设运行在需要新权限的设备上
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/// 设置服务状态监听
|
|
void _setupServiceListeners() {
|
|
// 监听蓝牙状态变化
|
|
_bluetoothStateWorker =
|
|
ever(_bluetoothService.bluetoothState.obs, (String state) {
|
|
_bluetoothState.value = state;
|
|
_updateStatusDescription();
|
|
});
|
|
|
|
// 监听设备连接状态变化
|
|
_deviceWorker =
|
|
ever(_bluetoothService.currentDeviceRx, (BluetoothDevice? device) {
|
|
if (device != null && !_isNavigatingToHome) {
|
|
Logger.i(_tag, '配对页面发现设备已连接: ${device.name}');
|
|
_handleDeviceConnected();
|
|
}
|
|
_updateStatusDescription();
|
|
});
|
|
}
|
|
|
|
/// 处理设备连接成功事件
|
|
Future<void> _handleDeviceConnected() async {
|
|
if (!isDeviceConnected || _isNavigatingToHome) return;
|
|
|
|
// 检查配对状态,决定下一步操作
|
|
await _checkDevicePairingStatus();
|
|
|
|
// 如果还未配对且设备合规,启动配对流程
|
|
if (!_companionAssociated.value &&
|
|
_isValidDeviceName(currentDevice!.name)) {
|
|
_startCompanionAssociation();
|
|
}
|
|
}
|
|
|
|
/// 更新状态描述
|
|
void _updateStatusDescription() {
|
|
if (_userRejectedAssociation.value) {
|
|
// 用户拒绝关联时的提示 - 这是最高优先级
|
|
statusDescription.value = '关联后才能使用全部功能';
|
|
actionButtonText.value = '关联DeapSound耳机';
|
|
return;
|
|
}
|
|
|
|
if (!hasPermissions) {
|
|
statusDescription.value = 'permissionDenied'.tr;
|
|
actionButtonText.value = '去系统设置';
|
|
return;
|
|
}
|
|
|
|
if (bluetoothState == 'off') {
|
|
statusDescription.value = 'bluetoothTurnedOff'.tr;
|
|
actionButtonText.value = '去系统设置';
|
|
return;
|
|
}
|
|
|
|
if (isDeviceConnected) {
|
|
if (Platform.isAndroid && !isCompanionAssociated && _isAssociating) {
|
|
statusDescription.value =
|
|
'${'associatingDevice'.tr} ${currentDevice!.name}';
|
|
} else {
|
|
statusDescription.value =
|
|
'${'connectedDevice'.tr} ${currentDevice!.name}';
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (isLoading.value) {
|
|
statusDescription.value = 'searchingDevice'.tr;
|
|
return;
|
|
}
|
|
|
|
statusDescription.value = 'notFound'.tr;
|
|
}
|
|
|
|
/// 启动Companion设备关联流程
|
|
Future<void> _startCompanionAssociation() async {
|
|
// 如果已跳转,已完成关联或正在进行关联,直接返回
|
|
if (_isNavigatingToHome ||
|
|
_companionAssociated.value ||
|
|
_isAssociating ||
|
|
!isDeviceConnected) {
|
|
return;
|
|
}
|
|
|
|
// 重置拒绝状态
|
|
_userRejectedAssociation.value = false;
|
|
|
|
// 设置关联状态标志
|
|
_isAssociating = true;
|
|
_updateStatusDescription();
|
|
|
|
try {
|
|
// 检查是否支持Companion功能
|
|
final isSupported = await _bleService.isCompanionSupported();
|
|
if (!isSupported) {
|
|
Logger.i(_tag, '设备不支持Companion功能,跳过关联');
|
|
_companionAssociated.value = true;
|
|
_autoNavigateToHome();
|
|
return;
|
|
}
|
|
|
|
// 检查设备名称是否符合要求
|
|
if (!_isValidDeviceName(currentDevice!.name)) {
|
|
statusDescription.value = 'pleaseConnectDeepSoundDevice'.tr;
|
|
_isAssociating = false;
|
|
return;
|
|
}
|
|
|
|
Logger.i(_tag, '开始Companion设备关联流程, 设备MAC: ${currentDevice!.address}');
|
|
|
|
var success = await _bleService.startDeviceAssociation(
|
|
deviceNamePattern: currentDevice!.name,
|
|
macAddress: currentDevice!.address,
|
|
);
|
|
_isAssociating = false;
|
|
|
|
if (success) {
|
|
Logger.i(_tag, 'Companion设备关联成功');
|
|
_companionAssociated.value = true;
|
|
_userRejectedAssociation.value = false;
|
|
|
|
// 注册后台扫描,确保应用被杀死后仍能被唤醒
|
|
await _bleService.registerBackgroundScan(
|
|
macAddress: currentDevice!.address);
|
|
|
|
// 关联成功后跳转
|
|
_autoNavigateToHome();
|
|
} else {
|
|
Logger.e(_tag, 'Companion设备关联失败或被用户拒绝');
|
|
// 关联失败 - 用户拒绝关联
|
|
_userRejectedAssociation.value = true;
|
|
// 确保UI状态更新
|
|
_updateStatusDescription();
|
|
}
|
|
} catch (e) {
|
|
Logger.e(_tag, 'Companion设备关联过程发生错误: ${e.toString()}');
|
|
_isAssociating = false;
|
|
_userRejectedAssociation.value = true;
|
|
_updateStatusDescription();
|
|
}
|
|
}
|
|
|
|
/// 自动跳转到主页
|
|
Future<void> _autoNavigateToHome() async {
|
|
if (_isNavigatingToHome || !isDeviceConnected) return;
|
|
|
|
// 启动BLE后台服务
|
|
await _startAgentService();
|
|
|
|
_isNavigatingToHome = true;
|
|
Logger.i(_tag, '已找到连接的设备 ${currentDevice!.name},自动跳转到主页');
|
|
|
|
Future.delayed(const Duration(milliseconds: 500), () {
|
|
Get.offAllNamed(Routes.home);
|
|
});
|
|
}
|
|
|
|
/// 启动BLE后台服务
|
|
Future<void> _startAgentService() async {
|
|
try {
|
|
// 注释掉设备连接检查,确保无论是否连接设备都能启动服务
|
|
// if (!isDeviceConnected) return;
|
|
|
|
final result = await BleManager.to.startAgentService();
|
|
Logger.i(_tag, result ? 'BLE后台服务启动成功' : 'BLE后台服务启动失败');
|
|
} catch (e) {
|
|
Logger.e(_tag, '启动BLE后台服务发生错误: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
/// 停止BLE后台服务
|
|
Future<void> _stopAgentService() async {
|
|
try {
|
|
final result = await BleManager.to.stopAgentService();
|
|
Logger.i(_tag, result ? 'BLE后台服务停止成功' : 'BLE后台服务停止失败');
|
|
} catch (e) {
|
|
Logger.e(_tag, '停止BLE后台服务发生错误: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
/// 处理按钮点击
|
|
void handleActionButtonTap() {
|
|
if (userRejectedAssociation) {
|
|
// 用户拒绝关联后,点击按钮重新开始关联
|
|
restartAssociation();
|
|
} else if (!hasPermissions || !isBluetoothOn) {
|
|
// 权限未授予或蓝牙关闭时,打开系统设置
|
|
openBluetoothSettings();
|
|
}
|
|
}
|
|
|
|
/// 打开系统蓝牙设置
|
|
void openBluetoothSettings() {
|
|
try {
|
|
if (!hasPermissions) {
|
|
openAppSettings();
|
|
} else if (Platform.isAndroid && !isBluetoothOn) {
|
|
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
|
|
} else if (Platform.isIOS) {
|
|
// iOS上优先尝试打开蓝牙设置
|
|
if (!isBluetoothOn) {
|
|
// iOS上无法直接打开蓝牙设置,只能打开应用设置
|
|
AppSettings.openAppSettings();
|
|
} else {
|
|
// 如果蓝牙开启但有其他权限问题,打开应用设置
|
|
openAppSettings();
|
|
}
|
|
} else {
|
|
AppSettings.openAppSettings(type: AppSettingsType.bluetooth);
|
|
}
|
|
} catch (e) {
|
|
Logger.e(_tag, '打开设置失败: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
/// 重新初始化蓝牙
|
|
Future<void> reinitializeBluetooth() async {
|
|
try {
|
|
isLoading.value = true;
|
|
statusDescription.value = '正在重新初始化蓝牙...';
|
|
|
|
// iOS上尝试重置权限状态
|
|
if (Platform.isIOS) {
|
|
await Future.delayed(Duration(milliseconds: 500));
|
|
|
|
// 检查蓝牙权限
|
|
PermissionStatus bluetoothStatus = await Permission.bluetooth.status;
|
|
Logger.i(_tag, "iOS蓝牙权限当前状态: $bluetoothStatus");
|
|
|
|
if (bluetoothStatus.isPermanentlyDenied) {
|
|
Logger.i(_tag, "iOS蓝牙权限处于永久拒绝状态,提示用户手动开启");
|
|
_showPermissionSettingsDialog('蓝牙权限被拒绝,请在设置中手动开启');
|
|
return;
|
|
}
|
|
|
|
// 尝试重新请求权限
|
|
if (!bluetoothStatus.isGranted) {
|
|
bluetoothStatus = await Permission.bluetooth.request();
|
|
Logger.i(_tag, "iOS蓝牙权限请求结果: $bluetoothStatus");
|
|
}
|
|
|
|
// 尝试检查蓝牙状态,这会重新触发系统权限弹窗
|
|
try {
|
|
bool isEnabled = await _bluetoothService.isBluetoothEnabled();
|
|
Logger.i(_tag, "iOS蓝牙状态: ${isEnabled ? '开启' : '关闭'}");
|
|
} catch (e) {
|
|
Logger.e(_tag, "iOS蓝牙状态检查失败: ${e.toString()}");
|
|
}
|
|
|
|
// 检查Companion功能(iOS 18+)
|
|
try {
|
|
bool isSupported = await _bleService.isCompanionSupported();
|
|
if (isSupported) {
|
|
Logger.i(_tag, "iOS 18+ Companion功能可用,尝试初始化");
|
|
// 尝试预热AccessorySetupKit
|
|
await _bleService.getPairedMacAddress();
|
|
}
|
|
} catch (e) {
|
|
Logger.w(_tag, "iOS Companion功能检查失败: ${e.toString()}");
|
|
}
|
|
}
|
|
|
|
// 重新检查权限
|
|
if (await _checkAndRequestPermissions()) {
|
|
// 重新检查蓝牙状态
|
|
await _checkBluetoothAndRefresh();
|
|
}
|
|
} catch (e) {
|
|
Logger.e(_tag, '重新初始化蓝牙失败: ${e.toString()}');
|
|
} finally {
|
|
isLoading.value = false;
|
|
_updateStatusDescription();
|
|
}
|
|
}
|
|
|
|
/// 打开应用设置
|
|
Future<void> openAppSettings() async {
|
|
try {
|
|
await AppSettings.openAppSettings();
|
|
// 返回后重新检查权限状态
|
|
Future.delayed(Duration(milliseconds: 500), () {
|
|
reinitializeBluetooth();
|
|
});
|
|
} catch (e) {
|
|
Logger.e(_tag, '打开应用设置失败: ${e.toString()}');
|
|
}
|
|
}
|
|
|
|
/// 重新开始关联
|
|
void restartAssociation() {
|
|
if (isDeviceConnected && !isCompanionAssociated) {
|
|
_startCompanionAssociation();
|
|
}
|
|
}
|
|
|
|
/// 跳过连接
|
|
void skipConnecting() async {
|
|
// 即使没有设备连接,也启动BLE后台服务
|
|
await _startAgentService();
|
|
_isNavigatingToHome = true;
|
|
|
|
// 记录跳过配对的日志
|
|
Logger.i(_tag, '用户跳过设备配对,直接跳转到主页');
|
|
|
|
Get.offAllNamed(Routes.home);
|
|
}
|
|
}
|