diff --git a/lib/main.dart b/lib/main.dart index bef4ff8..776a8f7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,6 +19,9 @@ Future main() async { await Supabase.initialize( url: _supabaseUrl, anonKey: _supabaseAnonKey, + authOptions: const FlutterAuthClientOptions( + authFlowType: AuthFlowType.implicit, + ), ); runApp(const Car64App()); @@ -33,10 +36,18 @@ final scaffoldMessengerKey = GlobalKey(); /// Show a snackbar safely through the global key. void showGlobalSnackBar(String message, {bool isError = false}) { - scaffoldMessengerKey.currentState?.showSnackBar( + final messenger = scaffoldMessengerKey.currentState; + if (messenger == null) return; + + messenger + ..hideCurrentSnackBar() + ..showSnackBar( SnackBar( content: Text(message), backgroundColor: isError ? Colors.red : null, + behavior: SnackBarBehavior.floating, + margin: const EdgeInsets.fromLTRB(16, 0, 16, 96), + duration: const Duration(seconds: 3), ), ); } @@ -78,6 +89,7 @@ class AuthGate extends StatefulWidget { class _AuthGateState extends State { bool _isLoading = true; + bool _isInPasswordRecoveryFlow = false; Session? _session; String? _lastEnsuredUserId; @@ -103,6 +115,7 @@ class _AuthGateState extends State { _ensureDefaultCollectionIfNeeded(); if (authState.event == AuthChangeEvent.passwordRecovery) { + setState(() => _isInPasswordRecoveryFlow = true); _showResetPasswordDialog(); } }, @@ -132,6 +145,10 @@ class _AuthGateState extends State { barrierDismissible: false, builder: (_) => const _ResetPasswordDialog(), ); + + if (mounted) { + setState(() => _isInPasswordRecoveryFlow = false); + } } @override @@ -141,6 +158,7 @@ class _AuthGateState extends State { body: Center(child: CircularProgressIndicator()), ); } + if (_isInPasswordRecoveryFlow) return const LoginScreen(); return _session != null ? const HomeShell() : const LoginScreen(); } } diff --git a/lib/scanner_screen.dart b/lib/scanner_screen.dart index 6e30a72..be7c858 100644 --- a/lib/scanner_screen.dart +++ b/lib/scanner_screen.dart @@ -37,6 +37,7 @@ class _ScannerScreenState extends State { bool _cameraReady = false; String? _lastDetected; bool _scanAccepted = false; + bool _scanNotFound = false; String _statusText = 'Ready to scan'; String? _activeCollectionId; bool _autoScanEnabled = false; @@ -96,6 +97,7 @@ class _ScannerScreenState extends State { void _toggleAutoScan() { setState(() { _autoScanEnabled = !_autoScanEnabled; + _scanNotFound = false; _statusText = _autoScanEnabled ? 'Auto scan enabled' : 'Auto scan paused'; }); } @@ -106,6 +108,7 @@ class _ScannerScreenState extends State { setState(() { _isBusy = true; + _scanNotFound = false; _statusText = 'Scanning…'; }); @@ -133,18 +136,11 @@ class _ScannerScreenState extends State { await _submitDetected(found); } } else { - // Show all detected text so user knows what was seen. - final allText = recognized.blocks.map((b) => b.text).join('\n'); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - allText.isEmpty - ? 'No text detected — try again closer.' - : 'No HW ID found. Detected:\n$allText', - ), - duration: const Duration(seconds: 4), - ), - ); + setState(() { + _scanAccepted = false; + _scanNotFound = true; + _statusText = 'No HW ID found'; + }); } } catch (e) { if (!mounted) return; @@ -178,11 +174,17 @@ class _ScannerScreenState extends State { setState(() { _lastDetected = null; _scanAccepted = true; + _scanNotFound = false; _statusText = 'Saved. Ready for next scan'; }); Future.delayed(const Duration(milliseconds: 650), () { if (!mounted) return; - setState(() => _scanAccepted = false); + setState(() { + _scanAccepted = false; + if (_statusText == 'Saved. Ready for next scan') { + _statusText = 'Ready to scan'; + } + }); }); } else { Navigator.of(context).pop(); @@ -253,7 +255,9 @@ class _ScannerScreenState extends State { padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), color: _scanAccepted ? AppColors.success.withValues(alpha: 0.12) - : Colors.black.withValues(alpha: 0.55), + : _scanNotFound + ? AppColors.error.withValues(alpha: 0.14) + : Colors.black.withValues(alpha: 0.55), child: Row( children: [ Expanded( @@ -261,7 +265,11 @@ class _ScannerScreenState extends State { _statusText, textAlign: TextAlign.center, style: TextStyle( - color: _scanAccepted ? AppColors.success : Colors.white, + color: _scanAccepted + ? AppColors.success + : _scanNotFound + ? AppColors.error + : Colors.white, fontSize: 12, fontWeight: FontWeight.w600, ), @@ -308,7 +316,9 @@ class _ScannerScreenState extends State { border: Border.all( color: (_scanAccepted ? AppColors.success - : AppColors.orange) + : _scanNotFound + ? AppColors.error + : AppColors.orange) .withValues(alpha: 0.9), width: 2.5, ), diff --git a/lib/screens/collections_screen.dart b/lib/screens/collections_screen.dart index be5f0f6..bfd3950 100644 --- a/lib/screens/collections_screen.dart +++ b/lib/screens/collections_screen.dart @@ -172,7 +172,7 @@ class CollectionsScreenState extends State { await prefs.setString(_activeCollectionPrefKey, collectionId); if (!mounted) return; setState(() => _activeCollectionId = collectionId); - showGlobalSnackBar('Active scan collection set.'); + showGlobalSnackBar('Main collection set for scanning.'); } @override @@ -419,7 +419,7 @@ class _CollectionCard extends StatelessWidget { borderRadius: BorderRadius.circular(6), ), child: const Text( - 'Active', + 'Main', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, @@ -436,7 +436,9 @@ class _CollectionCard extends StatelessWidget { Column( children: [ IconButton( - tooltip: isActive ? 'Already active for scan' : 'Set active for scan', + tooltip: isActive + ? 'Already main collection' + : 'Make main collection', icon: Icon( isActive ? Icons.my_location : Icons.location_searching, color: isActive ? AppColors.success : AppColors.textHint, diff --git a/lib/screens/garage_screen.dart b/lib/screens/garage_screen.dart index a6e8f51..2f4a86a 100644 --- a/lib/screens/garage_screen.dart +++ b/lib/screens/garage_screen.dart @@ -655,23 +655,26 @@ class GarageScreenState extends State { child: ElevatedButton.icon( onPressed: () => _editCar(car, context), icon: const Icon(Icons.edit, size: 18), - label: const Text('Edit Details'), - ), - ), - const SizedBox(width: 8), - Expanded( - child: OutlinedButton.icon( - onPressed: () => _deleteCar(car, context), - icon: const Icon(Icons.delete_outline, color: AppColors.error), - label: const Text('Remove', - style: TextStyle(color: AppColors.error)), - style: OutlinedButton.styleFrom( - side: const BorderSide(color: AppColors.error), - ), + label: const Text('Edit'), ), ), ], ), + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: OutlinedButton.icon( + onPressed: () => _deleteCar(car, context), + icon: const Icon(Icons.delete_outline, color: AppColors.error), + label: const Text( + 'Remove', + style: TextStyle(color: AppColors.error), + ), + style: OutlinedButton.styleFrom( + side: const BorderSide(color: AppColors.error), + ), + ), + ), ], ), ), diff --git a/lib/screens/home_shell.dart b/lib/screens/home_shell.dart index e833375..f25e32c 100644 --- a/lib/screens/home_shell.dart +++ b/lib/screens/home_shell.dart @@ -15,8 +15,6 @@ class _HomeShellState extends State { int _currentIndex = 0; final _collectionsKey = GlobalKey(); final _scanKey = GlobalKey(); - final Map _lastRefreshed = {}; - static const _refreshDebounce = Duration(seconds: 30); late final List _pages = [ CollectionsScreen(key: _collectionsKey), @@ -26,10 +24,7 @@ class _HomeShellState extends State { void _onTabSelected(int i) { setState(() => _currentIndex = i); - final now = DateTime.now(); - final last = _lastRefreshed[i]; - if (last != null && now.difference(last) < _refreshDebounce) return; - _lastRefreshed[i] = now; + if (i == 0) { _collectionsKey.currentState?.refresh(); } else if (i == 1) { diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index adf260f..bf4d457 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -76,7 +76,7 @@ class _LoginScreenState extends State try { await supabase.auth.resetPasswordForEmail( email, - redirectTo: 'hwcollector://login', + redirectTo: 'hwcollector://login/recovery', ); showGlobalSnackBar('Password reset email sent! Check your inbox.'); } on AuthException catch (e) { @@ -134,10 +134,17 @@ class _LoginScreenState extends State width: 2, ), ), - child: const Icon( - Icons.directions_car_filled, - size: 44, - color: Colors.white, + child: Padding( + padding: const EdgeInsets.all(14), + child: Image.asset( + 'assets/img/icon_bg_removed.png', + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) => const Icon( + Icons.directions_car_filled, + size: 44, + color: Colors.white, + ), + ), ), ), const SizedBox(height: 16), diff --git a/lib/screens/scan_tab.dart b/lib/screens/scan_tab.dart index cbbc557..e2b2185 100644 --- a/lib/screens/scan_tab.dart +++ b/lib/screens/scan_tab.dart @@ -156,7 +156,7 @@ class ScanTabState extends State { )) .toList(), onChanged: (id) { - _setActiveCollection(id); + _setSelectedCollection(id); }, ), ), @@ -227,15 +227,12 @@ class ScanTabState extends State { ); } - Future _setActiveCollection(String? id) async { + void _setSelectedCollection(String? id) { if (id == null) return; final matching = _collections.where((c) => c.id == id); if (matching.isEmpty) return; setState(() => _selectedCollection = matching.first); - - final prefs = await SharedPreferences.getInstance(); - await prefs.setString(_activeCollectionPrefKey, id); } Future _openScanner() async { @@ -244,7 +241,7 @@ class ScanTabState extends State { builder: (_) => ScannerScreen( collections: _collections, activeCollectionId: _selectedCollection?.id, - onCollectionChanged: _setActiveCollection, + onCollectionChanged: _setSelectedCollection, onDetected: (hwId) => _processHwId(hwId), ), ),