payfrit-app/lib/screens/beacon_scan_screen_broken.dart
John Mizerek 6f3dc7e477 Fix nested modifier saving with proper parent tracking
- Fixed _addModifiersRecursively to track OrderLineItemID through recursion
- Changed from hardcoded parentOrderLineItemId: 0 to actual parent IDs
- Added logic to find root item's OrderLineItemID before starting recursion
- Added logic to find each modifier's OrderLineItemID for its children
- Fixed API_BASE_URL to AALISTS_API_BASE_URL for environment consistency
- Added comprehensive debug logging for troubleshooting

This fix ensures nested modifiers (e.g., Customize Spread > Extra) are
properly saved to the database with correct parent-child relationships.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-30 14:40:55 -08:00

243 lines
7 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:beacons_plugin/beacons_plugin.dart';
import '../app/app_router.dart';
import '../app/app_state.dart';
import '../services/beacon_permissions.dart';
import '../services/api.dart';
class BeaconScanScreen extends StatefulWidget {
const BeaconScanScreen({super.key});
@override
State<BeaconScanScreen> createState() => _BeaconScanScreenState();
}
class _BeaconScanScreenState extends State<BeaconScanScreen> {
String _status = 'Initializing...';
bool _scanning = false;
bool _permissionsGranted = false;
// Track beacons by UUID -> (BeaconID, RSSI)
final Map<String, MapEntry<int, int>> _detectedBeacons = {};
Map<String, int> _uuidToBeaconId = {};
@override
void initState() {
super.initState();
_startScanFlow();
}
Future<void> _startScanFlow() async {
// Step 1: Request permissions
setState(() => _status = 'Requesting permissions...');
final granted = await BeaconPermissions.requestPermissions();
if (!granted) {
setState(() {
_status = 'Permissions denied';
_permissionsGranted = false;
});
return;
}
setState(() => _permissionsGranted = true);
// Step 2: Fetch all active beacons from server
setState(() => _status = 'Loading beacon data...');
try {
_uuidToBeaconId = await Api.listAllBeacons();
} catch (e) {
debugPrint('[BeaconScan] Error loading beacons: $e');
_uuidToBeaconId = {};
}
if (_uuidToBeaconId.isEmpty) {
// No beacons in database, skip scan and go straight to manual selection
debugPrint('[BeaconScan] No beacons in database, going to restaurant select');
if (mounted) _navigateToRestaurantSelect();
return;
}
// Step 3: Perform initial scan
setState(() {
_status = 'Scanning for nearby beacons...';
_scanning = true;
});
await _performInitialScan();
}
Future<void> _performInitialScan() async {
try {
// Setup beacon monitoring
await BeaconsPlugin.listenToBeacons(
_beaconDataReceived,
);
// Start beacon monitoring
await BeaconsPlugin.addRegion(
"PayfritBeacons",
"00000000-0000-0000-0000-000000000000", // Placeholder UUID
);
await BeaconsPlugin.startMonitoring();
// Scan for 3 seconds
await Future.delayed(const Duration(seconds: 3));
// Stop scanning
await BeaconsPlugin.stopMonitoring();
if (!mounted) return;
if (_detectedBeacons.isEmpty) {
// No beacons found
setState(() => _status = 'No beacons nearby');
await Future.delayed(const Duration(milliseconds: 800));
if (mounted) _navigateToRestaurantSelect();
} else {
// Find beacon with highest RSSI
final best = _findBestBeacon();
if (best != null) {
setState(() => _status = 'Beacon detected! Loading menu...');
await _autoSelectBusinessFromBeacon(best);
} else {
_navigateToRestaurantSelect();
}
}
} catch (e) {
debugPrint('[BeaconScan] Error during scan: $e');
if (mounted) {
setState(() => _status = 'Scan error: ${e.toString()}');
await Future.delayed(const Duration(seconds: 2));
if (mounted) _navigateToRestaurantSelect();
}
}
}
void _beaconDataReceived(dynamic result) {
if (result is Map) {
try {
final uuid = (result["uuid"] ?? "").toString().trim().toUpperCase().replaceAll('-', '');
final rssi = int.tryParse((result["rssi"] ?? "-100").toString()) ?? -100;
if (uuid.isNotEmpty && _uuidToBeaconId.containsKey(uuid)) {
final beaconId = _uuidToBeaconId[uuid]!;
// Update if this is a new beacon or better RSSI
if (!_detectedBeacons.containsKey(uuid) || _detectedBeacons[uuid]!.value < rssi) {
setState(() {
_detectedBeacons[uuid] = MapEntry(beaconId, rssi);
});
debugPrint('[BeaconScan] Detected: UUID=$uuid, BeaconID=$beaconId, RSSI=$rssi');
}
}
} catch (e) {
debugPrint('[BeaconScan] Error parsing beacon data: $e');
}
}
}
MapEntry<String, int>? _findBestBeacon() {
if (_detectedBeacons.isEmpty) return null;
String? bestUUID;
int bestRSSI = -200;
for (final entry in _detectedBeacons.entries) {
if (entry.value.value > bestRSSI) {
bestRSSI = entry.value.value;
bestUUID = entry.key;
}
}
if (bestUUID != null) {
final beaconId = _detectedBeacons[bestUUID]!.key;
return MapEntry(bestUUID, beaconId);
}
return null;
}
Future<void> _autoSelectBusinessFromBeacon(MapEntry<String, int> beacon) async {
final beaconId = beacon.value;
debugPrint('[BeaconScan] Found beacon! BeaconID=$beaconId, UUID=${beacon.key}');
// TODO: Fetch Business + ServicePoint info from BeaconID
// For now, navigate to restaurant select
_navigateToRestaurantSelect();
}
void _navigateToRestaurantSelect() {
Navigator.of(context).pushReplacementNamed(AppRoutes.restaurantSelect);
}
void _retryPermissions() async {
await BeaconPermissions.openSettings();
}
@override
void dispose() {
BeaconsPlugin.stopMonitoring();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_scanning)
const CircularProgressIndicator(color: Colors.white)
else if (!_permissionsGranted)
const Icon(Icons.bluetooth_disabled, color: Colors.white70, size: 64)
else
const Icon(Icons.bluetooth_searching, color: Colors.white70, size: 64),
const SizedBox(height: 24),
Text(
_status,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
if (_detectedBeacons.isNotEmpty) ...[
const SizedBox(height: 16),
Text(
'Found ${_detectedBeacons.length} beacon(s)',
style: const TextStyle(color: Colors.white70, fontSize: 12),
),
],
if (!_permissionsGranted && _status.contains('denied')) ...[
const SizedBox(height: 24),
FilledButton(
onPressed: _retryPermissions,
child: const Text('Open Settings'),
),
const SizedBox(height: 12),
TextButton(
onPressed: _navigateToRestaurantSelect,
style: TextButton.styleFrom(foregroundColor: Colors.white70),
child: const Text('Skip and select manually'),
),
],
],
),
),
);
}
}