diff --git a/Project2/lib/main.dart b/Project2/lib/main.dart index 97a37ba..6789c28 100644 --- a/Project2/lib/main.dart +++ b/Project2/lib/main.dart @@ -104,7 +104,41 @@ class MyApp extends StatelessWidget { ], child: MaterialApp.router( title: 'Smart WIC Cart', - theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.teal), + theme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFFD1001C), + primary: const Color(0xFFD1001C), + secondary: const Color(0xFFD1001C), + surface: Colors.white, + ), + scaffoldBackgroundColor: Colors.white, + appBarTheme: const AppBarTheme( + backgroundColor: Color(0xFFD1001C), + foregroundColor: Colors.white, + elevation: 2, + ), + cardTheme: CardThemeData( + color: Colors.white, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: BorderSide(color: Colors.grey.shade200, width: 1), + ), + ), + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom( + backgroundColor: const Color(0xFFD1001C), + foregroundColor: Colors.white, + disabledBackgroundColor: Colors.grey.shade300, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + ), + ), + floatingActionButtonTheme: const FloatingActionButtonThemeData( + backgroundColor: Color(0xFFD1001C), + foregroundColor: Colors.white, + ), + ), routerConfig: router, ), ); diff --git a/Project2/lib/screens/balances_screen.dart b/Project2/lib/screens/balances_screen.dart index c6b23bd..fe14cc5 100644 --- a/Project2/lib/screens/balances_screen.dart +++ b/Project2/lib/screens/balances_screen.dart @@ -47,7 +47,6 @@ class BalancesScreen extends StatelessWidget { return Scaffold( appBar: AppBar( title: const Text('WIC Benefits'), - centerTitle: true, actions: [ // Sign out button IconButton( @@ -145,10 +144,7 @@ class BalancesScreen extends StatelessWidget { class _BalanceCard extends StatelessWidget { const _BalanceCard({required this.category, required this.data}); - /// The canonical category name (uppercase). final String category; - - /// Balance data map with 'allowed' and 'used' keys from [AppState.balances]. final Map data; /// Calculates the color for the progress bar based on usage percentage. @@ -160,11 +156,11 @@ class _BalanceCard extends StatelessWidget { /// /// For unlimited categories (allowed is null), always returns green. Color _getProgressColor(int? allowed, int used) { - if (allowed == null) return Colors.green; + if (allowed == null) return const Color(0xFFD1001C); final pct = used / allowed; - if (pct < 0.6) return Colors.green; - if (pct < 0.85) return Colors.orange; - return Colors.red; + if (pct < 0.6) return const Color(0xFFD1001C); + if (pct < 0.85) return Colors.orange.shade600; + return Colors.red.shade700; } @override @@ -192,6 +188,7 @@ class _BalanceCard extends StatelessWidget { style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, + color: Color(0xFFD1001C), ), ), ), @@ -202,14 +199,14 @@ class _BalanceCard extends StatelessWidget { vertical: 4, ), decoration: BoxDecoration( - color: Colors.green.shade100, + color: const Color(0xFFD1001C).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12), ), - child: Text( + child: const Text( 'Unlimited', style: TextStyle( fontSize: 12, - color: Colors.green.shade700, + color: Color(0xFFD1001C), fontWeight: FontWeight.bold, ), ), diff --git a/Project2/lib/screens/basket_screen.dart b/Project2/lib/screens/basket_screen.dart index 0047cbd..539983d 100644 --- a/Project2/lib/screens/basket_screen.dart +++ b/Project2/lib/screens/basket_screen.dart @@ -36,7 +36,6 @@ class BasketScreen extends StatelessWidget { return Scaffold( appBar: AppBar( title: const Text('My Basket'), - backgroundColor: Theme.of(context).colorScheme.inversePrimary, actions: [ IconButton( icon: const Icon(Icons.logout), @@ -56,7 +55,15 @@ class BasketScreen extends StatelessWidget { Container( width: double.infinity, padding: const EdgeInsets.all(16), - color: Colors.blue.shade50, + decoration: BoxDecoration( + color: const Color(0xFFD1001C).withValues(alpha: 0.1), + border: Border( + bottom: BorderSide( + color: const Color(0xFFD1001C).withValues(alpha: 0.2), + width: 1, + ), + ), + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -65,14 +72,25 @@ class BasketScreen extends StatelessWidget { style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, + color: Color(0xFFD1001C), ), ), - Text( - '$totalItems', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.blue.shade700, + Container( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + decoration: BoxDecoration( + color: const Color(0xFFD1001C), + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '$totalItems', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), ), ], @@ -84,7 +102,7 @@ class BasketScreen extends StatelessWidget { itemCount: basket.length, itemBuilder: (context, index) { final item = basket[index]; - return _BasketItemTile(item: item); + return _BasketItem(item: item); }, ), ), @@ -118,7 +136,7 @@ class BasketScreen extends StatelessWidget { style: TextStyle(fontSize: 14, color: Colors.grey.shade500), ), const SizedBox(height: 24), - ElevatedButton.icon( + FilledButton.icon( onPressed: () => context.go('/scan'), icon: const Icon(Icons.qr_code_scanner), label: const Text('Start Scanning'), @@ -139,8 +157,8 @@ class BasketScreen extends StatelessWidget { /// Provides increment/decrement buttons that call [AppState.incrementItem] /// and [AppState.decrementItem] respectively. Buttons are styled with /// visual feedback and disabled states based on category limits. -class _BasketItemTile extends StatelessWidget { - const _BasketItemTile({required this.item}); +class _BasketItem extends StatelessWidget { + const _BasketItem({required this.item}); /// The basket item data map containing 'upc', 'name', 'category', and 'qty'. final Map item; @@ -158,12 +176,12 @@ class _BasketItemTile extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), child: ListTile( leading: CircleAvatar( - backgroundColor: Colors.blue.shade100, + backgroundColor: const Color(0xFFD1001C).withValues(alpha: 0.1), child: Text( qty.toString(), - style: TextStyle( + style: const TextStyle( fontWeight: FontWeight.bold, - color: Colors.blue.shade700, + color: Color(0xFFD1001C), ), ), ), @@ -175,7 +193,7 @@ class _BasketItemTile extends StatelessWidget { // Decrement button IconButton( icon: const Icon(Icons.remove_circle_outline), - color: Colors.red.shade400, + color: const Color(0xFFD1001C), onPressed: () => appState.decrementItem(upc), tooltip: 'Remove one', ), @@ -183,7 +201,7 @@ class _BasketItemTile extends StatelessWidget { IconButton( icon: Icon( Icons.add_circle_outline, - color: canAdd ? Colors.green.shade400 : Colors.grey.shade300, + color: canAdd ? const Color(0xFFD1001C) : Colors.grey.shade300, ), onPressed: canAdd ? () => appState.incrementItem(upc) : null, tooltip: canAdd ? 'Add one' : 'Category limit reached', diff --git a/Project2/lib/screens/scan_screen.dart b/Project2/lib/screens/scan_screen.dart index 03e735a..d80cdcf 100644 --- a/Project2/lib/screens/scan_screen.dart +++ b/Project2/lib/screens/scan_screen.dart @@ -50,20 +50,20 @@ class _ScanScreenState extends State { /// /// Uses test UPC `000000743266` to verify [AplService] can read from Firestore. /// Displays success or error message via [_snack]. - Future _diagnose() async { - const testUpc = '000000743266'; - try { - final info = await _apl.findByUpc(testUpc); - if (!mounted) return; - _snack( - info == null - ? 'Firestore MISSING: $testUpc' - : 'Firestore OK: $testUpc → ${info['name']}', - ); - } catch (e) { - _snack('Firestore ERROR: $e'); - } - } + // Future _diagnose() async { + // const testUpc = '000000743266'; + // try { + // final info = await _apl.findByUpc(testUpc); + // if (!mounted) return; + // _snack( + // info == null + // ? 'Firestore MISSING: $testUpc' + // : 'Firestore OK: $testUpc → ${info['name']}', + // ); + // } catch (e) { + // _snack('Firestore ERROR: $e'); + // } + // } /// Checks WIC eligibility for the scanned/entered barcode. /// @@ -121,13 +121,28 @@ class _ScanScreenState extends State { } final appState = context.read(); + final category = _lastInfo!['category'] ?? 'Unknown'; + + // Check if item can be added + if (!appState.canAdd(category)) { + _snack('Cannot add: Category limit reached'); + return; + } + appState.addItem( upc: _lastScanned ?? '', name: _lastInfo!['name'] ?? 'Unknown', - category: _lastInfo!['category'] ?? 'Unknown', + category: category, ); _snack('Added ${_lastInfo!['name']} to basket'); + + // Clear the scanned item after adding + setState(() { + _lastScanned = null; + _lastInfo = null; + _input.clear(); + }); } /// Handles barcode detection from [MobileScanner]. @@ -144,20 +159,17 @@ class _ScanScreenState extends State { @override Widget build(BuildContext context) { + final appState = context.watch(); final isMobile = MediaQuery.of(context).size.width < 600; + final canAdd = + _lastInfo != null && appState.canAdd(_lastInfo!['category'] ?? ''); return Scaffold( appBar: AppBar( title: const Text('Scan Product'), actions: [ - IconButton( - icon: const Icon(Icons.bug_report), - tooltip: 'Test Firestore', - onPressed: _diagnose, - ), IconButton( icon: const Icon(Icons.logout), - tooltip: 'Log out', onPressed: () async { await FirebaseAuth.instance.signOut(); if (context.mounted) context.go('/login'); @@ -176,6 +188,7 @@ class _ScanScreenState extends State { style: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, + color: Color(0xFFD1001C), ), ), const SizedBox(height: 16), @@ -184,7 +197,16 @@ class _ScanScreenState extends State { height: 280, child: ClipRRect( borderRadius: BorderRadius.circular(12), - child: MobileScanner(onDetect: _onDetect), + child: Container( + decoration: BoxDecoration( + border: Border.all( + color: const Color(0xFFD1001C), + width: 3, + ), + borderRadius: BorderRadius.circular(12), + ), + child: MobileScanner(onDetect: _onDetect), + ), ), ), const SizedBox(height: 16), @@ -199,8 +221,13 @@ class _ScanScreenState extends State { ), const SizedBox(width: 12), FilledButton( - onPressed: _lastInfo != null ? _addToBasket : null, - child: const Text('Add to Cart'), + onPressed: canAdd ? _addToBasket : null, + style: FilledButton.styleFrom( + backgroundColor: canAdd + ? const Color(0xFFD1001C) + : Colors.grey.shade300, + ), + child: const Text('Add to Basket'), ), ], ), @@ -218,10 +245,53 @@ class _ScanScreenState extends State { style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, + color: Color(0xFFD1001C), + ), + ), + const SizedBox(height: 4), + Text( + 'Category: ${_lastInfo!['category']}', + style: TextStyle(color: Colors.grey.shade700), + ), + Text( + 'UPC: $_lastScanned', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 12, ), ), - Text('Category: ${_lastInfo!['category']}'), - Text('UPC: $_lastScanned'), + if (!canAdd) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning_amber_rounded, + size: 16, + color: Colors.orange.shade700, + ), + const SizedBox(width: 4), + Text( + 'Category limit reached', + style: TextStyle( + color: Colors.orange.shade700, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], ], ), ), @@ -231,60 +301,172 @@ class _ScanScreenState extends State { ), ), ) - : Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 400), - child: Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text( - 'Enter UPC manually', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + : SafeArea( + child: Center( + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Card( + elevation: 4, + child: Container( + constraints: const BoxConstraints(maxWidth: 500), + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.qr_code_scanner, + size: 64, + color: const Color(0xFFD1001C), ), - ), - const SizedBox(height: 16), - TextField( - controller: _input, - decoration: const InputDecoration( - labelText: 'UPC Code', - border: OutlineInputBorder(), + const SizedBox(height: 24), + const Text( + 'Enter UPC Code', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Color(0xFFD1001C), + ), ), - onSubmitted: _checkEligibility, - ), - const SizedBox(height: 16), - Row( - children: [ - Expanded( - child: FilledButton( - onPressed: () => _checkEligibility(_input.text), - child: const Text('Check'), + const SizedBox(height: 8), + Text( + 'Type the barcode number manually', + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 32), + TextField( + controller: _input, + decoration: InputDecoration( + labelText: 'UPC Code', + hintText: '000000000000', + prefixIcon: const Icon( + Icons.numbers, + color: Color(0xFFD1001C), + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Color(0xFFD1001C), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Color(0xFFD1001C), + width: 2, + ), ), ), - const SizedBox(width: 12), - Expanded( - child: FilledButton( - onPressed: _lastInfo != null - ? _addToBasket - : null, - child: const Text('Add'), + keyboardType: TextInputType.number, + maxLength: 13, + onSubmitted: (value) { + if (value.isNotEmpty) { + _checkEligibility(value); + } + }, + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: FilledButton.icon( + onPressed: () { + final upc = _input.text.trim(); + if (upc.isNotEmpty) { + _checkEligibility(upc); + } + }, + icon: const Icon(Icons.search), + label: const Text('Check'), + ), + ), + if (_lastInfo != null) ...[ + const SizedBox(width: 12), + Expanded( + child: FilledButton.icon( + onPressed: canAdd ? _addToBasket : null, + style: FilledButton.styleFrom( + backgroundColor: canAdd + ? const Color(0xFFD1001C) + : Colors.grey.shade300, + ), + icon: const Icon(Icons.add_shopping_cart), + label: const Text('Add'), + ), + ), + ], + ], + ), + if (_lastInfo != null) ...[ + const SizedBox(height: 24), + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color( + 0xFFD1001C, + ).withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color( + 0xFFD1001C, + ).withValues(alpha: 0.2), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _lastInfo!['name'] ?? 'Unknown', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: Color(0xFFD1001C), + ), + ), + const SizedBox(height: 8), + Text( + 'Category: ${_lastInfo!['category']}', + style: TextStyle( + color: Colors.grey.shade700, + ), + ), + Text( + 'UPC: $_lastScanned', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 12, + ), + ), + if (!canAdd) ...[ + const SizedBox(height: 12), + Row( + children: [ + Icon( + Icons.warning_amber_rounded, + size: 16, + color: Colors.orange.shade700, + ), + const SizedBox(width: 4), + Text( + 'Category limit reached', + style: TextStyle( + color: Colors.orange.shade700, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ], ), ), ], - ), - if (_lastInfo != null) ...[ - const SizedBox(height: 16), - Text( - _lastInfo!['name'] ?? 'Unknown', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - Text('Category: ${_lastInfo!['category']}'), ], - ], + ), ), ), ),