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 scanResults = [].obs; // 连接的设备 final Rx connectedDevice = Rx(null); // 错误信息 final RxString errorMessage = ''.obs; @override void onInit() { super.onInit(); _initBluetooth(); } @override void onClose() { stopScan(); super.onClose(); } // 初始化蓝牙 Future _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 _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 _blePermissions = [ Permission.bluetoothScan, Permission.bluetoothConnect, Permission.location, ]; // 请求权限(仅由用户点击"扫描"触发) // // 合规(场景 1):拒绝之后不再弹任何框、不跳设置页——原来这里在检测到 // permanentlyDenied 时会立刻弹一个带"前往设置"的自定义框,正是要禁掉的行为。 // 现在统一走 PermissionUtil,拒绝即进入 48h 冷却,返回 false 后由 UI 置灰扫描按钮。 Future _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 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 stopScan() async { try { await fbp.FlutterBluePlus.stopScan(); } catch (e) { print('停止扫描失败: $e'); } } // 连接设备 Future 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 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; } }