import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../app/app_router.dart'; import '../app/app_state.dart'; import '../services/api.dart'; import '../services/beacon_scanner_service.dart'; /// A button that triggers a beacon rescan /// Can be used anywhere in the app - will use the current business if available class RescanButton extends StatefulWidget { final bool showLabel; final Color? iconColor; const RescanButton({ super.key, this.showLabel = false, this.iconColor, }); @override State createState() => _RescanButtonState(); } class _RescanButtonState extends State { final _scanner = BeaconScannerService(); bool _isScanning = false; Future _performRescan() async { if (_isScanning) return; setState(() => _isScanning = true); final appState = context.read(); final currentBusinessId = appState.selectedBusinessId; // Show scanning indicator final scaffold = ScaffoldMessenger.of(context); scaffold.showSnackBar( const SnackBar( content: Row( children: [ SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ), SizedBox(width: 12), Text('Scanning for your table...'), ], ), duration: Duration(seconds: 3), ), ); try { // Use current business for optimized scan if available final result = await _scanner.scan(businessId: currentBusinessId); if (!mounted) return; scaffold.hideCurrentSnackBar(); if (result.foundBeacon) { final beacon = result.bestBeacon!; // Check if it's the same business/table or a new one // Also check if the beacon's business matches our parent (food court scenario) final isSameLocation = (beacon.businessId == currentBusinessId && beacon.servicePointId == appState.selectedServicePointId); final isSameParentLocation = (appState.hasParentBusiness && beacon.businessId == appState.parentBusinessId && beacon.servicePointId == appState.selectedServicePointId); if (isSameLocation || isSameParentLocation) { // Same location - just confirm scaffold.showSnackBar( SnackBar( content: Text('Still at ${appState.selectedServicePointName ?? beacon.servicePointName}'), duration: const Duration(seconds: 2), ), ); } else { // Different location - ask to switch _showSwitchDialog(beacon); } } else if (result.beaconsFound > 0) { scaffold.showSnackBar( SnackBar( content: Text('Found ${result.beaconsFound} beacon(s) but none registered'), duration: const Duration(seconds: 2), ), ); } else { scaffold.showSnackBar( const SnackBar( content: Text('No beacons detected nearby'), duration: Duration(seconds: 2), ), ); } } catch (e) { if (mounted) { scaffold.hideCurrentSnackBar(); scaffold.showSnackBar( SnackBar( content: Text('Scan failed: $e'), duration: const Duration(seconds: 2), ), ); } } finally { if (mounted) { setState(() => _isScanning = false); } } } void _showSwitchDialog(BeaconLookupResult beacon) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('New Location Detected'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('You appear to be at:'), const SizedBox(height: 8), Text( beacon.businessName, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), if (beacon.servicePointName.isNotEmpty) Text( beacon.servicePointName, style: TextStyle(color: Colors.grey.shade600), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Stay Here'), ), FilledButton( onPressed: () { Navigator.pop(context); _switchToBeacon(beacon); }, child: const Text('Switch'), ), ], ), ); } Future _switchToBeacon(BeaconLookupResult beacon) async { final appState = context.read(); // Handle parent business (food court scenario) if (beacon.hasChildren) { try { final children = await Api.getChildBusinesses(businessId: beacon.businessId); if (!mounted) return; if (children.isNotEmpty) { Navigator.of(context).pushReplacementNamed( AppRoutes.businessSelector, arguments: { 'parentBusinessId': beacon.businessId, 'parentBusinessName': beacon.businessName, 'servicePointId': beacon.servicePointId, 'servicePointName': beacon.servicePointName, 'children': children, }, ); return; } } catch (e) { debugPrint('[Rescan] Error fetching children: $e'); } } // Single business - update state and navigate appState.setBusinessAndServicePoint( beacon.businessId, beacon.servicePointId, businessName: beacon.businessName, servicePointName: beacon.servicePointName, parentBusinessId: beacon.hasParent ? beacon.parentBusinessId : null, parentBusinessName: beacon.hasParent ? beacon.parentBusinessName : null, ); appState.setOrderType(OrderType.dineIn); Api.setBusinessId(beacon.businessId); if (!mounted) return; Navigator.of(context).pushReplacementNamed( AppRoutes.menuBrowse, arguments: { 'businessId': beacon.businessId, 'servicePointId': beacon.servicePointId, }, ); } @override Widget build(BuildContext context) { if (widget.showLabel) { return TextButton.icon( onPressed: _isScanning ? null : _performRescan, icon: _isScanning ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : Icon(Icons.bluetooth_searching, color: widget.iconColor), label: Text( _isScanning ? 'Scanning...' : 'Find My Table', style: TextStyle(color: widget.iconColor), ), ); } return IconButton( icon: _isScanning ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : Icon(Icons.bluetooth_searching, color: widget.iconColor), tooltip: 'Find My Table', onPressed: _isScanning ? null : _performRescan, ); } }