Compare commits

...

2 Commits

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:meals/screens/CategoriesScreen.dart';
import 'package:meals/screens/tabs.dart';
@ -13,7 +14,7 @@ final theme = ThemeData(
);
void main() {
runApp(const App());
runApp(const ProviderScope(child: App()));
}
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 {
final List<Meal> availableMeals;
final void Function(Meal meal) onToggleFavorite;
const CategoriesScreen({super.key,
const CategoriesScreen({
super.key,
required this.availableMeals,
required this.onToggleFavorite});
});
void _selectCategory(BuildContext context, Category category) {
final meals = availableMeals
@ -21,11 +21,9 @@ class CategoriesScreen extends StatelessWidget {
Navigator.push(
context,
MaterialPageRoute(
builder: (ctx) =>
MealsScreen(
builder: (ctx) => MealsScreen(
title: category.title,
meals: meals,
onToggleFavorite: onToggleFavorite,
)));
}

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

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

@ -1,24 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:meals/providers/favorites_provider.dart';
import '../models/Meal.dart';
class MealDetailScreen extends StatelessWidget {
class MealDetailScreen extends ConsumerWidget {
final Meal meal;
final void Function(Meal meal) onToggleFavorite;
const MealDetailScreen(
{Key? key, required this.meal, required this.onToggleFavorite})
: super(key: key);
const MealDetailScreen({Key? key, required this.meal}) : super(key: key);
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final favoriteMeals = ref.watch(favoriteMealsProvider);
final isIntoFavorite = favoriteMeals.contains(meal);
return Scaffold(
appBar: AppBar(
title: Text(meal.title),
actions: [
IconButton(
onPressed: () => onToggleFavorite(meal),
icon: const Icon(Icons.star))
onPressed: () {
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(

@ -1,36 +1,22 @@
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/MealsScreen.dart';
import 'package:meals/screens/filters.dart';
import 'package:meals/widgets/main_drawer.dart';
import '../models/Meal.dart';
import '../providers/filters_provider.dart';
const kDefaultFilters = {
Filter.glutenFree: false,
Filter.lactoseFree: false,
Filter.veggie: false,
Filter.vegan: false,
};
class TabsScreen extends StatefulWidget {
class TabsScreen extends ConsumerStatefulWidget {
const TabsScreen({Key? key}) : super(key: key);
@override
State<TabsScreen> createState() => _TabsScreenState();
ConsumerState<TabsScreen> createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
class _TabsScreenState extends ConsumerState<TabsScreen> {
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) {
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 {
Navigator.of(context).pop();
switch (identifier) {
case 'filters':
final results = await Navigator.of(context)
.push<Map<Filter, bool>>(MaterialPageRoute(
builder: (ctx) => FiltersScreen(
currentFilters: _selectedFilters,
)));
setState(() {
_selectedFilters = results ?? kDefaultFilters;
});
await Navigator.of(context).push<Map<Filter, bool>>(
MaterialPageRoute(builder: (ctx) => const FiltersScreen()));
break;
}
}
@ -73,35 +39,19 @@ class _TabsScreenState extends State<TabsScreen> {
Widget build(BuildContext context) {
Widget activePage;
String? activePageTitle;
final availableMeals = dummyMeals.where((meal) {
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();
final availableMeals = ref.watch(filteredMealsProvider);
switch (_selectedPageIndex) {
case 1:
activePageTitle = 'Favorites';
activePage = MealsScreen(
meals: _favoriteMeals,
onToggleFavorite: _toggleFavoriteMeal,
meals: ref.watch(favoriteMealsProvider),
);
break;
default:
activePageTitle = 'Pick your category';
activePage = CategoriesScreen(
availableMeals: availableMeals,
onToggleFavorite: _toggleFavoriteMeal,
);
}

@ -94,6 +94,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct dev"
description: flutter
@ -243,6 +251,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.4"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
sky_engine:
dependency: transitive
description: flutter
@ -264,6 +280,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:

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

Loading…
Cancel
Save