Compare commits

...

2 Commits

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:meals/screens/CategoriesScreen.dart'; import 'package:meals/screens/CategoriesScreen.dart';
import 'package:meals/screens/tabs.dart'; import 'package:meals/screens/tabs.dart';
@ -13,7 +14,7 @@ final theme = ThemeData(
); );
void main() { void main() {
runApp(const App()); runApp(const ProviderScope(child: App()));
} }
class App extends StatelessWidget { class App extends StatelessWidget {

@ -0,0 +1,20 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/models/Meal.dart';
class FavoriteMealsNotifier extends StateNotifier<List<Meal>> {
FavoriteMealsNotifier(List<Meal>? initialList) : super(initialList ?? []);
bool toggleMealFavoriteStatus(Meal meal) {
if (state.contains(meal)) {
state = state.where((Meal m) => m.id != meal.id).toList();
return false;
}
state = [...state, meal];
return true;
}
}
final favoriteMealsProvider =
StateNotifierProvider<FavoriteMealsNotifier, List<Meal>>(
(ref) => FavoriteMealsNotifier([]));

@ -0,0 +1,55 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/providers/meals_provider.dart';
import 'package:meals/screens/filters.dart';
enum Filter {
glutenFree,
lactoseFree,
veggie,
vegan,
}
class FiltersNotifier extends StateNotifier<Map<Filter, bool>> {
FiltersNotifier()
: super({
Filter.glutenFree: false,
Filter.lactoseFree: false,
Filter.veggie: false,
Filter.vegan: false,
});
void setFilter(Filter filter, bool isActive) {
state = {
...state,
filter: isActive,
};
}
void setFilters(Map<Filter, bool> newFilters) {
state = newFilters;
}
}
final filtersProvider =
StateNotifierProvider<FiltersNotifier, Map<Filter, bool>>(
(ref) => FiltersNotifier());
final filteredMealsProvider = Provider((ref) {
final meals = ref.watch(mealsProvider);
final activeFilters = ref.watch(filtersProvider);
return meals.where((meal) {
if (activeFilters[Filter.glutenFree]! && !meal.isGlutenFree) {
return false;
}
if (activeFilters[Filter.lactoseFree]! && !meal.isLactoseFree) {
return false;
}
if (activeFilters[Filter.veggie]! && !meal.isVegetarian) {
return false;
}
if (activeFilters[Filter.vegan]! && !meal.isVegan) {
return false;
}
return true;
}).toList();
});

@ -0,0 +1,4 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/data/dummy_categories.dart';
final mealsProvider = Provider((ref) => dummyMeals);

@ -7,11 +7,11 @@ import 'package:meals/widgets/CategoryGridItem.dart';
class CategoriesScreen extends StatelessWidget { class CategoriesScreen extends StatelessWidget {
final List<Meal> availableMeals; final List<Meal> availableMeals;
final void Function(Meal meal) onToggleFavorite;
const CategoriesScreen({super.key, const CategoriesScreen({
super.key,
required this.availableMeals, required this.availableMeals,
required this.onToggleFavorite}); });
void _selectCategory(BuildContext context, Category category) { void _selectCategory(BuildContext context, Category category) {
final meals = availableMeals final meals = availableMeals
@ -21,11 +21,9 @@ class CategoriesScreen extends StatelessWidget {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (ctx) => builder: (ctx) => MealsScreen(
MealsScreen(
title: category.title, title: category.title,
meals: meals, meals: meals,
onToggleFavorite: onToggleFavorite,
))); )));
} }

@ -6,13 +6,12 @@ import 'package:meals/widgets/MealItem.dart';
class MealsScreen extends StatelessWidget { class MealsScreen extends StatelessWidget {
final String? title; final String? title;
final List<Meal> meals; final List<Meal> meals;
final void Function(Meal meal) onToggleFavorite;
const MealsScreen( const MealsScreen({
{super.key, super.key,
this.title, this.title,
required this.meals, required this.meals,
required this.onToggleFavorite}); });
void _selectMeal(BuildContext buildContext, Meal meal) { void _selectMeal(BuildContext buildContext, Meal meal) {
Navigator.push( Navigator.push(
@ -20,7 +19,6 @@ class MealsScreen extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (ctx) => MealDetailScreen( builder: (ctx) => MealDetailScreen(
meal: meal, meal: meal,
onToggleFavorite: onToggleFavorite,
), ),
)); ));
} }

@ -1,65 +1,26 @@
import 'package:flutter/material.dart'; import 'dart:developer';
import 'package:meals/screens/tabs.dart';
import 'package:meals/widgets/main_drawer.dart';
enum Filter {
glutenFree,
lactoseFree,
veggie,
vegan,
}
class FiltersScreen extends StatefulWidget { import 'package:flutter/material.dart';
final Map<Filter, bool> currentFilters; import 'package:flutter_riverpod/flutter_riverpod.dart';
const FiltersScreen({Key? key, required this.currentFilters})
: super(key: key);
@override
State<FiltersScreen> createState() => _FiltersScreenState();
}
class _FiltersScreenState extends State<FiltersScreen> { import '../providers/filters_provider.dart';
bool _glutenFreeFilterSet = false;
bool _lactoseFreeFilterSet = false;
bool _veggieFilterSet = false;
bool _veganFilterSet = false;
@override class FiltersScreen extends ConsumerWidget {
void initState() { const FiltersScreen({Key? key}) : super(key: key);
super.initState();
setState(() {
_glutenFreeFilterSet = widget.currentFilters[Filter.glutenFree]!;
_lactoseFreeFilterSet = widget.currentFilters[Filter.lactoseFree]!;
_veggieFilterSet = widget.currentFilters[Filter.veggie]!;
_veganFilterSet = widget.currentFilters[Filter.vegan]!;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Your filters'), title: const Text('Your filters'),
), ),
body: WillPopScope( body: Column(
onWillPop: () async {
Navigator.of(context).pop({
Filter.glutenFree: _glutenFreeFilterSet,
Filter.lactoseFree: _lactoseFreeFilterSet,
Filter.veggie: _veggieFilterSet,
Filter.vegan: _veganFilterSet,
});
return false;
},
child: Column(
children: [ children: [
SwitchListTile( SwitchListTile(
value: _glutenFreeFilterSet, value: ref.watch(filtersProvider)[Filter.glutenFree]!,
onChanged: (checked) => setState(() { onChanged: (checked) => ref
_glutenFreeFilterSet = checked; .read(filtersProvider.notifier)
}), .setFilter(Filter.glutenFree, checked),
title: Text( title: Text(
'Gluten free', 'Gluten free',
style: Theme.of(context).textTheme.titleLarge!.copyWith( style: Theme.of(context).textTheme.titleLarge!.copyWith(
@ -74,10 +35,10 @@ class _FiltersScreenState extends State<FiltersScreen> {
contentPadding: const EdgeInsets.only(left: 34, right: 22), contentPadding: const EdgeInsets.only(left: 34, right: 22),
), ),
SwitchListTile( SwitchListTile(
value: _lactoseFreeFilterSet, value: ref.watch(filtersProvider)[Filter.lactoseFree]!,
onChanged: (checked) => setState(() { onChanged: (checked) => ref
_lactoseFreeFilterSet = checked; .read(filtersProvider.notifier)
}), .setFilter(Filter.lactoseFree, checked),
title: Text( title: Text(
'Lactose free', 'Lactose free',
style: Theme.of(context).textTheme.titleLarge!.copyWith( style: Theme.of(context).textTheme.titleLarge!.copyWith(
@ -92,10 +53,10 @@ class _FiltersScreenState extends State<FiltersScreen> {
contentPadding: const EdgeInsets.only(left: 34, right: 22), contentPadding: const EdgeInsets.only(left: 34, right: 22),
), ),
SwitchListTile( SwitchListTile(
value: _veggieFilterSet, value: ref.watch(filtersProvider)[Filter.veggie]!,
onChanged: (checked) => setState(() { onChanged: (checked) => ref
_veggieFilterSet = checked; .read(filtersProvider.notifier)
}), .setFilter(Filter.veggie, checked),
title: Text( title: Text(
'Veggie', 'Veggie',
style: Theme.of(context).textTheme.titleLarge!.copyWith( style: Theme.of(context).textTheme.titleLarge!.copyWith(
@ -110,10 +71,10 @@ class _FiltersScreenState extends State<FiltersScreen> {
contentPadding: const EdgeInsets.only(left: 34, right: 22), contentPadding: const EdgeInsets.only(left: 34, right: 22),
), ),
SwitchListTile( SwitchListTile(
value: _veganFilterSet, value: ref.watch(filtersProvider)[Filter.vegan]!,
onChanged: (checked) => setState(() { onChanged: (checked) => ref
_veganFilterSet = checked; .read(filtersProvider.notifier)
}), .setFilter(Filter.vegan, checked),
title: Text( title: Text(
'Vegan', 'Vegan',
style: Theme.of(context).textTheme.titleLarge!.copyWith( style: Theme.of(context).textTheme.titleLarge!.copyWith(
@ -128,7 +89,6 @@ class _FiltersScreenState extends State<FiltersScreen> {
contentPadding: const EdgeInsets.only(left: 34, right: 22), contentPadding: const EdgeInsets.only(left: 34, right: 22),
) )
], ],
),
)); ));
} }
} }

@ -1,24 +1,36 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/providers/favorites_provider.dart';
import '../models/Meal.dart'; import '../models/Meal.dart';
class MealDetailScreen extends StatelessWidget { class MealDetailScreen extends ConsumerWidget {
final Meal meal; final Meal meal;
final void Function(Meal meal) onToggleFavorite;
const MealDetailScreen( const MealDetailScreen({Key? key, required this.meal}) : super(key: key);
{Key? key, required this.meal, required this.onToggleFavorite})
: super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context, WidgetRef ref) {
final favoriteMeals = ref.watch(favoriteMealsProvider);
final isIntoFavorite = favoriteMeals.contains(meal);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(meal.title), title: Text(meal.title),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => onToggleFavorite(meal), onPressed: () {
icon: const Icon(Icons.star)) final wasAdded = ref
.read(favoriteMealsProvider.notifier)
.toggleMealFavoriteStatus(meal);
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(wasAdded
? 'Meal has been added to your favorite.'
: 'Meal is no longer as a favorite.')));
},
icon: Icon(isIntoFavorite ? Icons.star : Icons.star_border))
], ],
), ),
body: SingleChildScrollView( body: SingleChildScrollView(

@ -1,36 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meals/data/dummy_categories.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/providers/favorites_provider.dart';
import 'package:meals/screens/CategoriesScreen.dart'; import 'package:meals/screens/CategoriesScreen.dart';
import 'package:meals/screens/MealsScreen.dart'; import 'package:meals/screens/MealsScreen.dart';
import 'package:meals/screens/filters.dart'; import 'package:meals/screens/filters.dart';
import 'package:meals/widgets/main_drawer.dart'; import 'package:meals/widgets/main_drawer.dart';
import '../models/Meal.dart'; import '../providers/filters_provider.dart';
const kDefaultFilters = { class TabsScreen extends ConsumerStatefulWidget {
Filter.glutenFree: false,
Filter.lactoseFree: false,
Filter.veggie: false,
Filter.vegan: false,
};
class TabsScreen extends StatefulWidget {
const TabsScreen({Key? key}) : super(key: key); const TabsScreen({Key? key}) : super(key: key);
@override @override
State<TabsScreen> createState() => _TabsScreenState(); ConsumerState<TabsScreen> createState() => _TabsScreenState();
} }
class _TabsScreenState extends State<TabsScreen> { class _TabsScreenState extends ConsumerState<TabsScreen> {
int _selectedPageIndex = 0; int _selectedPageIndex = 0;
final List<Meal> _favoriteMeals = [];
Map<Filter, bool> _selectedFilters = kDefaultFilters;
void _showInfoMessage(String message) {
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(message)));
}
void _selectPage(int index) { void _selectPage(int index) {
setState(() { setState(() {
@ -38,33 +24,13 @@ class _TabsScreenState extends State<TabsScreen> {
}); });
} }
void _toggleFavoriteMeal(Meal meal) {
if (_favoriteMeals.contains(meal)) {
setState(() {
_favoriteMeals.remove(meal);
});
_showInfoMessage('Meal is no longer a favorite');
} else {
setState(() {
_favoriteMeals.add(meal);
_showInfoMessage('Meal has been added to your favorites');
});
}
}
void _setScreen(String identifier) async { void _setScreen(String identifier) async {
Navigator.of(context).pop(); Navigator.of(context).pop();
switch (identifier) { switch (identifier) {
case 'filters': case 'filters':
final results = await Navigator.of(context) await Navigator.of(context).push<Map<Filter, bool>>(
.push<Map<Filter, bool>>(MaterialPageRoute( MaterialPageRoute(builder: (ctx) => const FiltersScreen()));
builder: (ctx) => FiltersScreen(
currentFilters: _selectedFilters,
)));
setState(() {
_selectedFilters = results ?? kDefaultFilters;
});
break; break;
} }
} }
@ -73,35 +39,19 @@ class _TabsScreenState extends State<TabsScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget activePage; Widget activePage;
String? activePageTitle; String? activePageTitle;
final availableMeals = dummyMeals.where((meal) { final availableMeals = ref.watch(filteredMealsProvider);
if (_selectedFilters[Filter.glutenFree]! && !meal.isGlutenFree) {
return false;
}
if (_selectedFilters[Filter.lactoseFree]! && !meal.isLactoseFree) {
return false;
}
if (_selectedFilters[Filter.veggie]! && !meal.isVegetarian) {
return false;
}
if (_selectedFilters[Filter.vegan]! && !meal.isVegan) {
return false;
}
return true;
}).toList();
switch (_selectedPageIndex) { switch (_selectedPageIndex) {
case 1: case 1:
activePageTitle = 'Favorites'; activePageTitle = 'Favorites';
activePage = MealsScreen( activePage = MealsScreen(
meals: _favoriteMeals, meals: ref.watch(favoriteMealsProvider),
onToggleFavorite: _toggleFavoriteMeal,
); );
break; break;
default: default:
activePageTitle = 'Pick your category'; activePageTitle = 'Pick your category';
activePage = CategoriesScreen( activePage = CategoriesScreen(
availableMeals: availableMeals, availableMeals: availableMeals,
onToggleFavorite: _toggleFavoriteMeal,
); );
} }

@ -94,6 +94,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4
url: "https://pub.dev"
source: hosted
version: "2.3.6"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -243,6 +251,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.4" version: "4.2.4"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -264,6 +280,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.0"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289"
url: "https://pub.dev"
source: hosted
version: "0.7.2+1"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:

@ -37,6 +37,7 @@ dependencies:
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
google_fonts: ^4.0.3 google_fonts: ^4.0.3
transparent_image: ^2.0.1 transparent_image: ^2.0.1
flutter_riverpod: ^2.3.6
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save