Front-End/Flutter_Project_03_ToDo APP

8.Udemy 강의를 통한 Meals Project

sd4beatles 2025. 1. 27. 02:36

1. Adding More Filter Options

우리가 만든 filter에 option을 더 추가할 예정이다. 현재까지는 'Glutten-free' 였다면, 추가적으로 "Lactos-Free"를 더 추가해서 아래의 meal filter option을 풍요롭게 해준다.

import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';



class FilterScreen extends StatefulWidget{
  const FilterScreen({super.key});

  @override
  State<StatefulWidget>createState(){
    return _FilterScreen();
  }


}


class _FilterScreen extends State<FilterScreen>{
  var _glutenFreeFilterSet=false;
  var _lactoseFreeFilterSet=false;
  var _vegeterianFilterSet=false;
  var _veganFilterSet=false;




  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //Scaffold-appBar
      appBar:AppBar(
        title:const Text("Your filter"),
      )
      ,//Scaffold-body
      body:Column(
        children: [
          //value:boolean, onChanged:activation funciton if true
          //title: message appears
          //activeColor: choose the color if switch is activated
          //SwitchListTile(01)
          SwitchListTile(
            value:_glutenFreeFilterSet , 
            onChanged: (bool isChecked){
              setState((){
                _glutenFreeFilterSet=isChecked;
              });
            },
            //1 ) SwitchListTile-Glutten-free
            title:Text(
              'Glutten-free',
              style:Theme.of(context).textTheme.titleLarge!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,)),
            //SwitchListTile-subtitle
            subtitle:Text(
              'Only include glute-free meals.',
              style:Theme.of(context).textTheme.labelMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,
              )
            ),
            //active color-color if switch is activated
            activeColor: Theme.of(context).colorScheme.tertiary,
            contentPadding: const EdgeInsets.only(left:34,right:22),    

            ),

            //2) SwitchListTitle-Lactose Free
          SwitchListTile(
            value:_lactoseFreeFilterSet,
            onChanged:(bool isChecked){
              setState((){
                _lactoseFreeFilterSet=isChecked;
              });
            },
            title:Text(
              'Lactose-Free',
              style:Theme.of(context).textTheme.titleLarge!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,)
            ),
            subtitle:Text(
              "Only include lactose-free meals",
              style:Theme.of(context).textTheme.labelMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,
              )
            ),
            activeColor:Theme.of(context).colorScheme.tertiary,
            contentPadding: const EdgeInsets.only(left:34,right:22),
          ),

          //3) SwitchListTitle-vegeterian
         SwitchListTile(
            value:_vegeterianFilterSet,
            onChanged:(bool isChecked){
              setState((){
                _vegeterianFilterSet=isChecked;
              });
            },
            title:Text(
              'Vegetrian',
              style:Theme.of(context).textTheme.titleLarge!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,)
            ),
            subtitle:Text(
              "Only include meals for Vegeterian",
              style:Theme.of(context).textTheme.labelMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,
              )
            ),
            activeColor:Theme.of(context).colorScheme.tertiary,
            contentPadding: const EdgeInsets.only(left:34,right:22),
          ),



          //4) SwitchListTitle-_veganFilterSet
           SwitchListTile(
            value:_veganFilterSet,
            onChanged:(bool isChecked){
              setState((){
                _veganFilterSet=isChecked;
              });
            },
            title:Text(
              'Vegean',
              style:Theme.of(context).textTheme.titleLarge!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,)
            ),
            subtitle:Text(
              "Only include meals for Vegan",
              style:Theme.of(context).textTheme.labelMedium!.copyWith(
                color:Theme.of(context).colorScheme.onSurface,
              )
            ),
            activeColor:Theme.of(context).colorScheme.tertiary,
            contentPadding: const EdgeInsets.only(left:34,right:22),
          ),





        ],
      )
    );

  }

}

2. Displaying Chosen Meals Based on Filter Selections

2.1 PopScope

2.2 Project (Part 01)

Filter부분에서 check된 selections을 토대로, 다시 tap화면으로 돌아갔을 때, 그 options들을 토대로 tap화면에는 선택된 음식들만 보여주게 끔 하면 된다. filters.dart로 다시 돌아가서, 아래의 Scaffold에 다가 refractor를 이용하여, wrap with widget을 사용한다. 그 후에, widget을 PopScope으로 작성하도록 하자. 그리고, onPopInvokedWithResult option에서, pop을 통해서 보내줄 데이터를 결정하다록 하자.

pop에서 보내줄 데이터는 key-value 형태의 map 자료구조이면, 우리는 unique한 key가 필요하므로, 일단 파일 서두에 enum(열거형 자료)를 통해서 unique key를 선정하도록 하자.

enum filter{
  glutenFree,
  lactosFree,
  vegeterian,
  vegan,
}

😒추가보충 내용 (강의내용이 너무 어렵고,교사의 내용전달이 부실함)

  • 일반 코드의 흐름은 Navigator.of(context).pop([...result])는 이전에서 이야기 했던 것 처럼, 현재의 route를 제거(즉,현재화면을 닫고), 선택적으로 결과(result)를 이전 화면으로 반환할 수가 았다. 즉, pop()안에 있는 데이터가 이전의 화면으로 전달되는 값이다.
  • onPopInvokedWithResult 는 뒤로 가기 동장이 호출되었을 때, 실행되는 것이다.
    • didPop이 true라면, 뒤로 가기 동작이 이미 처리된 상태이므로, 추가 작업 없이 종료됨
    • didPop이 false라면, 사용자가 뒤로 가기 동작을 차단했으며, 우리는 map을 이전화면으로 데이터반환할 예정임
```flutter  
PopScope(  
canPop: false,  
onPopInvokedWithResult: (bool didPop, dynamic result) {  
if(didPop) return;  
Navigator.of(context).pop({  
Filter.glutenFree: \_glutenFreeFilterSet,  
Filter.lactoseFree: \_lactoseFreeFilterSet,  
Filter.vegetarian: \_vegetarianFilterSet,  
Filter.vegan: \_veganFilterSet,  
});  
},  
child: Column(...) // same code as shown in the next lecture  
),

The complete code is as follows;

import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';

enum Filter{
  glutenFree,
  lactoseFree,
  vegetarian,
  vegan
}

class FilterScreen extends StatefulWidget{
  const FilterScreen({super.key});

  @override
  State<StatefulWidget>createState(){
    return _FilterScreen();
  }


}


class _FilterScreen extends State<FilterScreen>{
  var _glutenFreeFilterSet=false;
  var _lactoseFreeFilterSet=false;
  var _vegetarianFilterSet=false;
  var _veganFilterSet=false;





  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //Scaffold-appBar
      appBar:AppBar(
        title:const Text("Your filter"),
      )
      ,//Scaffold-body

      body:PopScope(
        canPop: false,  
        onPopInvokedWithResult: (bool didPop, dynamic result) {  
          if(didPop) return;  
          Navigator.of(context).pop({  
            Filter.glutenFree: _glutenFreeFilterSet,  
            Filter.lactoseFree:_lactoseFreeFilterSet,  
            Filter.vegetarian:_vegetarianFilterSet,  
            Filter.vegan: _veganFilterSet, });  
            },  
        child: Column(
          children: [
            //value:boolean, onChanged:activation funciton if true
            //title: message appears
            //activeColor: choose the color if switch is activated
            //SwitchListTile(01)
            SwitchListTile(
              value:_glutenFreeFilterSet , 
              onChanged: (bool isChecked){
                setState((){
                  _glutenFreeFilterSet=isChecked;
                });
              },
              //1 ) SwitchListTile-Glutten-free
              title:Text(
                'Glutten-free',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)),
              //SwitchListTile-subtitle
              subtitle:Text(
                'Only include glute-free meals.',
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              //active color-color if switch is activated
              activeColor: Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),    

              ),

              //2) SwitchListTitle-Lactose Free
            SwitchListTile(
              value:_lactoseFreeFilterSet,
              onChanged:(bool isChecked){
                setState((){
                  _lactoseFreeFilterSet=isChecked;
                });
              },
              title:Text(
                'Lactose-Free',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)
              ),
              subtitle:Text(
                "Only include lactose-free meals",
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              activeColor:Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),
            ),

            //3) SwitchListTitle-vegeterian
           SwitchListTile(
              value:_vegetarianFilterSet,
              onChanged:(bool isChecked){
                setState((){
                  _vegetarianFilterSet=isChecked;
                });
              },
              title:Text(
                'Vegetrian',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)
              ),
              subtitle:Text(
                "Only include meals for Vegeterian",
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              activeColor:Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),
            ),



            //4) SwitchListTitle-_veganFilterSet
             SwitchListTile(
              value:_veganFilterSet,
              onChanged:(bool isChecked){
                setState((){
                  _veganFilterSet=isChecked;
                });
              },
              title:Text(
                'Vegean',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)
              ),
              subtitle:Text(
                "Only include meals for Vegan",
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              activeColor:Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),
            ),


          ],
        ),
      )
    );

  }

}

그리고 난 다음에 다시 ./screens/tabs.dart로 돌아간다. 그리고는 아래와 같이 void _setScreen 함수에 아래와 같이 코드를 수정해주는데,이는 아래와 같다. ( 반환되는 값은 Futre이므로 꼭 async awit문을 쓸 것!!)

 void _setScreen(String identifier)async  {
    Navigator.of(context).pop();
    if(identifier=='filters'){
      final result=await Navigator.of(context).push<Map<Filter,bool>>(
          MaterialPageRoute(builder: (ctx)=>const FilterScreen(),
          )
      );

2.3 Project (Part 02) Applying Filters

step 01)

다시 tabs.dart로 돌아가서, void _setScreen 함수를 찾아보도록 하자. 일단, tabs.dart상단에 우리가 이전 Map에서 지정했던, 모든 key,value를 담은 Map 자료구조를 사용하도록 하자. 이 자료구조는 global 변수로서, 초기화값을 담을 때 사용한다.

const KInitialFilters={
    Filter.glutenFree:false,
    Filter.lactoseFree:false,
    Filter.vegetarian:false,
    Filter.vegan:false,

  };

또한, _TabsScreen에 _selectedFilters의 초기화값으로 KInitialFilters를 사용하도록 하자.

 class _TabsScreen extends State<TabsScreen>{

  int _selectedPageIndex=0;
  final List<Meal>_favoriteMeals=[];
  Map<Filter,bool>_selectedFilters=KInitialFilters;

void _setScreen함수에서, setState() 함수를 다시 작동시켜서, filter의 초기화값을 result값으로 설정한 후, 다시 화면을 전환하는 방법을 설정한다. 근데 이때, _selectedFilters는 절대로 null값을 허용하지 않는다. 그러므로 dart문법에 사용되어지는, ?? 사용을 권장한다. 이때, left_value가 null이면, right_value를 자동으로 채택하게 된다.


 //new function
  void _setScreen(String identifier)async  {
    Navigator.of(context).pop();
    if(identifier=='filters'){
      final result=await Navigator.of(context).push<Map<Filter,bool>>(
          MaterialPageRoute(builder: (ctx)=>const FilterScreen(),
          )
      );

      setState(() {
        _selectedFilters=result??KInitialFilters;
      });



    }else{
      Navigator.of(context).pop();
    }

  }

step 02) 정의내린 filter를 category screen에 적용하도록 하자.

Widget build함수에 availableMeals의 list를 하나 만들어준다. 이 list의 원소는 아래와 같다. 아래의 예시는 glutenFree이고, 이를 적용한 알고리즘은 다른 항목에도 동일하게 적용된다.

  • 조건(1)_selectedFilters[Filter.glutenFree]항목이 true이면, 배제
  • 조건(2)dummyMeals의 data중에서 meal.isGluten항목이 false이면, !meal.isGlutenFree는 true가 됨.

두 조건이 true이면, 해당 Meal항목을 제거해야 하므로, false를 반한한다.


final availableMeals=dummyMeals.where((meal){
      if(_selectedFilters[Filter.glutenFree]! && !meal.isGlutenFree){
        return false;
      }

       if(_selectedFilters[Filter.lactoseFree]! && !meal.isLactoseFree){
        return false;
      }

       if(_selectedFilters[Filter.vegan]! && !meal.isVegan){
        return false;
      }

       if(_selectedFilters[Filter.vegetarian]! && !meal.isVegetarian){
        return false;}

      return true;
      }).toList();

이제,filter의 모든 항목을 정했으니, 이를 CategoryScreen에 적용해야 한다. 앞에 정의내린 List을 받아줄 변수를 하나 생성한다.

class CategoriesScreen extends StatelessWidget{
  const CategoriesScreen({
    super.key,
    required this.ontoggleFavorite,
    required this.availableMeals,
    });

  final void Function(Meal meal) ontoggleFavorite;
  final List<Meal>availableMeals;


  void __selectCategory(BuildContext context,Category category){
    final filterMeals=availableMeals.where((meal)=>meal.categories.contains(category.id)).toList();

그렇다면, tab.dart에 CategoriesScreen부분 또한 이 변화된 내용을 적용해야하므로, 아래와 같이 정의한다.

   Widget activePage=CategoriesScreen(
      ontoggleFavorite:_toggleMealFavoriteStatus,
      availableMeals: availableMeals,);

step 03) 아래의 filter내용을 조정한다.


  import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';

enum Filter{
  glutenFree,
  lactoseFree,
  vegetarian,
  vegan
}

class FilterScreen extends StatefulWidget{
  const FilterScreen({super.key,required this.currentFilters});
  final Map<Filter,bool>currentFilters;

  @override
  State<StatefulWidget>createState(){
    return _FilterScreen();
  }


}


class _FilterScreen extends State<FilterScreen>{
  var _glutenFreeFilterSet=false;
  var _lactoseFreeFilterSet=false;
  var _vegetarianFilterSet=false;
  var _veganFilterSet=false;


  @override
  void initState(){
    super.initState();
    _glutenFreeFilterSet=widget.currentFilters[Filter.glutenFree]!;
    _lactoseFreeFilterSet=widget.currentFilters[Filter.lactoseFree]!;
    _vegetarianFilterSet=widget.currentFilters[Filter.vegetarian]!;
    _veganFilterSet=widget.currentFilters[Filter.vegan]!;
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //Scaffold-appBar
      appBar:AppBar(
        title:const Text("Your filter"),
      )
      ,//Scaffold-body

      body:PopScope(
      canPop: false,
      onPopInvokedWithResult: (bool didPop, dynamic result) {
        if(didPop) return;
          Navigator.of(context).pop({
            Filter.glutenFree: _glutenFreeFilterSet,
            Filter.lactoseFree: _lactoseFreeFilterSet,
            Filter.vegetarian: _vegetarianFilterSet,
            Filter.vegan: _veganFilterSet,
          });
        },



        child: Column(
          children: [

아래는 full code

./screens/filter.dart

import 'package:flutter/material.dart';
import 'tabs.dart';
import '../widget/main_drawer.dart';

enum Filter{
  glutenFree,
  lactoseFree,
  vegetarian,
  vegan
}

class FilterScreen extends StatefulWidget{
  const FilterScreen({super.key,required this.currentFilters});
  final Map<Filter,bool>currentFilters;

  @override
  State<StatefulWidget>createState(){
    return _FilterScreen();
  }


}


class _FilterScreen extends State<FilterScreen>{
  var _glutenFreeFilterSet=false;
  var _lactoseFreeFilterSet=false;
  var _vegetarianFilterSet=false;
  var _veganFilterSet=false;


  @override
  void initState(){
    super.initState();
    _glutenFreeFilterSet=widget.currentFilters[Filter.glutenFree]!;
    _lactoseFreeFilterSet=widget.currentFilters[Filter.lactoseFree]!;
    _vegetarianFilterSet=widget.currentFilters[Filter.vegetarian]!;
    _veganFilterSet=widget.currentFilters[Filter.vegan]!;
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      //Scaffold-appBar
      appBar:AppBar(
        title:const Text("Your filter"),
      )
      ,//Scaffold-body

      body:PopScope(
      canPop: false,
      onPopInvokedWithResult: (bool didPop, dynamic result) {
        if(didPop) return;
          Navigator.of(context).pop({
            Filter.glutenFree: _glutenFreeFilterSet,
            Filter.lactoseFree: _lactoseFreeFilterSet,
            Filter.vegetarian: _vegetarianFilterSet,
            Filter.vegan: _veganFilterSet,
          });
        },



        child: Column(
          children: [
            //value:boolean, onChanged:activation funciton if true
            //title: message appears
            //activeColor: choose the color if switch is activated
            //SwitchListTile(01)
            SwitchListTile(
              value:_glutenFreeFilterSet , 
              onChanged: (bool isChecked){
                setState((){
                  _glutenFreeFilterSet=isChecked;
                });
              },
              //1 ) SwitchListTile-Glutten-free
              title:Text(
                'Glutten-free',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)),
              //SwitchListTile-subtitle
              subtitle:Text(
                'Only include glute-free meals.',
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              //active color-color if switch is activated
              activeColor: Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),    

              ),

              //2) SwitchListTitle-Lactose Free
            SwitchListTile(
              value:_lactoseFreeFilterSet,
              onChanged:(bool isChecked){
                setState((){
                  _lactoseFreeFilterSet=isChecked;
                });
              },
              title:Text(
                'Lactose-Free',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)
              ),
              subtitle:Text(
                "Only include lactose-free meals",
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              activeColor:Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),
            ),

            //3) SwitchListTitle-vegeterian
           SwitchListTile(
              value:_vegetarianFilterSet,
              onChanged:(bool isChecked){
                setState((){
                  _vegetarianFilterSet=isChecked;
                });
              },
              title:Text(
                'Vegetrian',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)
              ),
              subtitle:Text(
                "Only include meals for Vegeterian",
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              activeColor:Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),
            ),



            //4) SwitchListTitle-_veganFilterSet
             SwitchListTile(
              value:_veganFilterSet,
              onChanged:(bool isChecked){
                setState((){
                  _veganFilterSet=isChecked;
                });
              },
              title:Text(
                'Vegean',
                style:Theme.of(context).textTheme.titleLarge!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,)
              ),
              subtitle:Text(
                "Only include meals for Vegan",
                style:Theme.of(context).textTheme.labelMedium!.copyWith(
                  color:Theme.of(context).colorScheme.onSurface,
                )
              ),
              activeColor:Theme.of(context).colorScheme.tertiary,
              contentPadding: const EdgeInsets.only(left:34,right:22),
            ),


          ],
        ),
      )
    );

  }

}

./screens/tabs.dart


import 'package:flutter/material.dart';
import 'package:meal_app/data/dummy_data.dart';
import 'categories.dart';
import 'filters.dart';
import 'meals.dart';
import '../models/meal.dart';
import '../widget/main_drawer.dart';
import '../widget/meal_item.dart';

const KInitialFilters={
    Filter.glutenFree:false,
    Filter.lactoseFree:false,
    Filter.vegetarian:false,
    Filter.vegan:false,

  };


//하나의 route에서 다른 route로 이동을 해야하므로, 우리는
//StatelessWidget이 아닌 StatefulWidget를 사용한다. 
class TabsScreen extends StatefulWidget{
  const TabsScreen({super.key});

  @override
  State<TabsScreen>createState(){
    return _TabsScreen();
  }

}


class _TabsScreen extends State<TabsScreen>{

  int _selectedPageIndex=0;
  final List<Meal>_favoriteMeals=[];
  Map<Filter,bool>_selectedFilters=KInitialFilters;





  /*
  그렇다면, snackBar라는 option을 통해서 우리가 전달하고자 하는 메시지를
  인자로 주고, 그걸 snackBar에 보여주는 것을 목표로 한다. 

  1) _favoriteMeals에 meal을 추가할때, 
      "Marked as a favorite"메세지 보내기

  2) _favoriteMeals에서 meal을 삭제할때,
     "Meal is no longer a favorite"메시지 보내기


  */
  void _showInfoMessage(String message){
    ScaffoldMessenger.of(context).clearSnackBars();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        ),
    );
  }

  void _toggleMealFavoriteStatus(Meal meal){
    //parameter로 받은 meal이 현재 _favoriteMeal에 있는지 체크
    final isExisting=_favoriteMeals.contains(meal);

    if(isExisting){
       setState(() {
          _favoriteMeals.remove(meal);
       });
       _showInfoMessage("Meal is no longer a favorite");

    }else{
      setState(() {
         _favoriteMeals.add(meal);
      });
      _showInfoMessage("Marked as a favorite");

    }    
  }





  var activePageTitle="Categories";

  void _selectPage(int index){
    setState((){
      _selectedPageIndex=index;

    });
  }

  //new function
  void _setScreen(String identifier)async  {
    Navigator.of(context).pop();
    if(identifier=='filters'){
      final result=await Navigator.of(context).push<Map<Filter,bool>>(
          MaterialPageRoute(builder: (ctx)=>FilterScreen(currentFilters: _selectedFilters,),
          )
      );

      setState(() {
        _selectedFilters=result??KInitialFilters;
      });



    }else{
      Navigator.of(context).pop();
    }

  }

  @override
  Widget build(BuildContext context){
    final availableMeals=dummyMeals.where((meal){
      if(_selectedFilters[Filter.glutenFree]! && !meal.isGlutenFree){
        return false;
      }

       if(_selectedFilters[Filter.lactoseFree]! && !meal.isLactoseFree){
        return false;
      }

       if(_selectedFilters[Filter.vegan]! && !meal.isVegan){
        return false;
      }

       if(_selectedFilters[Filter.vegetarian]! && !meal.isVegetarian){
        return false;}

      return true;
      }).toList();





    Widget activePage=CategoriesScreen(
      ontoggleFavorite:_toggleMealFavoriteStatus,
      availableMeals: availableMeals,);

    if(_selectedPageIndex==1){
      activePage=MealsScreen( 
      meals:_favoriteMeals,
      ontoggleFavorite:_toggleMealFavoriteStatus,);
      activePageTitle="Your favorite";
    }



    return Scaffold(
      appBar:AppBar(
        title:Text(activePageTitle),
      ),
      drawer: MainDrawer(
        onSelectScreen:_setScreen,
      ),
      body:activePage,
      bottomNavigationBar:BottomNavigationBar(
        onTap:_selectPage,
        currentIndex: _selectedPageIndex,
        items:const [
          BottomNavigationBarItem(icon:Icon(Icons.set_meal),label:"Categories"),
          BottomNavigationBarItem(icon:Icon(Icons.star),label:"Favorite"),

        ]
       ),
    );
  }

}

./screens/categories.dart


import 'package:flutter/material.dart';
import '../data/dummy_data.dart';
import '../widget/category_grid_item.dart';
import '../models/category.dart';
import '../models/meal.dart';
import './meals.dart';

class CategoriesScreen extends StatelessWidget{
  const CategoriesScreen({
    super.key,
    required this.ontoggleFavorite,
    required this.availableMeals,
    });

  final void Function(Meal meal) ontoggleFavorite;
  final List<Meal>availableMeals;


  void __selectCategory(BuildContext context,Category category){
    final filterMeals=availableMeals.where((meal)=>meal.categories.contains(category.id)).toList();


    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context)=>MealsScreen(
          title: 'Welcome to ${category.title} section', 
          meals: filterMeals,
          ontoggleFavorite:ontoggleFavorite,
          ) )
    );
  }




  @override
  Widget build(BuildContext context){
    return Scaffold(
      //appBar
      body:GridView(
        gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          childAspectRatio: 3/2,
          crossAxisSpacing: 20,
          mainAxisSpacing: 20,
          ),
        children: [
          for(final category in availableCategories)
          CategoryGridItem(
            category:category,
            onSelectCategory:(){
              __selectCategory(context,category);
            },

          )

        ],
      ),




    );
  }
}