diff --git a/lib/main.dart b/lib/main.dart index 5a75fba..4757a42 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -26,31 +26,32 @@ class MyApp extends StatelessWidget { themeMode: provider.themeMode, theme: ThemeData( useMaterial3: true, - scaffoldBackgroundColor: const Color(0xFFEFF1F1), // Light Grey + scaffoldBackgroundColor: const Color(0xFFF5F1ED), // Warm cream background colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF253ABD), // Primary Blue + seedColor: const Color(0xFF6F4E37), // Coffee brown brightness: Brightness.light, - primary: const Color(0xFF253ABD), - secondary: const Color(0xFFE5E5EA), // Light Grey for elements - surface: const Color(0xFFFFFFFF), // White for cards + primary: const Color(0xFF6F4E37), // Coffee brown + secondary: const Color(0xFFE5DDD5), // Warm light beige + surface: const Color(0xFFFFFBF7), // Warm white for cards onPrimary: const Color(0xFFFFFFFF), - onSurface: const Color(0xFF000000), + onSurface: const Color(0xFF2B1F1A), // Dark brown text + tertiary: const Color(0xFFD2691E), // Warm amber accent ), textTheme: _buildTextTheme(Brightness.light), appBarTheme: AppBarTheme( - backgroundColor: const Color(0xFFEFF1F1), - foregroundColor: Colors.black, + backgroundColor: const Color(0xFFF5F1ED), + foregroundColor: const Color(0xFF2B1F1A), elevation: 0, centerTitle: true, titleTextStyle: TextStyle( fontFamily: 'RobotoMono', fontSize: 20, fontWeight: FontWeight.bold, - color: Colors.black, + color: const Color(0xFF2B1F1A), ), ), floatingActionButtonTheme: const FloatingActionButtonThemeData( - backgroundColor: Color(0xFF253ABD), + backgroundColor: Color(0xFF6F4E37), // Coffee brown foregroundColor: Colors.white, ), ), @@ -58,13 +59,14 @@ class MyApp extends StatelessWidget { useMaterial3: true, scaffoldBackgroundColor: const Color(0xFF000000), // Black colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFFFF9F0A), // Technical Orange + seedColor: const Color(0xFFD2691E), // Warm amber (chocolate) brightness: Brightness.dark, - primary: const Color(0xFFFF9F0A), + primary: const Color(0xFFD2691E), // Warm amber secondary: const Color(0xFF3A3A3C), // Dark Grey for elements surface: const Color(0xFF1C1C1E), // Slightly lighter grey for cards onPrimary: const Color(0xFF000000), onSurface: const Color(0xFFFFFFFF), + tertiary: const Color(0xFFFF9F0A), // Keep technical orange as accent ), textTheme: _buildTextTheme(Brightness.dark), appBarTheme: AppBarTheme( @@ -80,8 +82,8 @@ class MyApp extends StatelessWidget { ), ), floatingActionButtonTheme: const FloatingActionButtonThemeData( - backgroundColor: Color(0xFFFF9F0A), - foregroundColor: Colors.black, + backgroundColor: Color(0xFFD2691E), // Warm amber + foregroundColor: Colors.white, ), ), home: const AppHome(), diff --git a/lib/screens/add_bean_screen.dart b/lib/screens/add_bean_screen.dart index 6ffa403..50f77f0 100644 --- a/lib/screens/add_bean_screen.dart +++ b/lib/screens/add_bean_screen.dart @@ -431,7 +431,7 @@ class _AddBeanScreenState extends State { _buildLabel('NOTES'), _buildTextField( _notesController, - 'Tasting notes, etc.', + 'What makes these beans special?', maxLines: 3, ), const SizedBox(height: 24), diff --git a/lib/screens/add_shot_screen.dart b/lib/screens/add_shot_screen.dart index 65d1527..ad84e51 100644 --- a/lib/screens/add_shot_screen.dart +++ b/lib/screens/add_shot_screen.dart @@ -16,6 +16,18 @@ class AddShotScreen extends StatefulWidget { } class _AddShotScreenState extends State { + // Encouragement messages for shot logging + static const _encouragementMessages = [ + 'Nice pull! 🎯', + 'Another one dialed in! ☕', + 'Shot logged! Keep brewing! 💪', + 'Great work! ✨', + ]; + + // Perfect shot threshold: flavor coordinates within ±0.2 of center + // (center = 0,0 meaning balanced between sour/bitter and weak/strong) + static const _perfectShotThreshold = 0.2; + final _doseInController = TextEditingController(text: '18.0'); final _doseOutController = TextEditingController(text: '36.0'); final _durationController = TextEditingController(text: '00:00.0'); @@ -231,6 +243,37 @@ class _AddShotScreenState extends State { context, listen: false, ).addShot(widget.beanId, shot, updatePreferredGrind: _updatePreferred); + + // Show encouraging message based on flavor profile + // Note: shotCount includes the newly added shot since addShot was called above + final isPerfectShot = _flavourX.abs() < _perfectShotThreshold && + _flavourY.abs() < _perfectShotThreshold; + final shotCount = Provider.of(context, listen: false) + .beans + .firstWhere((b) => b.id == widget.beanId) + .shots + .length; + + String message; + if (isPerfectShot) { + message = '🎯 Perfect shot! You nailed it!'; + } else if (shotCount >= 10 && shotCount % 10 == 0) { + message = '🔥 ${shotCount} shots logged! You\'re on fire!'; + } else { + message = _encouragementMessages[(shotCount - 1) % _encouragementMessages.length]; + } + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: isPerfectShot + ? Colors.green + : Theme.of(context).colorScheme.primary, + behavior: SnackBarBehavior.floating, + duration: const Duration(seconds: 2), + ), + ); + Navigator.pop(context); } else { ScaffoldMessenger.of(context).showSnackBar( @@ -605,14 +648,29 @@ class _AddShotScreenState extends State { // Flavour Graph ExpansionTile( - title: Text( - 'FLAVOUR PROFILE', - style: TextStyle( - fontFamily: 'RobotoMono', - fontWeight: FontWeight.bold, - fontSize: 14, - color: Theme.of(context).colorScheme.onSurface, - ), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'FLAVOUR PROFILE', + style: TextStyle( + fontFamily: 'RobotoMono', + fontWeight: FontWeight.bold, + fontSize: 14, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 4), + Text( + 'Tap the grid to map your shot\'s taste', + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 11, + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.5), + fontStyle: FontStyle.italic, + ), + ), + ], ), children: [ const SizedBox(height: 12), diff --git a/lib/screens/bean_detail_screen.dart b/lib/screens/bean_detail_screen.dart index 0e4820f..d7e76a9 100644 --- a/lib/screens/bean_detail_screen.dart +++ b/lib/screens/bean_detail_screen.dart @@ -342,7 +342,15 @@ class _BeanDetailScreenState extends State { ), ], ), - const SizedBox(height: 16), + const SizedBox(height: 24), + + // Visual separator + Divider( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1), + thickness: 1, + height: 1, + ), + const SizedBox(height: 24), // Flavor Wheel (Radar Chart) _buildBentoContainer( @@ -457,7 +465,15 @@ class _BeanDetailScreenState extends State { }, ), ), - const SizedBox(height: 16), + const SizedBox(height: 24), + + // Visual separator + Divider( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1), + thickness: 1, + height: 1, + ), + const SizedBox(height: 24), // Chart Bento if (shots.length > 1) @@ -605,7 +621,42 @@ class _BeanDetailScreenState extends State { ), ), const SizedBox(height: 8), - ...shots.map((shot) { + if (shots.isEmpty) + Container( + padding: const EdgeInsets.all(32), + child: Center( + child: Column( + children: [ + Icon( + Icons.emoji_events_outlined, + size: 48, + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.4), + ), + const SizedBox(height: 12), + Text( + 'No shots logged yet', + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 16, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 4), + Text( + 'Pull your first! 🎯', + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + ), + ) + else + ...shots.map((shot) { final machine = provider.machines.firstWhere( (m) => m.id == shot.machineId, orElse: () => CoffeeMachine(name: 'Unknown', id: ''), @@ -763,13 +814,27 @@ class _BeanDetailScreenState extends State { height: height, padding: const EdgeInsets.all(20), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surface.withValues(alpha: 0.9), + ], + ), borderRadius: BorderRadius.circular(24), border: Border.all( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.05), ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.06), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], ), child: child, ); @@ -786,6 +851,13 @@ class _BeanDetailScreenState extends State { context, ).colorScheme.onSurface.withValues(alpha: 0.05), ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.05), + blurRadius: 6, + offset: const Offset(0, 2), + ), + ], ), child: Column( children: [ diff --git a/lib/screens/bean_list_screen.dart b/lib/screens/bean_list_screen.dart index 25a3a9d..1a97830 100644 --- a/lib/screens/bean_list_screen.dart +++ b/lib/screens/bean_list_screen.dart @@ -23,7 +23,7 @@ class _BeanListScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Bean Vault'), + title: const Text('My Coffee Collection'), leading: const Icon(Icons.coffee), // Aesthetic icon actions: [ PopupMenuButton( @@ -98,6 +98,8 @@ class _BeanListScreenState extends State { }, backgroundColor: Theme.of(context).colorScheme.surface, selectedColor: Theme.of(context).colorScheme.primary, + elevation: isSelected ? 4 : 0, + shadowColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), labelStyle: TextStyle( color: isSelected ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurface, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, @@ -108,6 +110,7 @@ class _BeanListScreenState extends State { color: isSelected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.1), + width: isSelected ? 2 : 1, ), ), showCheckmark: false, @@ -125,16 +128,36 @@ class _BeanListScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( - Icons.coffee_maker_outlined, - size: 64, - color: Theme.of(context).colorScheme.secondary, + Icons.coffee, + size: 80, + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), ), const SizedBox(height: 16), Text( - 'No beans found', - style: TextStyle(fontFamily: 'RobotoMono', - fontSize: 18, - color: Theme.of(context).colorScheme.secondary, + 'Your bean collection is empty', + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 20, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), + ), + const SizedBox(height: 8), + Text( + 'Time to stock up! ☕', + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 16, + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.6), + ), + ), + const SizedBox(height: 8), + Text( + 'Tap + to add your first coffee', + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 14, + color: Theme.of(context).colorScheme.primary, ), ), ], diff --git a/lib/screens/gear_settings_screen.dart b/lib/screens/gear_settings_screen.dart index abb960a..8d47c63 100644 --- a/lib/screens/gear_settings_screen.dart +++ b/lib/screens/gear_settings_screen.dart @@ -279,7 +279,12 @@ class GearSettingsScreen extends StatelessWidget { color: Theme.of(context).colorScheme.onSurface, ), decoration: InputDecoration( - labelText: 'Name', + labelText: isMachine ? 'Machine Name' : 'Grinder Name', + hintText: isMachine ? 'e.g., "My trusty Gaggia"' : 'e.g., "Betsy the Grinder"', + hintStyle: TextStyle( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.4), + fontSize: 12, + ), labelStyle: const TextStyle(color: Colors.grey), enabledBorder: UnderlineInputBorder( borderSide: BorderSide( diff --git a/lib/screens/onboarding_screen.dart b/lib/screens/onboarding_screen.dart index 9482dae..2c57d47 100644 --- a/lib/screens/onboarding_screen.dart +++ b/lib/screens/onboarding_screen.dart @@ -28,7 +28,7 @@ class _OnboardingScreenState extends State { ), OnboardingPage( icon: Icons.inventory_2_outlined, - title: 'Bean Vault', + title: 'My Coffee Collection', description: 'Store and organize your coffee bean collection with detailed information.', details: [ @@ -71,7 +71,7 @@ class _OnboardingScreenState extends State { description: 'You\'re ready to start your coffee journey!', details: [ - '1. Tap + on Bean Vault to add your first bean', + '1. Tap + on My Coffee Collection to add your first bean', '2. Select a bean to view details', '3. Tap + on bean details to log a shot', '4. Use the dial to set your grind size', diff --git a/lib/widgets/analog_dial.dart b/lib/widgets/analog_dial.dart index aa9eed7..8470262 100644 --- a/lib/widgets/analog_dial.dart +++ b/lib/widgets/analog_dial.dart @@ -93,11 +93,22 @@ class _AnalogDialState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ // The Dial (Left side) - GestureDetector( - onPanUpdate: _onPanUpdate, - child: SizedBox( - width: 150, - height: 300, + Container( + width: 150, + height: 300, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface.withValues(alpha: 0.5), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: GestureDetector( + onPanUpdate: _onPanUpdate, child: CustomPaint( painter: _RadioTunerPainter( value: _currentValue, @@ -112,25 +123,40 @@ class _AnalogDialState extends State { ), const SizedBox(width: 20), // The Readout (Right side) - SizedBox( - //padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + Container( width: 150, - // decoration: BoxDecoration( - // color: Theme.of(context).colorScheme.surface, // Theme surface - // borderRadius: BorderRadius.circular(40), - // border: Border.all( - // color: Theme.of( - // context, - // ).colorScheme.onSurface.withValues(alpha: 0.1), - // ), - // ), - child: Text( - _currentValue.toStringAsFixed(1), - style: TextStyle( - fontFamily: 'RobotoMono', - fontSize: 56, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.onSurface, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surface.withValues(alpha: 0.9), + ], + ), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.3), + width: 2, + ), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], + ), + child: Center( + child: Text( + _currentValue.toStringAsFixed(1), + style: TextStyle( + fontFamily: 'RobotoMono', + fontSize: 56, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), ), ), ), diff --git a/lib/widgets/bean_card.dart b/lib/widgets/bean_card.dart index 569667c..403d7fc 100644 --- a/lib/widgets/bean_card.dart +++ b/lib/widgets/bean_card.dart @@ -14,13 +14,27 @@ class BeanCard extends StatelessWidget { child: Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.surface.withValues(alpha: 0.85), + ], + ), borderRadius: BorderRadius.circular(20), border: Border.all( color: Theme.of( context, ).colorScheme.onSurface.withValues(alpha: 0.1), ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.08), + blurRadius: 12, + offset: const Offset(0, 4), + ), + ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -87,14 +101,22 @@ class BeanCard extends StatelessWidget { bean.name, style: Theme.of( context, - ).textTheme.titleLarge?.copyWith(fontSize: 20), + ).textTheme.titleLarge?.copyWith( + fontSize: 24, + fontWeight: FontWeight.bold, + ), ), const SizedBox(height: 4), Text( - bean.notes.isNotEmpty ? bean.notes : 'No notes', + bean.notes.isNotEmpty ? bean.notes : 'Tell us about these beans...', style: Theme.of( context, - ).textTheme.bodyMedium?.copyWith(color: Colors.grey[600]), + ).textTheme.bodyMedium?.copyWith( + color: bean.notes.isNotEmpty + ? Colors.grey[600] + : Theme.of(context).colorScheme.primary.withValues(alpha: 0.5), + fontStyle: bean.notes.isEmpty ? FontStyle.italic : FontStyle.normal, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ),