import "dart:async"; 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/auth_storage.dart"; import "../services/beacon_cache.dart"; import "../services/beacon_channel.dart"; import "../services/beacon_permissions.dart"; class SplashScreen extends StatefulWidget { const SplashScreen({super.key}); @override State createState() => _SplashScreenState(); } class _SplashScreenState extends State { // Beacon scanning state BeaconLookupResult? _bestBeacon; @override void initState() { super.initState(); debugPrint('[Splash] Starting...'); _initializeApp(); } Future _initializeApp() async { debugPrint('[Splash] Initializing...'); // Run auth check and beacon prep in parallel final authFuture = _checkAuth(); final beaconPrepFuture = _prepareBeaconScan(); // Wait for both to complete await Future.wait([authFuture, beaconPrepFuture]); // Now do the beacon scan (needs permissions from prep) await _performBeaconScan(); // Navigate based on results if (!mounted) return; _navigateToNextScreen(); } Future _checkAuth() async { final credentials = await AuthStorage.loadAuth(); if (credentials != null && mounted) { debugPrint('[Splash] Found saved credentials'); Api.setAuthToken(credentials.token); // Validate token in background - don't block startup _validateToken().then((isValid) { if (isValid && mounted) { final appState = context.read(); appState.setUserId(credentials.userId); } else { AuthStorage.clearAuth(); Api.clearAuthToken(); } }); } } Future _prepareBeaconScan() async { // Request permissions (this is the slow part) await BeaconPermissions.requestPermissions(); } Future _validateToken() async { try { final profile = await Api.getProfile(); return profile.userId > 0; } catch (e) { debugPrint('[Splash] Token validation failed: $e'); return false; } } Future _performBeaconScan() async { // Check permissions (already requested in parallel) final hasPerms = await BeaconPermissions.checkPermissions(); if (!hasPerms) { debugPrint('[Splash] Permissions not granted'); return; } // Check Bluetooth final bluetoothOn = await BeaconPermissions.isBluetoothEnabled(); if (!bluetoothOn) { debugPrint('[Splash] Bluetooth is OFF'); return; } // Load known beacons from cache or server Map knownBeacons = {}; final cached = await BeaconCache.load(); if (cached != null && cached.isNotEmpty) { knownBeacons = cached; // Refresh cache in background Api.listAllBeacons().then((fresh) => BeaconCache.save(fresh)); } else { try { knownBeacons = await Api.listAllBeacons(); await BeaconCache.save(knownBeacons); } catch (e) { debugPrint('[Splash] Failed to fetch beacons: $e'); return; } } if (knownBeacons.isEmpty) return; // Scan for beacons debugPrint('[Splash] Scanning...'); final detectedBeacons = await BeaconChannel.startScan( regions: knownBeacons.keys.toList(), ); if (detectedBeacons.isEmpty) { debugPrint('[Splash] No beacons detected'); return; } debugPrint('[Splash] Found ${detectedBeacons.length} beacons'); // Filter to known beacons final validBeacons = detectedBeacons .where((b) => knownBeacons.containsKey(b.uuid)) .toList(); if (validBeacons.isEmpty) return; // Look up business info final uuids = validBeacons.map((b) => b.uuid).toList(); try { final lookupResults = await Api.lookupBeacons(uuids); if (lookupResults.isNotEmpty) { final rssiMap = {for (var b in validBeacons) b.uuid: b.rssi}; BeaconLookupResult? best; int bestRssi = -999; for (final result in lookupResults) { final rssi = rssiMap[result.uuid] ?? -100; if (rssi > bestRssi) { bestRssi = rssi; best = result; } } _bestBeacon = best; debugPrint('[Splash] Best: ${_bestBeacon?.beaconName} (RSSI=$bestRssi)'); } } catch (e) { debugPrint('[Splash] Lookup error: $e'); } } Future _navigateToNextScreen() async { if (!mounted) return; if (_bestBeacon != null) { final beacon = _bestBeacon!; print('[Splash] Best beacon: ${beacon.businessName}, hasChildren=${beacon.hasChildren}, parent=${beacon.parentBusinessName}'); // Check if this business has child businesses (food court scenario) if (beacon.hasChildren) { print('[Splash] Business has children - showing selector'); // Need to fetch children and show selector 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) { print('[Splash] Error fetching children: $e'); } } // Single business - go directly to menu final appState = context.read(); appState.setBusinessAndServicePoint( beacon.businessId, beacon.servicePointId, businessName: beacon.businessName, servicePointName: beacon.servicePointName, parentBusinessId: beacon.hasParent ? beacon.parentBusinessId : null, parentBusinessName: beacon.hasParent ? beacon.parentBusinessName : null, ); // Beacon detected = dine-in at a table appState.setOrderType(OrderType.dineIn); Api.setBusinessId(beacon.businessId); print('[Splash] Auto-selected: ${beacon.businessName}'); Navigator.of(context).pushReplacementNamed( AppRoutes.menuBrowse, arguments: { 'businessId': beacon.businessId, 'servicePointId': beacon.servicePointId, }, ); return; } // No beacon or error - go to restaurant select print('[Splash] Going to restaurant select'); Navigator.of(context).pushReplacementNamed(AppRoutes.restaurantSelect); } @override Widget build(BuildContext context) { return const Scaffold( backgroundColor: Colors.black, body: Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white54, ), ), ), ); } }