From 7c366d5a9c48262c205f39aa6de3a6b370ba2da9 Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Wed, 31 Dec 2025 12:04:50 -0800 Subject: [PATCH] Add production API support and fix login flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Configure production API URL (biz.payfrit.com) as default - Add INTERNET permission to AndroidManifest for API calls - Remove login requirement from restaurant selection (allow anonymous browsing) - Add enhanced logging for beacon scanning diagnostics - Add splash screen logging for auth flow debugging Technical changes: - api.dart: Default baseUrl to production instead of throwing error - restaurant_select_screen.dart: Remove userId requirement for browsing - beacon_scan_screen.dart: Replace debugPrint with print for better log capture - splash_screen.dart: Add diagnostic logging for auth restoration - AndroidManifest.xml: Add INTERNET permission Known issue: - Beacon detection not working - app receives beacon data from API (3 beacons) but BLE scanning not detecting physical beacons. Needs investigation of: * Physical beacon UUID configuration * Android BLE permissions at runtime * Beacon plugin initialization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- android/app/src/main/AndroidManifest.xml | 3 ++ lib/screens/beacon_scan_screen.dart | 60 ++++++++++++----------- lib/screens/restaurant_select_screen.dart | 14 +----- lib/screens/splash_screen.dart | 7 +++ lib/services/api.dart | 6 +-- 5 files changed, 45 insertions(+), 45 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6cb2d5e..c901fa0 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,7 @@ + + + diff --git a/lib/screens/beacon_scan_screen.dart b/lib/screens/beacon_scan_screen.dart index 9e31a42..0e50f10 100644 --- a/lib/screens/beacon_scan_screen.dart +++ b/lib/screens/beacon_scan_screen.dart @@ -49,10 +49,12 @@ class _BeaconScanScreenState extends State with SingleTickerPr Future _startScanFlow() async { // Step 1: Request permissions setState(() => _status = 'Requesting permissions...'); + print('[BeaconScan] 🔐 Requesting permissions...'); final granted = await BeaconPermissions.requestPermissions(); if (!granted) { + print('[BeaconScan] ❌ Permissions DENIED'); setState(() { _status = 'Permissions denied - Please enable Location & Bluetooth'; _permissionsGranted = false; @@ -60,6 +62,7 @@ class _BeaconScanScreenState extends State with SingleTickerPr return; } + print('[BeaconScan] ✅ Permissions GRANTED'); setState(() => _permissionsGranted = true); // Step 2: Fetch all active beacons from server @@ -67,21 +70,21 @@ class _BeaconScanScreenState extends State with SingleTickerPr try { _uuidToBeaconId = await Api.listAllBeacons(); - debugPrint('[BeaconScan] ========================================'); - debugPrint('[BeaconScan] Loaded ${_uuidToBeaconId.length} beacons from database:'); + print('[BeaconScan] ========================================'); + print('[BeaconScan] Loaded ${_uuidToBeaconId.length} beacons from database:'); _uuidToBeaconId.forEach((uuid, beaconId) { - debugPrint('[BeaconScan] BeaconID=$beaconId'); - debugPrint('[BeaconScan] UUID=$uuid'); - debugPrint('[BeaconScan] ---'); + print('[BeaconScan] BeaconID=$beaconId'); + print('[BeaconScan] UUID=$uuid'); + print('[BeaconScan] ---'); }); - debugPrint('[BeaconScan] ========================================'); + print('[BeaconScan] ========================================'); } catch (e) { debugPrint('[BeaconScan] Error loading beacons: $e'); _uuidToBeaconId = {}; } if (_uuidToBeaconId.isEmpty) { - debugPrint('[BeaconScan] No beacons in database, going to restaurant select'); + print('[BeaconScan] âš ī¸ No beacons in database, going to restaurant select'); if (mounted) _navigateToRestaurantSelect(); return; } @@ -104,11 +107,11 @@ class _BeaconScanScreenState extends State with SingleTickerPr final regions = _uuidToBeaconId.keys.map((uuid) { // Format UUID with dashes for the plugin final formattedUUID = '${uuid.substring(0, 8)}-${uuid.substring(8, 12)}-${uuid.substring(12, 16)}-${uuid.substring(16, 20)}-${uuid.substring(20)}'; - debugPrint('[BeaconScan] Creating region for UUID: $uuid -> $formattedUUID'); + print('[BeaconScan] 🔍 Creating region for UUID: $uuid -> $formattedUUID'); return Region(identifier: uuid, proximityUUID: formattedUUID); }).toList(); - debugPrint('[BeaconScan] Created ${regions.length} regions for scanning'); + print('[BeaconScan] 📡 Created ${regions.length} regions for scanning'); if (regions.isEmpty) { if (mounted) _navigateToRestaurantSelect(); @@ -116,30 +119,30 @@ class _BeaconScanScreenState extends State with SingleTickerPr } // Perform 5 scans of 2 seconds each to increase chance of detecting all beacons - debugPrint('[BeaconScan] Starting 5 scan cycles of 2 seconds each'); + print('[BeaconScan] 🔄 Starting 5 scan cycles of 2 seconds each'); for (int scanCycle = 1; scanCycle <= 5; scanCycle++) { - debugPrint('[BeaconScan] ----- Scan cycle $scanCycle/5 -----'); + print('[BeaconScan] ----- Scan cycle $scanCycle/5 -----'); StreamSubscription? subscription; subscription = flutterBeacon.ranging(regions).listen((result) { - debugPrint('[BeaconScan] Ranging result: ${result.beacons.length} beacons in range'); + print('[BeaconScan] đŸ“ļ Ranging result: ${result.beacons.length} beacons in range'); for (var beacon in result.beacons) { final rawUUID = beacon.proximityUUID; final uuid = rawUUID.toUpperCase().replaceAll('-', ''); final rssi = beacon.rssi; - debugPrint('[BeaconScan] Raw beacon detected: UUID=$rawUUID (normalized: $uuid), RSSI=$rssi, Major=${beacon.major}, Minor=${beacon.minor}'); + print('[BeaconScan] 📍 Raw beacon detected: UUID=$rawUUID (normalized: $uuid), RSSI=$rssi, Major=${beacon.major}, Minor=${beacon.minor}'); if (_uuidToBeaconId.containsKey(uuid)) { // Collect RSSI samples for averaging _beaconRssiSamples.putIfAbsent(uuid, () => []).add(rssi); _beaconDetectionCount[uuid] = (_beaconDetectionCount[uuid] ?? 0) + 1; - debugPrint('[BeaconScan] MATCHED! BeaconID=${_uuidToBeaconId[uuid]}, Sample #${_beaconDetectionCount[uuid]}, RSSI=$rssi'); + print('[BeaconScan] ✅ MATCHED! BeaconID=${_uuidToBeaconId[uuid]}, Sample #${_beaconDetectionCount[uuid]}, RSSI=$rssi'); } else { - debugPrint('[BeaconScan] UUID not in database, ignoring'); + print('[BeaconScan] âš ī¸ UUID not in database, ignoring'); } } }); @@ -154,13 +157,13 @@ class _BeaconScanScreenState extends State with SingleTickerPr } } - debugPrint('[BeaconScan] All scan cycles complete'); + print('[BeaconScan] âœ”ī¸ All scan cycles complete'); if (!mounted) return; // Analyze results and select best beacon - debugPrint('[BeaconScan] ===== SCAN COMPLETE ====='); - debugPrint('[BeaconScan] Total beacons detected: ${_beaconRssiSamples.length}'); + print('[BeaconScan] ===== SCAN COMPLETE ====='); + print('[BeaconScan] 📊 Total beacons detected: ${_beaconRssiSamples.length}'); final beaconScores = {}; @@ -187,19 +190,19 @@ class _BeaconScanScreenState extends State with SingleTickerPr } if (beaconScores.isNotEmpty) { - debugPrint('[BeaconScan] Beacon analysis results:'); + print('[BeaconScan] Beacon analysis results:'); final sorted = beaconScores.values.toList() ..sort((a, b) => b.avgRssi.compareTo(a.avgRssi)); // Sort by avg RSSI descending for (final score in sorted) { - debugPrint('[BeaconScan] - BeaconID=${score.beaconId}:'); - debugPrint('[BeaconScan] Avg RSSI: ${score.avgRssi.toStringAsFixed(1)}'); - debugPrint('[BeaconScan] Range: ${score.minRssi} to ${score.maxRssi}'); - debugPrint('[BeaconScan] Detections: ${score.detectionCount}'); - debugPrint('[BeaconScan] Variance: ${score.variance.toStringAsFixed(2)}'); + print('[BeaconScan] - BeaconID=${score.beaconId}:'); + print('[BeaconScan] Avg RSSI: ${score.avgRssi.toStringAsFixed(1)}'); + print('[BeaconScan] Range: ${score.minRssi} to ${score.maxRssi}'); + print('[BeaconScan] Detections: ${score.detectionCount}'); + print('[BeaconScan] Variance: ${score.variance.toStringAsFixed(2)}'); } } - debugPrint('[BeaconScan] =========================='); + print('[BeaconScan] =========================='); if (beaconScores.isEmpty) { setState(() => _status = 'No beacons nearby'); @@ -209,18 +212,19 @@ class _BeaconScanScreenState extends State with SingleTickerPr // Find beacon with highest average RSSI and minimum detections final best = _findBestBeacon(beaconScores); if (best != null) { - debugPrint('[BeaconScan] Selected beacon: BeaconID=${best.beaconId} (avg RSSI: ${best.avgRssi.toStringAsFixed(1)})'); + print('[BeaconScan] đŸŽ¯ Selected beacon: BeaconID=${best.beaconId} (avg RSSI: ${best.avgRssi.toStringAsFixed(1)})'); setState(() => _status = 'Beacon detected! Loading business...'); await _autoSelectBusinessFromBeacon(best.beaconId); } else { - debugPrint('[BeaconScan] No beacon met minimum confidence threshold'); + print('[BeaconScan] âš ī¸ No beacon met minimum confidence threshold'); setState(() => _status = 'No strong beacon signal'); await Future.delayed(const Duration(milliseconds: 800)); if (mounted) _navigateToRestaurantSelect(); } } } catch (e) { - debugPrint('[BeaconScan] Error during scan: $e'); + print('[BeaconScan] ❌ ERROR during scan: $e'); + print('[BeaconScan] Stack trace: ${StackTrace.current}'); if (mounted) { setState(() => _status = 'Scan error - continuing to manual selection'); await Future.delayed(const Duration(seconds: 1)); diff --git a/lib/screens/restaurant_select_screen.dart b/lib/screens/restaurant_select_screen.dart index 909297c..f2fb0f1 100644 --- a/lib/screens/restaurant_select_screen.dart +++ b/lib/screens/restaurant_select_screen.dart @@ -37,16 +37,6 @@ class _RestaurantSelectScreenState extends State { Future _selectBusinessAndContinue(Restaurant r) async { final appState = context.read(); - // You MUST have a userId for ordering. - final userId = appState.userId; - if (userId == null || userId <= 0) { - if (!mounted) return; - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text("Missing UserID (not logged in).")), - ); - return; - } - // Set selected business appState.setBusiness(r.businessId); @@ -55,7 +45,6 @@ class _RestaurantSelectScreenState extends State { AppRoutes.servicePointSelect, arguments: { "BusinessID": r.businessId, - "UserID": userId, }, ); @@ -65,13 +54,12 @@ class _RestaurantSelectScreenState extends State { // Store selection in AppState appState.setServicePoint(sp.servicePointId); - // Navigate to Menu Browse + // Navigate to Menu Browse - user can browse anonymously Navigator.of(context).pushNamed( AppRoutes.menuBrowse, arguments: { "BusinessID": r.businessId, "ServicePointID": sp.servicePointId, - "UserID": userId, }, ); } diff --git a/lib/screens/splash_screen.dart b/lib/screens/splash_screen.dart index 5096c76..308c317 100644 --- a/lib/screens/splash_screen.dart +++ b/lib/screens/splash_screen.dart @@ -20,22 +20,29 @@ class _SplashScreenState extends State { @override void initState() { super.initState(); + print('[Splash] 🚀 SplashScreen initState called'); _timer = Timer(const Duration(milliseconds: 2400), () async { + print('[Splash] ⏰ Timer fired, starting navigation logic'); if (!mounted) return; // Check for saved authentication credentials + print('[Splash] 🔐 Checking for saved auth credentials...'); final credentials = await AuthStorage.loadAuth(); if (credentials != null) { + print('[Splash] ✅ Found saved credentials: UserID=${credentials.userId}'); // Restore authentication state Api.setAuthToken(credentials.token); final appState = context.read(); appState.setUserId(credentials.userId); + } else { + print('[Splash] â„šī¸ No saved credentials found'); } if (!mounted) return; // Always go to beacon scan first - allows browsing without login + print('[Splash] 📡 Navigating to beacon scan screen'); Navigator.of(context).pushReplacementNamed(AppRoutes.beaconScan); }); } diff --git a/lib/services/api.dart b/lib/services/api.dart index b953531..0b001b2 100644 --- a/lib/services/api.dart +++ b/lib/services/api.dart @@ -60,10 +60,8 @@ class Api { static String get baseUrl { const v = String.fromEnvironment("AALISTS_API_BASE_URL"); if (v.isEmpty) { - throw StateError( - "AALISTS_API_BASE_URL is not set. Example (Android emulator): " - "--dart-define=AALISTS_API_BASE_URL=http://10.0.2.2:8888/biz.payfrit.com/api", - ); + // Default to production API + return "https://biz.payfrit.com/api"; } return v; }