import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:permission_handler/permission_handler.dart'; /// Outcome of a Bluetooth permission check or request. enum BleAccess { /// Everything granted. Bluetooth adapter state is checked separately. granted, /// The user declined but can be asked again. denied, /// The user declined with "don't ask again" (Android) or in Settings (iOS). /// Only the system settings screen can fix this. permanentlyDenied, /// The platform reported Bluetooth as unavailable. unsupported, } /// Requests the runtime permissions OpenMotion needs to scan and connect. /// /// Android 12+ (API 31): BLUETOOTH_SCAN and BLUETOOTH_CONNECT, shown to the /// user as "Nearby devices". Android 11 and below: legacy BLE scanning needs /// location while in use. iOS: the single Bluetooth permission, which the OS /// prompts for the first time CoreBluetooth is touched. class BlePermissions { BlePermissions._(); static int? _androidSdk; static Future androidSdk() async { if (!Platform.isAndroid) return 0; return _androidSdk ??= (await DeviceInfoPlugin().androidInfo).version.sdkInt; } static Future> _needed() async { if (Platform.isAndroid) { final sdk = await androidSdk(); return sdk >= 31 ? const [Permission.bluetoothScan, Permission.bluetoothConnect] : const [Permission.locationWhenInUse]; } if (Platform.isIOS || Platform.isMacOS) return const [Permission.bluetooth]; return const []; } /// Checks without prompting. static Future status() async { final statuses = []; for (final p in await _needed()) { statuses.add(await p.status); } return _combine(statuses); } /// Prompts for anything not yet granted. static Future request() async { final needed = await _needed(); if (needed.isEmpty) return BleAccess.granted; final results = await needed.request(); return _combine(results.values.toList()); } static BleAccess _combine(List statuses) { if (statuses.any((s) => s.isPermanentlyDenied || s.isRestricted)) { return BleAccess.permanentlyDenied; } if (statuses.any((s) => s.isDenied)) return BleAccess.denied; return BleAccess.granted; } static Future openSettings() => openAppSettings(); }