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.
 
 
 
 
 
 

267 lines
7.6 KiB

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart' as fbp;
import 'package:permission_handler/permission_handler.dart';
import '../../../core/utils/permission_util.dart';
class BleController extends GetxController {
// 扫描状态
final RxBool isScanning = false.obs;
final RxBool isBluetoothOn = false.obs;
final RxBool hasPermissions = false.obs;
// 发现的设备列表
final RxList<fbp.ScanResult> scanResults = <fbp.ScanResult>[].obs;
// 连接的设备
final Rx<fbp.BluetoothDevice?> connectedDevice =
Rx<fbp.BluetoothDevice?>(null);
// 错误信息
final RxString errorMessage = ''.obs;
@override
void onInit() {
super.onInit();
_initBluetooth();
}
@override
void onClose() {
stopScan();
super.onClose();
}
// 初始化蓝牙
Future<void> _initBluetooth() async {
try {
// 先检查权限
await _checkPermissions();
// 检查蓝牙适配器状态
fbp.FlutterBluePlus.adapterState.listen((state) {
isBluetoothOn.value = state == fbp.BluetoothAdapterState.on;
if (state == fbp.BluetoothAdapterState.on) {
errorMessage.value = '';
} else {
errorMessage.value = 'pleaseEnableBluetooth'.tr;
stopScan();
}
});
// 监听扫描结果
fbp.FlutterBluePlus.scanResults.listen((results) {
scanResults.value = results;
});
// 监听扫描状态
fbp.FlutterBluePlus.isScanning.listen((scanning) {
isScanning.value = scanning;
});
} catch (e) {
errorMessage.value = 'bleInitFailedWithError'.trParams({'error': e.toString()});
}
}
// 检查权限(只查状态,绝不发起申请)
//
// 合规(场景 1/2):进页面就自动弹权限框是明确禁止的。这里原来虽然叫
// "不请求",实际却调了 .request(),用户一进 BLE 页就被弹蓝牙+定位框,
// 拒绝之后每次重进页面还会再弹。现在只读状态,申请交给用户点"扫描"时触发。
Future<void> _checkPermissions() async {
try {
bool allGranted = true;
for (final p in _blePermissions) {
if (!await p.status.isGranted) {
allGranted = false;
break;
}
}
hasPermissions.value = allGranted;
if (!allGranted) {
errorMessage.value = 'needBluetoothAndLocationPermission'.tr;
}
} catch (e) {
print('检查权限失败: $e');
hasPermissions.value = false;
}
}
// 扫描需要的权限(成套,缺一个都扫不出设备)
static const List<Permission> _blePermissions = [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
];
// 请求权限(仅由用户点击"扫描"触发)
//
// 合规(场景 1):拒绝之后不再弹任何框、不跳设置页——原来这里在检测到
// permanentlyDenied 时会立刻弹一个带"前往设置"的自定义框,正是要禁掉的行为。
// 现在统一走 PermissionUtil,拒绝即进入 48h 冷却,返回 false 后由 UI 置灰扫描按钮。
Future<bool> _requestPermissions() async {
try {
final allGranted = await PermissionUtil.instance.requestPermissions(
permissions: _blePermissions,
permissionName: 'bluetooth'.tr,
explanationText: 'needBluetoothAndLocationPermission'.tr,
);
hasPermissions.value = allGranted;
if (!allGranted) {
// 只做静态提示,不引导、不跳转
errorMessage.value = 'needBluetoothAndLocationPermission'.tr;
}
return allGranted;
} catch (e) {
print('请求权限失败: $e');
errorMessage.value = 'permissionRequestFailedWithError'.trParams({'error': e.toString()});
return false;
}
}
/// 权限被拒且仍在冷却期 —— 视图据此把扫描入口置灰
bool get isPermissionCoolingDown => _blePermissions
.any((p) => PermissionUtil.instance.isDenialCoolingDown(p));
// 显示蓝牙开启对话框
void _showBluetoothDialog() {
Get.dialog(
AlertDialog(
title: Text('needEnableBluetooth'.tr),
content: Text('enableBluetoothDialogContent'.tr),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('cancel'.tr),
),
TextButton(
onPressed: () {
Get.back();
// 尝试开启蓝牙
fbp.FlutterBluePlus.turnOn();
},
child: Text('enableBluetooth'.tr),
),
],
),
);
}
// 开始扫描
Future<void> startScan() async {
try {
errorMessage.value = '';
// 检查权限
if (!hasPermissions.value) {
if (!await _requestPermissions()) {
return;
}
}
// 检查蓝牙状态
if (!isBluetoothOn.value) {
_showBluetoothDialog();
return;
}
// 清空之前的结果
scanResults.clear();
// 开始扫描
await fbp.FlutterBluePlus.startScan(
timeout: const Duration(seconds: 30),
androidUsesFineLocation: true,
);
} catch (e) {
errorMessage.value = 'bleScanFailedWithError'.trParams({'error': e.toString()});
}
}
// 停止扫描
Future<void> stopScan() async {
try {
await fbp.FlutterBluePlus.stopScan();
} catch (e) {
print('停止扫描失败: $e');
}
}
// 连接设备
Future<void> connectToDevice(fbp.BluetoothDevice device) async {
try {
errorMessage.value = '';
// 停止扫描
await stopScan();
// 连接设备
await device.connect(timeout: const Duration(seconds: 15));
connectedDevice.value = device;
Get.snackbar(
'bleConnectionSuccess'.tr,
'bleConnectedToDevice'.trParams({'device': device.platformName.isNotEmpty ? device.platformName : 'unknownEquipment'.tr}),
backgroundColor: Colors.green,
colorText: Colors.white,
);
} catch (e) {
errorMessage.value = 'bleConnectionFailedWithError'.trParams({'error': e.toString()});
Get.snackbar(
'connectionFailed'.tr,
e.toString(),
backgroundColor: Colors.red,
colorText: Colors.white,
);
}
}
// 断开连接
Future<void> disconnectDevice() async {
try {
if (connectedDevice.value != null) {
await connectedDevice.value!.disconnect();
connectedDevice.value = null;
Get.snackbar(
'bleDisconnected'.tr,
'bleDeviceDisconnected'.tr,
backgroundColor: Colors.orange,
colorText: Colors.white,
);
}
} catch (e) {
errorMessage.value = 'bleDisconnectFailedWithError'.trParams({'error': e.toString()});
}
}
// 获取设备名称
String getDeviceName(fbp.ScanResult result) {
if (result.device.platformName.isNotEmpty) {
return result.device.platformName;
}
if (result.advertisementData.localName.isNotEmpty) {
return result.advertisementData.localName;
}
return 'unknownEquipment'.tr;
}
// 获取信号强度描述
String getSignalStrengthText(int rssi) {
if (rssi >= -50) return 'signalStrong'.tr;
if (rssi >= -70) return 'signalMedium'.tr;
if (rssi >= -90) return 'signalWeak'.tr;
return 'signalVeryWeak'.tr;
}
// 获取信号强度图标
IconData getSignalStrengthIcon(int rssi) {
if (rssi >= -50) return Icons.signal_wifi_4_bar;
if (rssi >= -70) return Icons.signal_wifi_4_bar;
if (rssi >= -90) return Icons.signal_wifi_4_bar;
return Icons.signal_wifi_4_bar;
}
}