payfrit-user/lib/services/beacon_permissions.dart
John Mizerek 982152383a Use native scanner across all beacon services, simplify splash
- Update beacon_scan_screen and beacon_scanner_service to use native
  Android scanner with Flutter plugin fallback for iOS
- Add native permission checking for fast path startup
- Simplify splash screen: remove bouncing logo animation, show only
  spinner on black background for clean transition
- Native splash is now black-only to match Flutter splash

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

156 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 {
// On Android, check native first (fast path)
if (Platform.isAndroid) {
final hasPerms = await BeaconChannel.hasPermissions();
if (hasPerms) {
debugPrint('[BeaconPermissions] Native check: granted');
return true;
}
debugPrint('[BeaconPermissions] Native check: not granted, requesting...');
}
// Request via Flutter plugin (slow but shows system dialogs)
final locationStatus = await Permission.locationWhenInUse.request();
debugPrint('[BeaconPermissions] Location: $locationStatus');
bool bluetoothGranted = true;
if (Platform.isIOS) {
final bluetoothStatus = await Permission.bluetooth.request();
debugPrint('[BeaconPermissions] Bluetooth (iOS): $bluetoothStatus');
bluetoothGranted = bluetoothStatus.isGranted;
} else {
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;
debugPrint('[BeaconPermissions] All granted: $allGranted');
return allGranted;
} catch (e) {
debugPrint('[BeaconPermissions] Error: $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 {
// On Android, use native check (much faster)
if (Platform.isAndroid) {
return await BeaconChannel.hasPermissions();
}
// iOS: use Flutter plugin
final locationStatus = await Permission.locationWhenInUse.status;
final bluetoothStatus = await Permission.bluetooth.status;
return locationStatus.isGranted && bluetoothStatus.isGranted;
}
static Future<void> openSettings() async {
await openAppSettings();
}
}