- BeaconScanner.swift: Native scanner using CBCentralManager - AppDelegate.swift: Wire up MethodChannel (same API as Android) - beacon_channel.dart: Support iOS in isSupported check - beacon_scanner_service.dart: Use native scanner on both platforms iOS now gets the same fast 2-second scan as Android. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
109 lines
2.9 KiB
Dart
109 lines
2.9 KiB
Dart
import "dart:io";
|
|
import "package:flutter/services.dart";
|
|
|
|
/// Detected beacon from native scanner
|
|
class DetectedBeacon {
|
|
final String uuid;
|
|
final int rssi;
|
|
final int samples;
|
|
|
|
const DetectedBeacon({
|
|
required this.uuid,
|
|
required this.rssi,
|
|
required this.samples,
|
|
});
|
|
|
|
factory DetectedBeacon.fromMap(Map<dynamic, dynamic> map) {
|
|
return DetectedBeacon(
|
|
uuid: (map["uuid"] as String?) ?? "",
|
|
rssi: (map["rssi"] as int?) ?? -100,
|
|
samples: (map["samples"] as int?) ?? 0,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() => "DetectedBeacon(uuid: $uuid, rssi: $rssi, samples: $samples)";
|
|
}
|
|
|
|
/// Native beacon scanner via MethodChannel
|
|
/// Works on both Android and iOS using platform-specific implementations.
|
|
class BeaconChannel {
|
|
static const _channel = MethodChannel("com.payfrit.app/beacon");
|
|
|
|
/// Check if native scanner is supported on this platform
|
|
static bool get isSupported => Platform.isAndroid || Platform.isIOS;
|
|
|
|
/// Check if Bluetooth permissions are granted
|
|
static Future<bool> hasPermissions() async {
|
|
if (!isSupported) return false;
|
|
|
|
try {
|
|
final result = await _channel.invokeMethod<bool>("hasPermissions");
|
|
return result ?? false;
|
|
} on PlatformException catch (e) {
|
|
print("[BeaconChannel] hasPermissions error: $e");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Check if Bluetooth is enabled
|
|
static Future<bool> isBluetoothEnabled() async {
|
|
if (!isSupported) return false;
|
|
|
|
try {
|
|
final result = await _channel.invokeMethod<bool>("isBluetoothEnabled");
|
|
return result ?? false;
|
|
} on PlatformException catch (e) {
|
|
print("[BeaconChannel] isBluetoothEnabled error: $e");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Start scanning for beacons
|
|
/// Returns list of detected beacons sorted by RSSI (strongest first)
|
|
static Future<List<DetectedBeacon>> startScan({List<String>? regions}) async {
|
|
if (!isSupported) {
|
|
print("[BeaconChannel] Not supported on this platform");
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
print("[BeaconChannel] Starting native beacon scan...");
|
|
|
|
final result = await _channel.invokeMethod<List<dynamic>>(
|
|
"startScan",
|
|
{"regions": regions ?? []},
|
|
);
|
|
|
|
if (result == null) {
|
|
print("[BeaconChannel] Scan returned null");
|
|
return [];
|
|
}
|
|
|
|
final beacons = result
|
|
.map((e) => DetectedBeacon.fromMap(e as Map<dynamic, dynamic>))
|
|
.toList();
|
|
|
|
print("[BeaconChannel] Scan complete: found ${beacons.length} beacons");
|
|
for (final b in beacons) {
|
|
print("[BeaconChannel] $b");
|
|
}
|
|
|
|
return beacons;
|
|
} on PlatformException catch (e) {
|
|
print("[BeaconChannel] Scan error: ${e.message}");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// Stop an ongoing scan
|
|
static Future<void> stopScan() async {
|
|
if (!isSupported) return;
|
|
|
|
try {
|
|
await _channel.invokeMethod("stopScan");
|
|
} on PlatformException catch (e) {
|
|
print("[BeaconChannel] stopScan error: $e");
|
|
}
|
|
}
|
|
}
|