- 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>
156 lines
5.7 KiB
Dart
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();
|
|
}
|
|
}
|