payfrit-user/lib/services/beacon_permissions.dart
John Mizerek bc81ca98b0 Add native Kotlin beacon scanner for faster detection
- Add AltBeacon library for native Android beacon scanning
- Create BeaconScanner.kt with 2-second scan duration
- Set up MethodChannel bridge in MainActivity.kt
- Add beacon_channel.dart for Dart/native communication
- Update splash_screen.dart to use native scanner on Android
- Keep dchs_flutter_beacon for iOS compatibility

Native scanner eliminates Flutter plugin warmup overhead.
Beacons broadcast at 200ms, so 2s scan captures ~10 samples.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 00:53:46 -08:00

158 lines
5.7 KiB
Dart

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:dchs_flutter_beacon/dchs_flutter_beacon.dart';
import 'beacon_channel.dart';
class BeaconPermissions {
static Future<bool> requestPermissions() async {
try {
// Request location permission (required for Bluetooth scanning)
final locationStatus = await Permission.locationWhenInUse.request();
debugPrint('[BeaconPermissions] Location: $locationStatus');
bool bluetoothGranted = true;
if (Platform.isIOS) {
// iOS uses a single Bluetooth permission
final bluetoothStatus = await Permission.bluetooth.request();
debugPrint('[BeaconPermissions] Bluetooth (iOS): $bluetoothStatus');
bluetoothGranted = bluetoothStatus.isGranted;
} else {
// Android 12+ requires separate scan/connect permissions
final bluetoothScan = await Permission.bluetoothScan.request();
final bluetoothConnect = await Permission.bluetoothConnect.request();
debugPrint('[BeaconPermissions] BluetoothScan: $bluetoothScan, BluetoothConnect: $bluetoothConnect');
bluetoothGranted = bluetoothScan.isGranted && bluetoothConnect.isGranted;
}
final allGranted = locationStatus.isGranted && bluetoothGranted;
if (allGranted) {
debugPrint('[BeaconPermissions] All permissions granted');
} else {
debugPrint('[BeaconPermissions] Permissions denied');
}
return allGranted;
} catch (e) {
debugPrint('[BeaconPermissions] Error requesting permissions: $e');
return false;
}
}
/// Check if Bluetooth is enabled - returns current state without prompting
static Future<bool> isBluetoothEnabled() async {
try {
// Use native channel on Android
if (Platform.isAndroid) {
return await BeaconChannel.isBluetoothEnabled();
}
// Use Flutter plugin on iOS
final bluetoothState = await flutterBeacon.bluetoothState;
return bluetoothState == BluetoothState.stateOn;
} catch (e) {
debugPrint('[BeaconPermissions] Error checking Bluetooth state: $e');
return false;
}
}
/// Request to enable Bluetooth via system prompt (Android only)
static Future<bool> requestEnableBluetooth() async {
try {
debugPrint('[BeaconPermissions] Requesting Bluetooth enable...');
// This opens a system dialog on Android asking user to turn on Bluetooth
final result = await flutterBeacon.requestAuthorization;
debugPrint('[BeaconPermissions] Request authorization result: $result');
return result;
} catch (e) {
debugPrint('[BeaconPermissions] Error requesting Bluetooth enable: $e');
return false;
}
}
/// Open Bluetooth settings
static Future<bool> openBluetoothSettings() async {
try {
debugPrint('[BeaconPermissions] Opening Bluetooth settings...');
final opened = await flutterBeacon.openBluetoothSettings;
debugPrint('[BeaconPermissions] Open Bluetooth settings result: $opened');
return opened;
} catch (e) {
debugPrint('[BeaconPermissions] Error opening Bluetooth settings: $e');
return false;
}
}
/// Check if Bluetooth is enabled, and try to enable it if not
static Future<bool> ensureBluetoothEnabled() async {
try {
// Check current Bluetooth state
final isOn = await isBluetoothEnabled();
debugPrint('[BeaconPermissions] Bluetooth state: ${isOn ? "ON" : "OFF"}');
if (isOn) {
debugPrint('[BeaconPermissions] Bluetooth is ON');
return true;
}
// Request to enable Bluetooth via system prompt
debugPrint('[BeaconPermissions] Bluetooth is OFF, requesting enable...');
await requestEnableBluetooth();
// Poll for Bluetooth state change - short wait first
for (int i = 0; i < 6; i++) {
await Future.delayed(const Duration(milliseconds: 500));
final newState = await isBluetoothEnabled();
debugPrint('[BeaconPermissions] Polling Bluetooth state ($i): ${newState ? "ON" : "OFF"}');
if (newState) {
debugPrint('[BeaconPermissions] Bluetooth is now ON');
return true;
}
}
// If still off after 3 seconds, try opening Bluetooth settings directly
debugPrint('[BeaconPermissions] Bluetooth still OFF, opening settings...');
await openBluetoothSettings();
// Poll again for up to 15 seconds (user needs time to toggle in settings)
for (int i = 0; i < 30; i++) {
await Future.delayed(const Duration(milliseconds: 500));
final newState = await isBluetoothEnabled();
debugPrint('[BeaconPermissions] Polling Bluetooth state after settings ($i): ${newState ? "ON" : "OFF"}');
if (newState) {
debugPrint('[BeaconPermissions] Bluetooth is now ON');
return true;
}
}
debugPrint('[BeaconPermissions] Bluetooth still OFF after waiting');
return false;
} catch (e) {
debugPrint('[BeaconPermissions] Error checking Bluetooth state: $e');
return false;
}
}
static Future<bool> checkPermissions() async {
final locationStatus = await Permission.locationWhenInUse.status;
bool bluetoothGranted = true;
if (Platform.isIOS) {
final bluetoothStatus = await Permission.bluetooth.status;
bluetoothGranted = bluetoothStatus.isGranted;
} else {
final bluetoothScan = await Permission.bluetoothScan.status;
final bluetoothConnect = await Permission.bluetoothConnect.status;
bluetoothGranted = bluetoothScan.isGranted && bluetoothConnect.isGranted;
}
return locationStatus.isGranted && bluetoothGranted;
}
static Future<void> openSettings() async {
await openAppSettings();
}
}