CoffeeAtHome/lib/main.dart
2026-03-29 08:13:38 -07:00

489 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:go_router/go_router.dart';
import 'providers/app_state.dart';
import 'screens/home_screen.dart';
import 'screens/beans_screen.dart';
import 'screens/machines_screen.dart';
import 'screens/recipes_screen.dart';
import 'screens/journal_screen.dart';
import 'screens/settings_screen.dart';
import 'components/global_search.dart';
void main() {
runApp(const CoffeeAtHomeApp());
}
class CoffeeAtHomeApp extends StatelessWidget {
const CoffeeAtHomeApp({super.key});
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [ChangeNotifierProvider(create: (_) => AppState())],
child: MaterialApp.router(
title: 'Coffee at Home',
theme: _buildTheme(),
routerConfig: _router,
),
);
}
ThemeData _buildTheme() {
return ThemeData(
useMaterial3: true,
colorScheme:
ColorScheme.fromSeed(
seedColor: const Color(0xFFD4A574), // Warm coffee brown
brightness: Brightness.dark,
).copyWith(
primary: const Color(0xFFD4A574),
secondary: const Color(0xFF6F4E37),
surface: const Color(0xFF2D2D2D),
onPrimary: const Color(0xFF1A1A1A),
onSecondary: const Color(0xFFFFFFFF),
onSurface: const Color(0xFFF5F5DC),
),
scaffoldBackgroundColor: const Color(0xFF1A1A1A),
cardTheme: CardThemeData(
color: const Color(0xFF2D2D2D),
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(12)),
side: const BorderSide(color: Color(0xFF3A3A3A)),
),
shadowColor: Colors.black.withValues(alpha: 0.4),
),
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF2D2D2D),
foregroundColor: Color(0xFFF5F5DC),
elevation: 0,
titleTextStyle: TextStyle(
color: Color(0xFFF5F5DC),
fontSize: 20,
fontWeight: FontWeight.w500,
),
surfaceTintColor: Colors.transparent,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: Color(0xFF2D2D2D),
selectedItemColor: Color(0xFFD4A574),
unselectedItemColor: Color(0xFFD2B48C),
type: BottomNavigationBarType.fixed,
elevation: 3,
),
floatingActionButtonTheme: const FloatingActionButtonThemeData(
backgroundColor: Color(0xFFD4A574),
foregroundColor: Color(0xFF1A1A1A),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFD4A574),
foregroundColor: const Color(0xFF1A1A1A),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
textStyle: const TextStyle(fontWeight: FontWeight.w600),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFFD4A574),
side: const BorderSide(color: Color(0xFFD4A574)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
),
textTheme: const TextTheme(
headlineLarge: TextStyle(
color: Color(0xFFF5F5DC),
fontWeight: FontWeight.w600,
),
headlineMedium: TextStyle(
color: Color(0xFFF5F5DC),
fontWeight: FontWeight.w600,
),
headlineSmall: TextStyle(
color: Color(0xFFF5F5DC),
fontWeight: FontWeight.w500,
),
titleLarge: TextStyle(
color: Color(0xFFF5F5DC),
fontWeight: FontWeight.w500,
),
titleMedium: TextStyle(
color: Color(0xFFF5F5DC),
fontWeight: FontWeight.w500,
),
titleSmall: TextStyle(
color: Color(0xFFF5F5DC),
fontWeight: FontWeight.w500,
),
bodyLarge: TextStyle(color: Color(0xFFD2B48C)),
bodyMedium: TextStyle(color: Color(0xFFD2B48C)),
bodySmall: TextStyle(color: Color(0xFFD2B48C)),
),
);
}
}
final GoRouter _router = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) {
return MainScaffold(child: child);
},
routes: [
GoRoute(path: '/', redirect: (_, __) => '/home'),
GoRoute(path: '/home', builder: (context, state) => const HomeScreen()),
GoRoute(
path: '/beans',
builder: (context, state) => const BeansScreen(),
),
GoRoute(
path: '/machines',
builder: (context, state) => const MachinesScreen(),
),
GoRoute(
path: '/recipes',
builder: (context, state) => const RecipesScreen(),
),
GoRoute(
path: '/journal',
builder: (context, state) => const JournalScreen(),
),
GoRoute(
path: '/settings',
builder: (context, state) => const SettingsScreen(),
),
],
),
],
);
class MainScaffold extends StatefulWidget {
final Widget child;
const MainScaffold({super.key, required this.child});
@override
State<MainScaffold> createState() => _MainScaffoldState();
}
class _MainScaffoldState extends State<MainScaffold> {
int _currentIndex = 0;
bool _isSearchOpen = false;
final List<String> _routes = [
'/home',
'/beans',
'/machines',
'/recipes',
'/journal',
'/settings',
];
void _onItemTapped(int index) {
setState(() {
_currentIndex = index;
});
context.go(_routes[index]);
}
void _openSearch() {
setState(() {
_isSearchOpen = true;
});
}
void _closeSearch() {
setState(() {
_isSearchOpen = false;
});
}
void _onSearchResultSelected(SearchResult result) {
switch (result.type) {
case 'Bean':
context.go('/beans');
break;
case 'Machine':
context.go('/machines');
break;
case 'Recipe':
context.go('/recipes');
break;
case 'Journal':
context.go('/journal');
break;
}
}
@override
Widget build(BuildContext context) {
// Update current index based on current route
final currentRoute = GoRouterState.of(context).uri.path;
final routeIndex = _routes.indexOf(currentRoute);
if (routeIndex != -1 && routeIndex != _currentIndex) {
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_currentIndex = routeIndex;
});
});
}
return Scaffold(
body: _isSearchOpen
? Stack(
children: [
Column(
children: [
// AppBar
Container(
decoration: const BoxDecoration(
color: Color(0xFF2D2D2D),
border: Border(
bottom: BorderSide(
color: Color(0xFF3A3A3A),
width: 1,
),
),
),
child: SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Expanded(
child: Text(
'Coffee at Home',
style: TextStyle(
color: Color(0xFFF5F5DC),
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
IconButton(
icon: const Icon(
Icons.search,
color: Color(0xFFF5F5DC),
),
onPressed: _openSearch,
),
],
),
),
),
),
// Body
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
width: double.infinity,
constraints: BoxConstraints(
maxWidth: constraints.maxWidth > 1200
? 1200
: constraints.maxWidth,
),
margin: EdgeInsets.symmetric(
horizontal: constraints.maxWidth > 1200
? (constraints.maxWidth - 1200) / 2
: 0,
),
padding: const EdgeInsets.symmetric(vertical: 16),
child: widget.child,
);
},
),
),
// Bottom Navigation
Container(
decoration: const BoxDecoration(
color: Color(0xFF2D2D2D),
border: Border(
top: BorderSide(color: Color(0xFF3A3A3A), width: 1),
),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, -2),
),
],
),
child: SafeArea(
child: SizedBox(
height: 56,
child: Row(
children: [
_buildBottomNavItem(0, Icons.home, 'Home'),
_buildBottomNavItem(1, Icons.coffee, 'Beans'),
_buildBottomNavItem(
2,
Icons.kitchen,
'Equipment',
),
_buildBottomNavItem(
3,
Icons.menu_book,
'Recipes',
),
_buildBottomNavItem(4, Icons.book, 'Journal'),
_buildBottomNavItem(
5,
Icons.settings,
'Settings',
),
],
),
),
),
),
],
),
GlobalSearchWidget(
isOpen: _isSearchOpen,
onClose: _closeSearch,
onResultSelected: _onSearchResultSelected,
),
],
)
: Column(
children: [
// AppBar
Container(
decoration: const BoxDecoration(
color: Color(0xFF2D2D2D),
border: Border(
bottom: BorderSide(color: Color(0xFF3A3A3A), width: 1),
),
),
child: SafeArea(
child: Container(
height: 56,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Expanded(
child: Text(
'Coffee at Home',
style: TextStyle(
color: Color(0xFFF5F5DC),
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
IconButton(
icon: const Icon(
Icons.search,
color: Color(0xFFF5F5DC),
),
onPressed: _openSearch,
),
],
),
),
),
),
// Body
Expanded(
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
width: double.infinity,
constraints: BoxConstraints(
maxWidth: constraints.maxWidth > 1200
? 1200
: constraints.maxWidth,
),
margin: EdgeInsets.symmetric(
horizontal: constraints.maxWidth > 1200
? (constraints.maxWidth - 1200) / 2
: 0,
),
padding: const EdgeInsets.symmetric(vertical: 16),
child: widget.child,
);
},
),
),
// Bottom Navigation
Container(
decoration: const BoxDecoration(
color: Color(0xFF2D2D2D),
border: Border(
top: BorderSide(color: Color(0xFF3A3A3A), width: 1),
),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 8,
offset: Offset(0, -2),
),
],
),
child: SafeArea(
child: SizedBox(
height: 56,
child: Row(
children: [
_buildBottomNavItem(0, Icons.home, 'Home'),
_buildBottomNavItem(1, Icons.coffee, 'Beans'),
_buildBottomNavItem(2, Icons.kitchen, 'Equipment'),
_buildBottomNavItem(3, Icons.menu_book, 'Recipes'),
_buildBottomNavItem(4, Icons.book, 'Journal'),
_buildBottomNavItem(5, Icons.settings, 'Settings'),
],
),
),
),
),
],
),
);
}
Widget _buildBottomNavItem(int index, IconData icon, String label) {
final isSelected = _currentIndex == index;
return Expanded(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => _onItemTapped(index),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 6),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: isSelected
? const Color(0xFFD4A574)
: const Color(0xFFD2B48C),
size: 22,
),
const SizedBox(height: 2),
Text(
label,
style: TextStyle(
color: isSelected
? const Color(0xFFD4A574)
: const Color(0xFFD2B48C),
fontSize: 11,
fontWeight: isSelected
? FontWeight.w600
: FontWeight.normal,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
),
),
);
}
}