From d0c4a11867ffbb96e0b3c3bd86bbc677f6a15f20 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Fri, 9 Feb 2024 13:51:01 +0100 Subject: [PATCH 01/10] Adding Meal model and data --- .idea/o-reilly-course-meals-app-flutter.iml | 4 + lib/category_meals_screen.dart | 8 +- lib/dummy_data.dart | 412 ++++++++++++++++++-- lib/models/category.dart | 2 +- lib/models/meal.dart | 29 ++ 5 files changed, 423 insertions(+), 32 deletions(-) create mode 100644 lib/models/meal.dart diff --git a/.idea/o-reilly-course-meals-app-flutter.iml b/.idea/o-reilly-course-meals-app-flutter.iml index 0daf8a9..f6cab43 100644 --- a/.idea/o-reilly-course-meals-app-flutter.iml +++ b/.idea/o-reilly-course-meals-app-flutter.iml @@ -3,10 +3,14 @@ + + + + \ No newline at end of file diff --git a/lib/category_meals_screen.dart b/lib/category_meals_screen.dart index 4580ae2..ae72230 100644 --- a/lib/category_meals_screen.dart +++ b/lib/category_meals_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:meals_app/dummy_data.dart'; class CategoryMealsScreen extends StatelessWidget { static const routeName = '/category-meals'; @@ -8,13 +9,18 @@ class CategoryMealsScreen extends StatelessWidget { final routeArgs = ModalRoute.of(context)?.settings.arguments as Map; final categoryTitle = routeArgs['title']; final categoryId = routeArgs['id']; + final categoryMeals = DUMMY_MEALS.where((meal) { + return meal.categories.contains(categoryId); + }).toList(); final theme = Theme.of(context); return Scaffold( appBar: AppBar( backgroundColor: theme.primaryColor, title: Text(categoryTitle!), ), - + body: ListView.builder(itemBuilder: (ctx, index){ + return Text(categoryMeals[index].title); + }, itemCount: categoryMeals.length,), ); } } diff --git a/lib/dummy_data.dart b/lib/dummy_data.dart index add0d22..ce1d435 100644 --- a/lib/dummy_data.dart +++ b/lib/dummy_data.dart @@ -1,56 +1,408 @@ import 'package:flutter/material.dart'; import './models/category.dart'; +import './models/meal.dart'; const DUMMY_CATEGORIES = [ Category( - 'c1', - 'Italian', - Colors.purple, + id: 'c1', + title: 'Italian', + color: Colors.purple, ), Category( - 'c2', - 'Quick & Easy', - Colors.red, + id: 'c2', + title: 'Quick & Easy', + color: Colors.red, ), Category( - 'c3', - 'Hamburgers', - Colors.orange, + id: 'c3', + title: 'Hamburgers', + color: Colors.orange, ), Category( - 'c4', - 'German', - Colors.amber, + id: 'c4', + title: 'German', + color: Colors.amber, ), Category( - 'c5', - 'Light & Lovely', - Colors.blue, + id: 'c5', + title: 'Light & Lovely', + color: Colors.blue, ), Category( - 'c6', - 'Exotic', - Colors.green, + id: 'c6', + title: 'Exotic', + color: Colors.green, ), Category( - 'c7', - 'Breakfast', - Colors.lightBlue, + id: 'c7', + title: 'Breakfast', + color: Colors.lightBlue, ), Category( - 'c8', - 'Asian', - Colors.lightGreen, + id: 'c8', + title: 'Asian', + color: Colors.lightGreen, ), Category( - 'c9', - 'French', - Colors.pink, + id: 'c9', + title: 'French', + color: Colors.pink, ), Category( - 'c10', - 'Summer', - Colors.teal, + id: 'c10', + title: 'Summer', + color: Colors.teal, + ), +]; + +const DUMMY_MEALS = [ + Meal( + id: 'm1', + categories: [ + 'c1', + 'c2', + ], + title: 'Spaghetti with Tomato Sauce', + affordability: Affordability.affordable, + complexity: Complexity.simple, + imageUrl: + 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Spaghetti_Bolognese_mit_Parmesan_oder_Grana_Padano.jpg/800px-Spaghetti_Bolognese_mit_Parmesan_oder_Grana_Padano.jpg', + duration: 20, + ingredients: [ + '4 Tomatoes', + '1 Tablespoon of Olive Oil', + '1 Onion', + '250g Spaghetti', + 'Spices', + 'Cheese (optional)' + ], + steps: [ + 'Cut the tomatoes and the onion into small pieces.', + 'Boil some water - add salt to it once it boils.', + 'Put the spaghetti into the boiling water - they should be done in about 10 to 12 minutes.', + 'In the meantime, heaten up some olive oil and add the cut onion.', + 'After 2 minutes, add the tomato pieces, salt, pepper and your other spices.', + 'The sauce will be done once the spaghetti are.', + 'Feel free to add some cheese on top of the finished dish.' + ], + isGlutenFree: false, + isVegan: true, + isVegetarian: true, + isLactoseFree: true, + ), + Meal( + id: 'm2', + categories: [ + 'c2', + ], + title: 'Toast Hawaii', + affordability: Affordability.affordable, + complexity: Complexity.simple, + imageUrl: + 'https://cdn.pixabay.com/photo/2018/07/11/21/51/toast-3532016_1280.jpg', + duration: 10, + ingredients: [ + '1 Slice White Bread', + '1 Slice Ham', + '1 Slice Pineapple', + '1-2 Slices of Cheese', + 'Butter' + ], + steps: [ + 'Butter one side of the white bread', + 'Layer ham, the pineapple and cheese on the white bread', + 'Bake the toast for round about 10 minutes in the oven at 200°C' + ], + isGlutenFree: false, + isVegan: false, + isVegetarian: false, + isLactoseFree: false, + ), + Meal( + id: 'm3', + categories: [ + 'c2', + 'c3', + ], + title: 'Classic Hamburger', + affordability: Affordability.pricey, + complexity: Complexity.simple, + imageUrl: + 'https://cdn.pixabay.com/photo/2014/10/23/18/05/burger-500054_1280.jpg', + duration: 45, + ingredients: [ + '300g Cattle Hack', + '1 Tomato', + '1 Cucumber', + '1 Onion', + 'Ketchup', + '2 Burger Buns' + ], + steps: [ + 'Form 2 patties', + 'Fry the patties for c. 4 minutes on each side', + 'Quickly fry the buns for c. 1 minute on each side', + 'Bruch buns with ketchup', + 'Serve burger with tomato, cucumber and onion' + ], + isGlutenFree: false, + isVegan: false, + isVegetarian: false, + isLactoseFree: true, + ), + Meal( + id: 'm4', + categories: [ + 'c4', + ], + title: 'Wiener Schnitzel', + affordability: Affordability.luxurious, + complexity: Complexity.challenging, + imageUrl: + 'https://cdn.pixabay.com/photo/2018/03/31/19/29/schnitzel-3279045_1280.jpg', + duration: 60, + ingredients: [ + '8 Veal Cutlets', + '4 Eggs', + '200g Bread Crumbs', + '100g Flour', + '300ml Butter', + '100g Vegetable Oil', + 'Salt', + 'Lemon Slices' + ], + steps: [ + 'Tenderize the veal to about 2–4mm, and salt on both sides.', + 'On a flat plate, stir the eggs briefly with a fork.', + 'Lightly coat the cutlets in flour then dip into the egg, and finally, coat in breadcrumbs.', + 'Heat the butter and oil in a large pan (allow the fat to get very hot) and fry the schnitzels until golden brown on both sides.', + 'Make sure to toss the pan regularly so that the schnitzels are surrounded by oil and the crumbing becomes ‘fluffy’.', + 'Remove, and drain on kitchen paper. Fry the parsley in the remaining oil and drain.', + 'Place the schnitzels on awarmed plate and serve garnishedwith parsley and slices of lemon.' + ], + isGlutenFree: false, + isVegan: false, + isVegetarian: false, + isLactoseFree: false, + ), + Meal( + id: 'm5', + categories: [ + 'c2' + 'c5', + 'c10', + ], + title: 'Salad with Smoked Salmon', + affordability: Affordability.luxurious, + complexity: Complexity.simple, + imageUrl: + 'https://cdn.pixabay.com/photo/2016/10/25/13/29/smoked-salmon-salad-1768890_1280.jpg', + duration: 15, + ingredients: [ + 'Arugula', + 'Lamb\'s Lettuce', + 'Parsley', + 'Fennel', + '200g Smoked Salmon', + 'Mustard', + 'Balsamic Vinegar', + 'Olive Oil', + 'Salt and Pepper' + ], + steps: [ + 'Wash and cut salad and herbs', + 'Dice the salmon', + 'Process mustard, vinegar and olive oil into a dessing', + 'Prepare the salad', + 'Add salmon cubes and dressing' + ], + isGlutenFree: true, + isVegan: false, + isVegetarian: true, + isLactoseFree: true, + ), + Meal( + id: 'm6', + categories: [ + 'c6', + 'c10', + ], + title: 'Delicious Orange Mousse', + affordability: Affordability.affordable, + complexity: Complexity.hard, + imageUrl: + 'https://cdn.pixabay.com/photo/2017/05/01/05/18/pastry-2274750_1280.jpg', + duration: 240, + ingredients: [ + '4 Sheets of Gelatine', + '150ml Orange Juice', + '80g Sugar', + '300g Yoghurt', + '200g Cream', + 'Orange Peel', + ], + steps: [ + 'Dissolve gelatine in pot', + 'Add orange juice and sugar', + 'Take pot off the stove', + 'Add 2 tablespoons of yoghurt', + 'Stir gelatin under remaining yoghurt', + 'Cool everything down in the refrigerator', + 'Whip the cream and lift it under die orange mass', + 'Cool down again for at least 4 hours', + 'Serve with orange peel', + ], + isGlutenFree: true, + isVegan: false, + isVegetarian: true, + isLactoseFree: false, + ), + Meal( + id: 'm7', + categories: [ + 'c7', + ], + title: 'Pancakes', + affordability: Affordability.affordable, + complexity: Complexity.simple, + imageUrl: + 'https://cdn.pixabay.com/photo/2018/07/10/21/23/pancake-3529653_1280.jpg', + duration: 20, + ingredients: [ + '1 1/2 Cups all-purpose Flour', + '3 1/2 Teaspoons Baking Powder', + '1 Teaspoon Salt', + '1 Tablespoon White Sugar', + '1 1/4 cups Milk', + '1 Egg', + '3 Tablespoons Butter, melted', + ], + steps: [ + 'In a large bowl, sift together the flour, baking powder, salt and sugar.', + 'Make a well in the center and pour in the milk, egg and melted butter; mix until smooth.', + 'Heat a lightly oiled griddle or frying pan over medium high heat.', + 'Pour or scoop the batter onto the griddle, using approximately 1/4 cup for each pancake. Brown on both sides and serve hot.' + ], + isGlutenFree: true, + isVegan: false, + isVegetarian: true, + isLactoseFree: false, + ), + Meal( + id: 'm8', + categories: [ + 'c8', + ], + title: 'Creamy Indian Chicken Curry', + affordability: Affordability.pricey, + complexity: Complexity.challenging, + imageUrl: + 'https://cdn.pixabay.com/photo/2018/06/18/16/05/indian-food-3482749_1280.jpg', + duration: 35, + ingredients: [ + '4 Chicken Breasts', + '1 Onion', + '2 Cloves of Garlic', + '1 Piece of Ginger', + '4 Tablespoons Almonds', + '1 Teaspoon Cayenne Pepper', + '500ml Coconut Milk', + ], + steps: [ + 'Slice and fry the chicken breast', + 'Process onion, garlic and ginger into paste and sauté everything', + 'Add spices and stir fry', + 'Add chicken breast + 250ml of water and cook everything for 10 minutes', + 'Add coconut milk', + 'Serve with rice' + ], + isGlutenFree: true, + isVegan: false, + isVegetarian: false, + isLactoseFree: true, + ), + Meal( + id: 'm9', + categories: [ + 'c9', + ], + title: 'Chocolate Souffle', + affordability: Affordability.affordable, + complexity: Complexity.hard, + imageUrl: + 'https://cdn.pixabay.com/photo/2014/08/07/21/07/souffle-412785_1280.jpg', + duration: 45, + ingredients: [ + '1 Teaspoon melted Butter', + '2 Tablespoons white Sugar', + '2 Ounces 70% dark Chocolate, broken into pieces', + '1 Tablespoon Butter', + '1 Tablespoon all-purpose Flour', + '4 1/3 tablespoons cold Milk', + '1 Pinch Salt', + '1 Pinch Cayenne Pepper', + '1 Large Egg Yolk', + '2 Large Egg Whites', + '1 Pinch Cream of Tartar', + '1 Tablespoon white Sugar', + ], + steps: [ + 'Preheat oven to 190°C. Line a rimmed baking sheet with parchment paper.', + 'Brush bottom and sides of 2 ramekins lightly with 1 teaspoon melted butter; cover bottom and sides right up to the rim.', + 'Add 1 tablespoon white sugar to ramekins. Rotate ramekins until sugar coats all surfaces.', + 'Place chocolate pieces in a metal mixing bowl.', + 'Place bowl over a pan of about 3 cups hot water over low heat.', + 'Melt 1 tablespoon butter in a skillet over medium heat. Sprinkle in flour. Whisk until flour is incorporated into butter and mixture thickens.', + 'Whisk in cold milk until mixture becomes smooth and thickens. Transfer mixture to bowl with melted chocolate.', + 'Add salt and cayenne pepper. Mix together thoroughly. Add egg yolk and mix to combine.', + 'Leave bowl above the hot (not simmering) water to keep chocolate warm while you whip the egg whites.', + 'Place 2 egg whites in a mixing bowl; add cream of tartar. Whisk until mixture begins to thicken and a drizzle from the whisk stays on the surface about 1 second before disappearing into the mix.', + 'Add 1/3 of sugar and whisk in. Whisk in a bit more sugar about 15 seconds.', + 'whisk in the rest of the sugar. Continue whisking until mixture is about as thick as shaving cream and holds soft peaks, 3 to 5 minutes.', + 'Transfer a little less than half of egg whites to chocolate.', + 'Mix until egg whites are thoroughly incorporated into the chocolate.', + 'Add the rest of the egg whites; gently fold into the chocolate with a spatula, lifting from the bottom and folding over.', + 'Stop mixing after the egg white disappears. Divide mixture between 2 prepared ramekins. Place ramekins on prepared baking sheet.', + 'Bake in preheated oven until scuffles are puffed and have risen above the top of the rims, 12 to 15 minutes.', + ], + isGlutenFree: true, + isVegan: false, + isVegetarian: true, + isLactoseFree: false, + ), + Meal( + id: 'm10', + categories: [ + 'c2', + 'c5', + 'c10', + ], + title: 'Asparagus Salad with Cherry Tomatoes', + affordability: Affordability.luxurious, + complexity: Complexity.simple, + imageUrl: + 'https://cdn.pixabay.com/photo/2018/04/09/18/26/asparagus-3304997_1280.jpg', + duration: 30, + ingredients: [ + 'White and Green Asparagus', + '30g Pine Nuts', + '300g Cherry Tomatoes', + 'Salad', + 'Salt, Pepper and Olive Oil' + ], + steps: [ + 'Wash, peel and cut the asparagus', + 'Cook in salted water', + 'Salt and pepper the asparagus', + 'Roast the pine nuts', + 'Halve the tomatoes', + 'Mix with asparagus, salad and dressing', + 'Serve with Baguette' + ], + isGlutenFree: true, + isVegan: true, + isVegetarian: true, + isLactoseFree: true, ), ]; \ No newline at end of file diff --git a/lib/models/category.dart b/lib/models/category.dart index 08e7100..66c09b5 100644 --- a/lib/models/category.dart +++ b/lib/models/category.dart @@ -7,5 +7,5 @@ class Category{ final String title; final Color color; - const Category(this.id, this.title, this.color); + const Category({required this.id, required this.title, required this.color}); } \ No newline at end of file diff --git a/lib/models/meal.dart b/lib/models/meal.dart new file mode 100644 index 0000000..3fbb9a8 --- /dev/null +++ b/lib/models/meal.dart @@ -0,0 +1,29 @@ +enum Complexity{ + simple, + challenging, + hard +} + +enum Affordability { + affordable, + pricey, + luxurious +} +class Meal{ + final String id; + final List categories; + final String title; + final String imageUrl; + final List ingredients; + final List steps; + final int duration; + final Complexity complexity; + final Affordability affordability; + final bool isGlutenFree; + final bool isLactoseFree; + final bool isVegan; + final bool isVegetarian; + + const Meal({required this.id,required this.categories,required this.title, required this.imageUrl, required this.ingredients, required this.steps, required this.duration, required this.complexity, required this.affordability, + required this.isGlutenFree, required this.isLactoseFree, required this.isVegetarian, required this.isVegan}); +} \ No newline at end of file From ff0011c6b3be0d492e25fd9a497ff53104540499 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Fri, 9 Feb 2024 14:40:37 +0100 Subject: [PATCH 02/10] Add img to detail page --- lib/category_meals_screen.dart | 26 ------------ lib/main.dart | 4 +- lib/{ => screens}/categories_screen.dart | 2 +- lib/screens/category_meals_screen.dart | 31 ++++++++++++++ lib/{ => widgets}/category_item.dart | 2 +- lib/widgets/meal_item.dart | 53 ++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 30 deletions(-) delete mode 100644 lib/category_meals_screen.dart rename lib/{ => screens}/categories_screen.dart (94%) create mode 100644 lib/screens/category_meals_screen.dart rename lib/{ => widgets}/category_item.dart (94%) create mode 100644 lib/widgets/meal_item.dart diff --git a/lib/category_meals_screen.dart b/lib/category_meals_screen.dart deleted file mode 100644 index ae72230..0000000 --- a/lib/category_meals_screen.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:meals_app/dummy_data.dart'; - -class CategoryMealsScreen extends StatelessWidget { - static const routeName = '/category-meals'; - - @override - Widget build(BuildContext context) { - final routeArgs = ModalRoute.of(context)?.settings.arguments as Map; - final categoryTitle = routeArgs['title']; - final categoryId = routeArgs['id']; - final categoryMeals = DUMMY_MEALS.where((meal) { - return meal.categories.contains(categoryId); - }).toList(); - final theme = Theme.of(context); - return Scaffold( - appBar: AppBar( - backgroundColor: theme.primaryColor, - title: Text(categoryTitle!), - ), - body: ListView.builder(itemBuilder: (ctx, index){ - return Text(categoryMeals[index].title); - }, itemCount: categoryMeals.length,), - ); - } -} diff --git a/lib/main.dart b/lib/main.dart index cd04aa5..fcef7dd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:meals_app/categories_screen.dart'; -import 'package:meals_app/category_meals_screen.dart'; +import 'package:meals_app/screens/categories_screen.dart'; +import 'package:meals_app/screens/category_meals_screen.dart'; void main() => runApp(MyApp()); diff --git a/lib/categories_screen.dart b/lib/screens/categories_screen.dart similarity index 94% rename from lib/categories_screen.dart rename to lib/screens/categories_screen.dart index bc6df6b..a995c15 100644 --- a/lib/categories_screen.dart +++ b/lib/screens/categories_screen.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:meals_app/category_item.dart'; +import 'package:meals_app/widgets/category_item.dart'; import 'package:meals_app/dummy_data.dart'; class CategoriesScreen extends StatelessWidget { diff --git a/lib/screens/category_meals_screen.dart b/lib/screens/category_meals_screen.dart new file mode 100644 index 0000000..c740fd4 --- /dev/null +++ b/lib/screens/category_meals_screen.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:meals_app/dummy_data.dart'; +import 'package:meals_app/widgets/meal_item.dart'; + +class CategoryMealsScreen extends StatelessWidget { + static const routeName = '/category-meals'; + + @override + Widget build(BuildContext context) { + final routeArgs = + ModalRoute.of(context)?.settings.arguments as Map; + final categoryTitle = routeArgs['title']; + final categoryId = routeArgs['id']; + final categoryMeals = DUMMY_MEALS.where((meal) { + return meal.categories.contains(categoryId); + }).toList(); + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + backgroundColor: theme.primaryColor, + title: Text(categoryTitle!), + ), + body: ListView.builder( + itemBuilder: (ctx, index) { + return MealItem(title: categoryMeals[index].title, imageUrl: categoryMeals[index].imageUrl, duration: categoryMeals[index].duration, complexity: categoryMeals[index].complexity, affordability: categoryMeals[index].affordability); + }, + itemCount: categoryMeals.length, + ) + ); + } +} diff --git a/lib/category_item.dart b/lib/widgets/category_item.dart similarity index 94% rename from lib/category_item.dart rename to lib/widgets/category_item.dart index 065d7ff..bcd92aa 100644 --- a/lib/category_item.dart +++ b/lib/widgets/category_item.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:meals_app/category_meals_screen.dart'; +import 'package:meals_app/screens/category_meals_screen.dart'; class CategoryItem extends StatelessWidget { final String id; diff --git a/lib/widgets/meal_item.dart b/lib/widgets/meal_item.dart new file mode 100644 index 0000000..d6ac05f --- /dev/null +++ b/lib/widgets/meal_item.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:meals_app/models/meal.dart'; + +class MealItem extends StatelessWidget { + final String title; + final String imageUrl; + final int duration; + final Complexity complexity; + final Affordability affordability; + + const MealItem( + {super.key, + required this.title, + required this.imageUrl, + required this.duration, + required this.complexity, + required this.affordability}); + + void selectMeal() {} + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: selectMeal, + child: Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + elevation: 4, + margin: const EdgeInsets.all(10), + child: Column( + children: [ + Stack( + children: [ + ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(15), + topRight: Radius.circular(15)), + child: Image.network( + imageUrl, + height: 250, + width: double.infinity, + fit: BoxFit.cover, + ), + ) + ], + ) + ], + ), + ), + ); + } +} From c552f74c25b9e04ca60150ea876eb41196feceea Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Fri, 9 Feb 2024 15:31:46 +0100 Subject: [PATCH 03/10] Add detail meal screen --- lib/main.dart | 2 + lib/screens/category_meals_screen.dart | 2 +- lib/screens/meal_detail_screen.dart | 19 ++++++ lib/widgets/meal_item.dart | 89 +++++++++++++++++++++++--- 4 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 lib/screens/meal_detail_screen.dart diff --git a/lib/main.dart b/lib/main.dart index fcef7dd..a8bedd2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:meals_app/screens/categories_screen.dart'; import 'package:meals_app/screens/category_meals_screen.dart'; +import 'package:meals_app/screens/meal_detail_screen.dart'; void main() => runApp(MyApp()); @@ -26,6 +27,7 @@ class MyApp extends StatelessWidget { routes: { '/': (ctx) => const CategoriesScreen(), CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(), + MealDetailScreen.routeName: (ctx) => MealDetailScreen() }, ); } diff --git a/lib/screens/category_meals_screen.dart b/lib/screens/category_meals_screen.dart index c740fd4..0a1b929 100644 --- a/lib/screens/category_meals_screen.dart +++ b/lib/screens/category_meals_screen.dart @@ -22,7 +22,7 @@ class CategoryMealsScreen extends StatelessWidget { ), body: ListView.builder( itemBuilder: (ctx, index) { - return MealItem(title: categoryMeals[index].title, imageUrl: categoryMeals[index].imageUrl, duration: categoryMeals[index].duration, complexity: categoryMeals[index].complexity, affordability: categoryMeals[index].affordability); + return MealItem(title: categoryMeals[index].title, imageUrl: categoryMeals[index].imageUrl, duration: categoryMeals[index].duration, complexity: categoryMeals[index].complexity, affordability: categoryMeals[index].affordability, id: categoryMeals[index].id,); }, itemCount: categoryMeals.length, ) diff --git a/lib/screens/meal_detail_screen.dart b/lib/screens/meal_detail_screen.dart new file mode 100644 index 0000000..928d036 --- /dev/null +++ b/lib/screens/meal_detail_screen.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class MealDetailScreen extends StatelessWidget { + static const routeName = '/meal-detail'; + const MealDetailScreen({super.key}); + + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final mealId = ModalRoute.of(context)?.settings.arguments as String; + return Scaffold( + appBar: AppBar( + title: Text(mealId), + backgroundColor: theme.primaryColor, + ), + body: Center(child: Text(mealId),)); + } +} diff --git a/lib/widgets/meal_item.dart b/lib/widgets/meal_item.dart index d6ac05f..792a66f 100644 --- a/lib/widgets/meal_item.dart +++ b/lib/widgets/meal_item.dart @@ -1,27 +1,54 @@ import 'package:flutter/material.dart'; import 'package:meals_app/models/meal.dart'; +import 'package:meals_app/screens/meal_detail_screen.dart'; class MealItem extends StatelessWidget { + final String id; final String title; final String imageUrl; final int duration; final Complexity complexity; final Affordability affordability; - const MealItem( - {super.key, - required this.title, - required this.imageUrl, - required this.duration, - required this.complexity, - required this.affordability}); + const MealItem({ + super.key, + required this.id, + required this.title, + required this.imageUrl, + required this.duration, + required this.complexity, + required this.affordability + }); - void selectMeal() {} + String get complexityText { + switch (complexity) { + case Complexity.simple: + return 'Simple'; + case Complexity.hard: + return 'Hard'; + case Complexity.challenging: + return 'Challenging'; + } + } + + String get affordabilityText{ + switch (affordability){ + case Affordability.affordable: + return 'Affordable'; + case Affordability.pricey: + return 'Pricey'; + case Affordability.luxurious: + return 'Expensive'; + } + } + void selectMeal(BuildContext context) { + Navigator.of(context).pushNamed(MealDetailScreen.routeName, arguments: id); + } @override Widget build(BuildContext context) { return InkWell( - onTap: selectMeal, + onTap: () => selectMeal(context), child: Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), @@ -42,9 +69,51 @@ class MealItem extends StatelessWidget { width: double.infinity, fit: BoxFit.cover, ), + ), + Positioned( + bottom: 20, + right: 10, + child: Container( + width: 300, + color: Colors.black54, + padding: + const EdgeInsets.symmetric(vertical: 5, horizontal: 20), + child: Text( + title, + style: const TextStyle( + fontSize: 26, + color: Colors.white, + ), + softWrap: true, + overflow: TextOverflow.fade, + ), + ), ) ], - ) + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row(children: [ + const Icon(Icons.schedule), + const SizedBox(width: 6,), + Text('$duration min') + ],), + Row(children: [ + const Icon(Icons.work), + const SizedBox(width: 6,), + Text(complexityText) + ],), + Row(children: [ + const Icon(Icons.attach_money), + const SizedBox(width: 6,), + Text(affordabilityText) + ],) + ], + ), + ), ], ), ), From 6e400d258fbb36dfbc668aab363dcee38fa1c254 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Mon, 12 Feb 2024 13:38:09 +0100 Subject: [PATCH 04/10] Finishing detail page --- lib/main.dart | 3 ++ lib/screens/meal_detail_screen.dart | 77 +++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index a8bedd2..3b393f5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,6 +29,9 @@ class MyApp extends StatelessWidget { CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(), MealDetailScreen.routeName: (ctx) => MealDetailScreen() }, + onUnknownRoute: (settings) { + return MaterialPageRoute(builder: (ctx) => CategoriesScreen()); + }, ); } } diff --git a/lib/screens/meal_detail_screen.dart b/lib/screens/meal_detail_screen.dart index 928d036..65c5485 100644 --- a/lib/screens/meal_detail_screen.dart +++ b/lib/screens/meal_detail_screen.dart @@ -1,19 +1,86 @@ import 'package:flutter/material.dart'; +import 'package:meals_app/dummy_data.dart'; class MealDetailScreen extends StatelessWidget { static const routeName = '/meal-detail'; + const MealDetailScreen({super.key}); + Widget buildSectionTitle(BuildContext context, String text) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: Text( + text, + style: Theme.of(context).textTheme.titleLarge, + ), + ); + } + + Widget buildContainer(Widget child) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(10)), + margin: const EdgeInsets.all(10), + padding: const EdgeInsets.all(10), + height: 150, + width: 300, + child: child, + ); + } @override Widget build(BuildContext context) { final theme = Theme.of(context); final mealId = ModalRoute.of(context)?.settings.arguments as String; + final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId); return Scaffold( - appBar: AppBar( - title: Text(mealId), - backgroundColor: theme.primaryColor, - ), - body: Center(child: Text(mealId),)); + appBar: AppBar( + title: Text(selectedMeal.title), + backgroundColor: theme.primaryColor, + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + height: 300, + width: double.infinity, + child: Image.network( + selectedMeal.imageUrl, + fit: BoxFit.cover, + )), + buildSectionTitle(context, "Ingredients"), + buildContainer( + ListView.builder( + itemBuilder: (ctx, index) => Card( + color: theme.primaryColor, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + child: Text(selectedMeal.ingredients[index]), + ), + ), + itemCount: selectedMeal.ingredients.length, + ), + ), + buildSectionTitle(context, 'Steps'), + buildContainer(ListView.builder( + itemBuilder: (ctx, index) => Column( + children: [ + ListTile( + leading: CircleAvatar( + child: Text('# ${index + 1}'), + ), + title: Text(selectedMeal.steps[index]), + ), + const Divider() + ], + ), + itemCount: selectedMeal.steps.length, + )) + ], + ), + )); } } From 2b3bee69beaa7d4bf89d755de9ea6153a690aa73 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Mon, 12 Feb 2024 15:23:35 +0100 Subject: [PATCH 05/10] Add BottomTab an Drawer to the app --- lib/main.dart | 3 +- lib/screens/categories_screen.dart | 36 ++++++++---------- lib/screens/favorites_screen.dart | 10 +++++ lib/screens/tabs_screen.dart | 59 ++++++++++++++++++++++++++++++ lib/widgets/main_drawer.dart | 49 +++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 lib/screens/favorites_screen.dart create mode 100644 lib/screens/tabs_screen.dart create mode 100644 lib/widgets/main_drawer.dart diff --git a/lib/main.dart b/lib/main.dart index 3b393f5..e16330b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:meals_app/screens/categories_screen.dart'; import 'package:meals_app/screens/category_meals_screen.dart'; import 'package:meals_app/screens/meal_detail_screen.dart'; +import 'package:meals_app/screens/tabs_screen.dart'; void main() => runApp(MyApp()); @@ -25,7 +26,7 @@ class MyApp extends StatelessWidget { color: Color.fromRGBO(20, 51, 51, 1), ))), routes: { - '/': (ctx) => const CategoriesScreen(), + '/': (ctx) => const TabScreen(), CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(), MealDetailScreen.routeName: (ctx) => MealDetailScreen() }, diff --git a/lib/screens/categories_screen.dart b/lib/screens/categories_screen.dart index a995c15..e26a6e9 100644 --- a/lib/screens/categories_screen.dart +++ b/lib/screens/categories_screen.dart @@ -8,26 +8,22 @@ class CategoriesScreen extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return Scaffold( - appBar: AppBar( - elevation: 5, - title: const Text('DeliMeal'), - backgroundColor: theme.primaryColor, - ), - body: Container( - color: theme.canvasColor, - child: GridView( - padding: const EdgeInsets.all(15), - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - childAspectRatio: 3 / 2, - crossAxisSpacing: 20, - mainAxisSpacing: 20), - children: DUMMY_CATEGORIES - .map((catData) => - CategoryItem(title: catData.title, color: catData.color, id: catData.id,)) - .toList(), - ), + return Container( + color: theme.canvasColor, + child: GridView( + padding: const EdgeInsets.all(15), + gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + childAspectRatio: 3 / 2, + crossAxisSpacing: 20, + mainAxisSpacing: 20), + children: DUMMY_CATEGORIES + .map((catData) => CategoryItem( + title: catData.title, + color: catData.color, + id: catData.id, + )) + .toList(), ), ); } diff --git a/lib/screens/favorites_screen.dart b/lib/screens/favorites_screen.dart new file mode 100644 index 0000000..f98d3c9 --- /dev/null +++ b/lib/screens/favorites_screen.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class FavoritesScreen extends StatelessWidget { + const FavoritesScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/screens/tabs_screen.dart b/lib/screens/tabs_screen.dart new file mode 100644 index 0000000..d207736 --- /dev/null +++ b/lib/screens/tabs_screen.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:meals_app/screens/categories_screen.dart'; +import 'package:meals_app/screens/favorites_screen.dart'; +import 'package:meals_app/widgets/main_drawer.dart'; + +class TabScreen extends StatefulWidget { + const TabScreen({super.key}); + + @override + State createState() => _TabScreenState(); +} + +class _TabScreenState extends State { + final List _pages = [ + CategoriesScreen(), + FavoritesScreen(), + ]; + + int _selectedPageIndex = 0; + + void _selectPage(int index) { + setState(() { + _selectedPageIndex = index; + }); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + backgroundColor: theme.primaryColor, + title: Text(_selectedPageIndex == 0 ? 'Categories' : 'Favorites'), + ), + drawer: MainDrawer(), + body: _pages[_selectedPageIndex], + bottomNavigationBar: BottomNavigationBar( + onTap: _selectPage, + backgroundColor: theme.primaryColor, + unselectedItemColor: Colors.white, + selectedItemColor: Colors.black, + currentIndex: _selectedPageIndex, + type: BottomNavigationBarType.shifting, + items: [ + BottomNavigationBarItem( + icon: const Icon(Icons.category), + label: 'Categories', + backgroundColor: theme.primaryColor, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.star), + label: 'Favorites', + backgroundColor: theme.primaryColor, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/main_drawer.dart b/lib/widgets/main_drawer.dart new file mode 100644 index 0000000..613d328 --- /dev/null +++ b/lib/widgets/main_drawer.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +class MainDrawer extends StatelessWidget { + const MainDrawer({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + Widget buildListTile(String title, IconData iconData) { + return ListTile( + leading: Icon(iconData), // Create an Icon widget from the IconData + title: Text( + title, + style: const TextStyle( + fontFamily: 'RobotoCondensed', + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + // Add onTap functionality here + }, + ); + } + + return Drawer( + child: Column( + children: [ + Container( + height: 120, + color: theme.canvasColor, + width: double.infinity, + padding: const EdgeInsets.all(20), + alignment: Alignment.centerLeft, + child: Text('Cooking up', style: TextStyle( + fontWeight: FontWeight.w900, + fontSize: 30, + color: theme.primaryColor, + ),), + ), + const SizedBox(height: 20,), + buildListTile('Meals', Icons.restaurant), + buildListTile('Filters', Icons.settings) + ], + ), + ); + } +} From a104f1f438f79e3b94bb767a3aa325584b50fb3d Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Tue, 13 Feb 2024 09:19:21 +0100 Subject: [PATCH 06/10] Add custom drawer with links --- .idea/misc.xml | 1 - lib/main.dart | 4 +++- lib/screens/category_meals_screen.dart | 12 +++++++++--- lib/screens/filters_screen.dart | 23 +++++++++++++++++++++++ lib/widgets/main_drawer.dart | 26 +++++++++++++++++--------- 5 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 lib/screens/filters_screen.dart diff --git a/.idea/misc.xml b/.idea/misc.xml index 3827c7d..374f537 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/lib/main.dart b/lib/main.dart index e16330b..b563b07 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:meals_app/screens/categories_screen.dart'; import 'package:meals_app/screens/category_meals_screen.dart'; +import 'package:meals_app/screens/filters_screen.dart'; import 'package:meals_app/screens/meal_detail_screen.dart'; import 'package:meals_app/screens/tabs_screen.dart'; @@ -28,7 +29,8 @@ class MyApp extends StatelessWidget { routes: { '/': (ctx) => const TabScreen(), CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(), - MealDetailScreen.routeName: (ctx) => MealDetailScreen() + MealDetailScreen.routeName: (ctx) => MealDetailScreen(), + FilterScreen.routeName: (ctx) => FilterScreen() }, onUnknownRoute: (settings) { return MaterialPageRoute(builder: (ctx) => CategoriesScreen()); diff --git a/lib/screens/category_meals_screen.dart b/lib/screens/category_meals_screen.dart index 0a1b929..4bb8d41 100644 --- a/lib/screens/category_meals_screen.dart +++ b/lib/screens/category_meals_screen.dart @@ -22,10 +22,16 @@ class CategoryMealsScreen extends StatelessWidget { ), body: ListView.builder( itemBuilder: (ctx, index) { - return MealItem(title: categoryMeals[index].title, imageUrl: categoryMeals[index].imageUrl, duration: categoryMeals[index].duration, complexity: categoryMeals[index].complexity, affordability: categoryMeals[index].affordability, id: categoryMeals[index].id,); + return MealItem( + title: categoryMeals[index].title, + imageUrl: categoryMeals[index].imageUrl, + duration: categoryMeals[index].duration, + complexity: categoryMeals[index].complexity, + affordability: categoryMeals[index].affordability, + id: categoryMeals[index].id, + ); }, itemCount: categoryMeals.length, - ) - ); + )); } } diff --git a/lib/screens/filters_screen.dart b/lib/screens/filters_screen.dart new file mode 100644 index 0000000..ec9c351 --- /dev/null +++ b/lib/screens/filters_screen.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:meals_app/widgets/main_drawer.dart'; + +class FilterScreen extends StatelessWidget { + const FilterScreen({super.key}); + + static const routeName = "filter"; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + backgroundColor: theme.primaryColor, + title: Text("Filters"), + ), + drawer: MainDrawer(), + body: Center( + child: Text("ddd"), + ), + ); + } +} diff --git a/lib/widgets/main_drawer.dart b/lib/widgets/main_drawer.dart index 613d328..eb32520 100644 --- a/lib/widgets/main_drawer.dart +++ b/lib/widgets/main_drawer.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:meals_app/screens/filters_screen.dart'; class MainDrawer extends StatelessWidget { const MainDrawer({super.key}); @@ -7,7 +8,7 @@ class MainDrawer extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - Widget buildListTile(String title, IconData iconData) { + Widget buildListTile(String title, IconData iconData, Function tapHandler) { return ListTile( leading: Icon(iconData), // Create an Icon widget from the IconData title: Text( @@ -18,9 +19,7 @@ class MainDrawer extends StatelessWidget { fontWeight: FontWeight.bold, ), ), - onTap: () { - // Add onTap functionality here - }, + onTap: () => tapHandler(), ); } @@ -33,15 +32,24 @@ class MainDrawer extends StatelessWidget { width: double.infinity, padding: const EdgeInsets.all(20), alignment: Alignment.centerLeft, - child: Text('Cooking up', style: TextStyle( + child: Text( + 'Cooking up', + style: TextStyle( fontWeight: FontWeight.w900, fontSize: 30, color: theme.primaryColor, - ),), + ), + ), ), - const SizedBox(height: 20,), - buildListTile('Meals', Icons.restaurant), - buildListTile('Filters', Icons.settings) + const SizedBox( + height: 20, + ), + buildListTile('Meals', Icons.restaurant, () { + Navigator.of(context).pushReplacementNamed("/"); + }), + buildListTile('Filters', Icons.settings, (){ + Navigator.of(context).pushReplacementNamed(FilterScreen.routeName); + }) ], ), ); From 07b80e2c1a9c2258a232cf9cde4d2ea541efdb91 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Tue, 13 Feb 2024 10:02:05 +0100 Subject: [PATCH 07/10] Popping pages and passing data back --- lib/screens/category_meals_screen.dart | 57 +++++++++++----- lib/screens/meal_detail_screen.dart | 91 ++++++++++++++------------ lib/widgets/meal_item.dart | 61 +++++++++++------ pubspec.yaml | 4 +- 4 files changed, 133 insertions(+), 80 deletions(-) diff --git a/lib/screens/category_meals_screen.dart b/lib/screens/category_meals_screen.dart index 4bb8d41..53a3058 100644 --- a/lib/screens/category_meals_screen.dart +++ b/lib/screens/category_meals_screen.dart @@ -2,18 +2,44 @@ import 'package:flutter/material.dart'; import 'package:meals_app/dummy_data.dart'; import 'package:meals_app/widgets/meal_item.dart'; -class CategoryMealsScreen extends StatelessWidget { +import '../models/meal.dart'; + +class CategoryMealsScreen extends StatefulWidget { static const routeName = '/category-meals'; + @override + State createState() => _CategoryMealsScreenState(); +} + +class _CategoryMealsScreenState extends State { + String? categoryTitle; + List? displayMeals; + bool _loadedInitData = false; + + @override + void didChangeDependencies() { + if (!_loadedInitData) { + final routeArgs = + ModalRoute.of(context)?.settings.arguments as Map; + categoryTitle = routeArgs['title']; + final categoryId = routeArgs['id']; + displayMeals = DUMMY_MEALS.where((meal) { + return meal.categories.contains(categoryId); + }).toList(); + _loadedInitData = true; + } + + super.didChangeDependencies(); + } + + void _removeMeal(String mealId) { + setState(() { + displayMeals?.removeWhere((meal) => meal.id == mealId); + }); + } + @override Widget build(BuildContext context) { - final routeArgs = - ModalRoute.of(context)?.settings.arguments as Map; - final categoryTitle = routeArgs['title']; - final categoryId = routeArgs['id']; - final categoryMeals = DUMMY_MEALS.where((meal) { - return meal.categories.contains(categoryId); - }).toList(); final theme = Theme.of(context); return Scaffold( appBar: AppBar( @@ -23,15 +49,16 @@ class CategoryMealsScreen extends StatelessWidget { body: ListView.builder( itemBuilder: (ctx, index) { return MealItem( - title: categoryMeals[index].title, - imageUrl: categoryMeals[index].imageUrl, - duration: categoryMeals[index].duration, - complexity: categoryMeals[index].complexity, - affordability: categoryMeals[index].affordability, - id: categoryMeals[index].id, + title: displayMeals![index].title, + imageUrl: displayMeals![index].imageUrl, + duration: displayMeals![index].duration, + complexity: displayMeals![index].complexity, + affordability: displayMeals![index].affordability, + id: displayMeals![index].id, + removeItem: _removeMeal, ); }, - itemCount: categoryMeals.length, + itemCount: displayMeals?.length, )); } } diff --git a/lib/screens/meal_detail_screen.dart b/lib/screens/meal_detail_screen.dart index 65c5485..faf69f9 100644 --- a/lib/screens/meal_detail_screen.dart +++ b/lib/screens/meal_detail_screen.dart @@ -36,51 +36,58 @@ class MealDetailScreen extends StatelessWidget { final mealId = ModalRoute.of(context)?.settings.arguments as String; final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId); return Scaffold( - appBar: AppBar( - title: Text(selectedMeal.title), - backgroundColor: theme.primaryColor, - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - height: 300, - width: double.infinity, - child: Image.network( - selectedMeal.imageUrl, - fit: BoxFit.cover, - )), - buildSectionTitle(context, "Ingredients"), - buildContainer( - ListView.builder( - itemBuilder: (ctx, index) => Card( - color: theme.primaryColor, - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 5, horizontal: 10), - child: Text(selectedMeal.ingredients[index]), - ), + appBar: AppBar( + title: Text(selectedMeal.title), + backgroundColor: theme.primaryColor, + ), + body: SingleChildScrollView( + child: Column( + children: [ + Container( + height: 300, + width: double.infinity, + child: Image.network( + selectedMeal.imageUrl, + fit: BoxFit.cover, + )), + buildSectionTitle(context, "Ingredients"), + buildContainer( + ListView.builder( + itemBuilder: (ctx, index) => Card( + color: theme.primaryColor, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 5, horizontal: 10), + child: Text(selectedMeal.ingredients[index]), ), - itemCount: selectedMeal.ingredients.length, ), + itemCount: selectedMeal.ingredients.length, ), - buildSectionTitle(context, 'Steps'), - buildContainer(ListView.builder( - itemBuilder: (ctx, index) => Column( - children: [ - ListTile( - leading: CircleAvatar( - child: Text('# ${index + 1}'), - ), - title: Text(selectedMeal.steps[index]), + ), + buildSectionTitle(context, 'Steps'), + buildContainer(ListView.builder( + itemBuilder: (ctx, index) => Column( + children: [ + ListTile( + leading: CircleAvatar( + child: Text('# ${index + 1}'), ), - const Divider() - ], - ), - itemCount: selectedMeal.steps.length, - )) - ], - ), - )); + title: Text(selectedMeal.steps[index]), + ), + const Divider() + ], + ), + itemCount: selectedMeal.steps.length, + )) + ], + ), + ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.delete), + onPressed: () { + Navigator.of(context).pop(mealId); + }, + ), + ); } } diff --git a/lib/widgets/meal_item.dart b/lib/widgets/meal_item.dart index 792a66f..fe6f556 100644 --- a/lib/widgets/meal_item.dart +++ b/lib/widgets/meal_item.dart @@ -9,15 +9,16 @@ class MealItem extends StatelessWidget { final int duration; final Complexity complexity; final Affordability affordability; + final Function removeItem; - const MealItem({ - super.key, + const MealItem({super.key, required this.id, required this.title, required this.imageUrl, required this.duration, required this.complexity, - required this.affordability + required this.affordability, + required this.removeItem }); String get complexityText { @@ -31,8 +32,8 @@ class MealItem extends StatelessWidget { } } - String get affordabilityText{ - switch (affordability){ + String get affordabilityText { + switch (affordability) { case Affordability.affordable: return 'Affordable'; case Affordability.pricey: @@ -41,8 +42,14 @@ class MealItem extends StatelessWidget { return 'Expensive'; } } + void selectMeal(BuildContext context) { - Navigator.of(context).pushNamed(MealDetailScreen.routeName, arguments: id); + Navigator.of(context) + .pushNamed(MealDetailScreen.routeName, arguments: id).then((value) { + if(value != null){ + removeItem(value); + } + }); } @override @@ -96,21 +103,33 @@ class MealItem extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Row(children: [ - const Icon(Icons.schedule), - const SizedBox(width: 6,), - Text('$duration min') - ],), - Row(children: [ - const Icon(Icons.work), - const SizedBox(width: 6,), - Text(complexityText) - ],), - Row(children: [ - const Icon(Icons.attach_money), - const SizedBox(width: 6,), - Text(affordabilityText) - ],) + Row( + children: [ + const Icon(Icons.schedule), + const SizedBox( + width: 6, + ), + Text('$duration min') + ], + ), + Row( + children: [ + const Icon(Icons.work), + const SizedBox( + width: 6, + ), + Text(complexityText) + ], + ), + Row( + children: [ + const Icon(Icons.attach_money), + const SizedBox( + width: 6, + ), + Text(affordabilityText) + ], + ) ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index cfab2c1..2fd71d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,14 +78,14 @@ flutter: - family: Raleway fonts: - asset: assets/fonts/Raleway-Regular.ttf - - asset: fonts/Raleway-Bold.ttf + - asset: assets/fonts/Raleway-Bold.ttf weight: 700 - asset: assets/fonts/Raleway-Black.ttf weight: 900 - family: RobotoCondensed fonts: - asset: assets/fonts/RobotoCondensed-Regular.ttf - - asset: fonts/RobotoCondensed-Italic.ttf + - asset: assets/fonts/RobotoCondensed-Italic.ttf weight: 700 - asset: assets/fonts/RobotoCondensed-Light.ttf weight: 300 From 2d8990345a8c8a02bc76b8cc646a96e439bf6731 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Tue, 13 Feb 2024 10:13:26 +0100 Subject: [PATCH 08/10] fix merge conflict --- lib/categories_screen.dart | 46 ------------------------------ lib/screens/categories_screen.dart | 13 ++++++--- 2 files changed, 9 insertions(+), 50 deletions(-) delete mode 100644 lib/categories_screen.dart diff --git a/lib/categories_screen.dart b/lib/categories_screen.dart deleted file mode 100644 index 4eb624a..0000000 --- a/lib/categories_screen.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:meals_app/category_item.dart'; -import 'package:meals_app/dummy_data.dart'; - -class CategoriesScreen extends StatelessWidget { - const CategoriesScreen({super.key}); - - // Constants for grid configuration - static const double _maxCrossAxisExtent = 200; - static const double _childAspectRatio = 3 / 2; - static const double _crossAxisSpacing = 20; - static const double _mainAxisSpacing = 20; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Scaffold( - appBar: AppBar( - elevation: 5, - title: const Text('DeliMeal'), - backgroundColor: theme.primaryColor, - ), - body: Container( - color: theme.canvasColor, - child: GridView( - padding: const EdgeInsets.all(15), - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: _maxCrossAxisExtent, - childAspectRatio: _childAspectRatio, - crossAxisSpacing: _crossAxisSpacing, - mainAxisSpacing: _mainAxisSpacing, - ), - children: DUMMY_CATEGORIES - .map( - (catData) => CategoryItem( - title: catData.title, - color: catData.color, - id: catData.id, - ), - ) - .toList(), - ), - ), - ); - } -} diff --git a/lib/screens/categories_screen.dart b/lib/screens/categories_screen.dart index e26a6e9..06307ee 100644 --- a/lib/screens/categories_screen.dart +++ b/lib/screens/categories_screen.dart @@ -5,6 +5,11 @@ import 'package:meals_app/dummy_data.dart'; class CategoriesScreen extends StatelessWidget { const CategoriesScreen({super.key}); + static const double _maxCrossAxisExtent = 200; + static const double _childAspectRatio = 3 / 2; + static const double _crossAxisSpacing = 20; + static const double _mainAxisSpacing = 20; + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -13,10 +18,10 @@ class CategoriesScreen extends StatelessWidget { child: GridView( padding: const EdgeInsets.all(15), gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - childAspectRatio: 3 / 2, - crossAxisSpacing: 20, - mainAxisSpacing: 20), + maxCrossAxisExtent: _maxCrossAxisExtent, + childAspectRatio: _childAspectRatio, + crossAxisSpacing: _crossAxisSpacing, + mainAxisSpacing: _mainAxisSpacing), children: DUMMY_CATEGORIES .map((catData) => CategoryItem( title: catData.title, From 9c224ded75e08a8d1e428d8f893b734c93d84f86 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Tue, 13 Feb 2024 11:09:14 +0100 Subject: [PATCH 09/10] set filters and logic --- lib/main.dart | 43 +++++++++++- lib/screens/category_meals_screen.dart | 11 ++-- lib/screens/filters_screen.dart | 91 ++++++++++++++++++++++++-- lib/widgets/category_item.dart | 16 ++--- 4 files changed, 137 insertions(+), 24 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b563b07..58f1cc3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,15 +1,52 @@ import 'package:flutter/material.dart'; +import 'package:meals_app/dummy_data.dart'; import 'package:meals_app/screens/categories_screen.dart'; import 'package:meals_app/screens/category_meals_screen.dart'; import 'package:meals_app/screens/filters_screen.dart'; import 'package:meals_app/screens/meal_detail_screen.dart'; import 'package:meals_app/screens/tabs_screen.dart'; +import 'models/meal.dart'; + void main() => runApp(MyApp()); -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + Map _filters = { + 'gluten': false, + 'lactose': false, + 'vegan': false, + 'vegetarian': false, + }; + List _availableMeals = DUMMY_MEALS; + + void _setFilters(Map filterData){ + setState(() { + _filters = filterData; + + _availableMeals = DUMMY_MEALS.where((meal) { + if(_filters['gluten']! && !meal.isGlutenFree){ + return false; + } + if(_filters['lactose']! && !meal.isLactoseFree){ + return false; + } + if(_filters['vegan']! && !meal.isVegan){ + return false; + } + if(_filters['vegetarian']! && !meal.isVegetarian){ + return false; + } + return true; + }).toList(); + }); + } @override Widget build(BuildContext context) { return MaterialApp( @@ -28,9 +65,9 @@ class MyApp extends StatelessWidget { ))), routes: { '/': (ctx) => const TabScreen(), - CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(), + CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(_availableMeals), MealDetailScreen.routeName: (ctx) => MealDetailScreen(), - FilterScreen.routeName: (ctx) => FilterScreen() + FilterScreen.routeName: (ctx) => FilterScreen( saveFilters: _setFilters,) }, onUnknownRoute: (settings) { return MaterialPageRoute(builder: (ctx) => CategoriesScreen()); diff --git a/lib/screens/category_meals_screen.dart b/lib/screens/category_meals_screen.dart index 53a3058..253439a 100644 --- a/lib/screens/category_meals_screen.dart +++ b/lib/screens/category_meals_screen.dart @@ -7,6 +7,10 @@ import '../models/meal.dart'; class CategoryMealsScreen extends StatefulWidget { static const routeName = '/category-meals'; + final List availableMeals; + + CategoryMealsScreen(this.availableMeals, {super.key}); + @override State createState() => _CategoryMealsScreenState(); } @@ -19,19 +23,18 @@ class _CategoryMealsScreenState extends State { @override void didChangeDependencies() { if (!_loadedInitData) { - final routeArgs = - ModalRoute.of(context)?.settings.arguments as Map; + final routeArgs = ModalRoute.of(context)?.settings.arguments as Map; categoryTitle = routeArgs['title']; final categoryId = routeArgs['id']; - displayMeals = DUMMY_MEALS.where((meal) { + displayMeals = widget.availableMeals.where((meal) { return meal.categories.contains(categoryId); }).toList(); _loadedInitData = true; } - super.didChangeDependencies(); } + void _removeMeal(String mealId) { setState(() { displayMeals?.removeWhere((meal) => meal.id == mealId); diff --git a/lib/screens/filters_screen.dart b/lib/screens/filters_screen.dart index ec9c351..a26d0b3 100644 --- a/lib/screens/filters_screen.dart +++ b/lib/screens/filters_screen.dart @@ -1,10 +1,31 @@ import 'package:flutter/material.dart'; import 'package:meals_app/widgets/main_drawer.dart'; -class FilterScreen extends StatelessWidget { - const FilterScreen({super.key}); - +class FilterScreen extends StatefulWidget { static const routeName = "filter"; + final Function saveFilters; + + const FilterScreen({super.key, required this.saveFilters}); + + @override + State createState() => _FilterScreenState(); +} + +class _FilterScreenState extends State { + bool _glutenFree = false; + bool _vegetarian = false; + bool _vegan = false; + bool _lactoseFree = false; + + Widget _buildSwitchListTitle(String title, String description, + bool currentValue, Function(bool) updateValue) { + return SwitchListTile( + value: currentValue, + onChanged: updateValue, + title: Text(title), + subtitle: Text(description), + ); + } @override Widget build(BuildContext context) { @@ -13,10 +34,68 @@ class FilterScreen extends StatelessWidget { appBar: AppBar( backgroundColor: theme.primaryColor, title: Text("Filters"), + actions: [ + IconButton(onPressed: () { + final selectedFilters = { + 'gluten': _glutenFree, + 'lactose': _lactoseFree, + 'vegan': _vegan, + 'vegetarian': _vegetarian, + }; + widget.saveFilters(selectedFilters); + }, icon: const Icon(Icons.save)) + ], ), - drawer: MainDrawer(), - body: Center( - child: Text("ddd"), + drawer: MainDrawer(), + body: Container( + color: theme.canvasColor, + child: Column( + children: [ + Container( + padding: EdgeInsets.all(20), + child: Text( + 'Adjust your meal selection.', + style: theme.textTheme.titleLarge, + ), + ), + Expanded( + child: ListView( + children: [ + _buildSwitchListTitle( + 'Gluten-free', + 'Only include gluten-free meals', _glutenFree, ( + newValue) { + setState(() { + _glutenFree = newValue; + }); + }), + _buildSwitchListTitle( + 'Lactose-free', + 'Only include lactose-free meals', _lactoseFree, ( + newValue) { + setState(() { + _lactoseFree = newValue; + }); + }), + _buildSwitchListTitle( + 'Vegetarian', + 'Only include vegetarian meals', _vegetarian, ( + newValue) { + setState(() { + _vegetarian = newValue; + }); + }), + _buildSwitchListTitle( + 'Vegan', + 'Only include vegan meals', _vegan, (newValue) { + setState(() { + _vegan = newValue; + }); + }), + ], + )) + ], + ), ), ); } diff --git a/lib/widgets/category_item.dart b/lib/widgets/category_item.dart index 8f0ff6a..d5d9832 100644 --- a/lib/widgets/category_item.dart +++ b/lib/widgets/category_item.dart @@ -1,16 +1,6 @@ import 'package:flutter/material.dart'; import 'package:meals_app/screens/category_meals_screen.dart'; -class CategoryArguments { - final String id; - final String title; - - CategoryArguments({ - required this.id, - required this.title - }); -} - class CategoryItem extends StatelessWidget { final String id; final String title; @@ -26,10 +16,14 @@ class CategoryItem extends StatelessWidget { void selectCategory(BuildContext ctx) { Navigator.of(ctx).pushNamed( CategoryMealsScreen.routeName, - arguments: CategoryArguments(id: id, title: title), + arguments: { + 'id': id, + 'title': title, + }, ); } + @override Widget build(BuildContext context) { final theme = Theme.of(context); From 2ded8349b5c7c68b8b880066a315a378bc7b10c2 Mon Sep 17 00:00:00 2001 From: hendrikvanderkaaden Date: Tue, 13 Feb 2024 11:13:50 +0100 Subject: [PATCH 10/10] filters are saved --- lib/main.dart | 2 +- lib/screens/filters_screen.dart | 12 +++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 58f1cc3..46c632f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -67,7 +67,7 @@ class _MyAppState extends State { '/': (ctx) => const TabScreen(), CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(_availableMeals), MealDetailScreen.routeName: (ctx) => MealDetailScreen(), - FilterScreen.routeName: (ctx) => FilterScreen( saveFilters: _setFilters,) + FilterScreen.routeName: (ctx) => FilterScreen(saveFilters: _setFilters, currentFilters: _filters,) }, onUnknownRoute: (settings) { return MaterialPageRoute(builder: (ctx) => CategoriesScreen()); diff --git a/lib/screens/filters_screen.dart b/lib/screens/filters_screen.dart index a26d0b3..b5a4920 100644 --- a/lib/screens/filters_screen.dart +++ b/lib/screens/filters_screen.dart @@ -4,8 +4,9 @@ import 'package:meals_app/widgets/main_drawer.dart'; class FilterScreen extends StatefulWidget { static const routeName = "filter"; final Function saveFilters; + final Map currentFilters; - const FilterScreen({super.key, required this.saveFilters}); + const FilterScreen({super.key, required this.saveFilters, required this.currentFilters}); @override State createState() => _FilterScreenState(); @@ -17,6 +18,15 @@ class _FilterScreenState extends State { bool _vegan = false; bool _lactoseFree = false; + @override + void initState() { + _glutenFree = widget.currentFilters['gluten']!; + _lactoseFree = widget.currentFilters['lactose']!; + _vegetarian = widget.currentFilters['vegetarian']!; + _vegan = widget.currentFilters['vegan']!; + super.initState(); + } + Widget _buildSwitchListTitle(String title, String description, bool currentValue, Function(bool) updateValue) { return SwitchListTile(