Post Detail

Post #102

Flutter Jour Cinq Part 2

Flutter Jour Cinq (Part 2) Part 1

DeliMeals

  • 11:15 - 12:15 : Ajout des tabs
Code jusque là (1)
 1import 'package:delimeals/screens/category_meals_screen.dart';
 2import 'package:delimeals/screens/meal_detail_screen.dart';
 3import 'package:delimeals/screens/tabs_screen.dart';
 4import 'package:flutter/material.dart';
 5
 6void main() {
 7  runApp(MyApp());
 8}
 9
10class MyApp extends StatelessWidget {
11  @override
12  Widget build(BuildContext context) {
13    return MaterialApp(
14      title: 'DeliMeals',
15      theme: ThemeData(
16        primarySwatch: Colors.pink,
17        accentColor: Colors.amber,
18        canvasColor: Color.fromRGBO(255, 254, 229, 1),
19        fontFamily: 'Raleway',
20        textTheme: TextTheme(
21          headline6: TextStyle(
22            fontWeight: FontWeight.bold,
23          ),
24        ),
25      ),
26      routes: {
27        '/': (context) => TabsScreen(),
28        CategoryMealsScreen.routeName: (context) => CategoryMealsScreen(),
29        MealDetailScreen.routeName: (context) => MealDetailScreen(),
30      },
31    );
32  }
33}
34
35class MyHomePage extends StatefulWidget {
36  @override
37  _MyHomePageState createState() => _MyHomePageState();
38}
39
40class _MyHomePageState extends State<MyHomePage> {
41  @override
42  Widget build(BuildContext context) {
43    return Scaffold(
44      appBar: AppBar(
45        title: Text('DeliMeals'),
46      ),
47      body: Center(
48        child: Text('Navigation Time!'),
49      ),
50    );
51  }
52}
 1import 'package:delimeals/dummy_data.dart';
 2import 'package:delimeals/widgets/category_item.dart';
 3import 'package:flutter/material.dart';
 4import 'package:flutter/widgets.dart';
 5
 6class CategoriesScreen extends StatelessWidget {
 7  @override
 8  Widget build(BuildContext context) {
 9    return GridView(
10      padding: const EdgeInsets.all(25),
11      children: DUMMY_CATEGORIES
12          .map(
13            (catData) => CategoryItem(
14              catData.id,
15              catData.title,
16              catData.color,
17            ),
18          )
19          .toList(),
20      gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
21        maxCrossAxisExtent: 200,
22        childAspectRatio: 3 / 2,
23        crossAxisSpacing: 20,
24        mainAxisSpacing: 20,
25      ),
26    );
27  }
28}
 1import 'package:flutter/material.dart';
 2
 3import 'categories_screen.dart';
 4import 'favorites_screen.dart';
 5
 6class TabsScreen extends StatefulWidget {
 7  @override
 8  _TabsScreenState createState() => _TabsScreenState();
 9}
10
11class _TabsScreenState extends State<TabsScreen> {
12  final List<Map<String, Object>> _pages = [
13    {'page': CategoriesScreen(), 'title': 'Categories'},
14    {'page': FavoritesScreen(), 'title': 'Your Favorites'},
15  ];
16
17  int _selectedPageIndex = 0;
18
19  void _selectPage(int index) {
20    setState(() {
21      _selectedPageIndex = index;
22    });
23  }
24
25  @override
26  Widget build(BuildContext context) {
27    return Scaffold(
28      appBar: AppBar(
29        title: Text(_pages[_selectedPageIndex]['title']),
30      ),
31      body: _pages[_selectedPageIndex]['page'],
32      bottomNavigationBar: BottomNavigationBar(
33        onTap: _selectPage,
34        backgroundColor: Theme.of(context).primaryColor,
35        unselectedItemColor: Colors.white,
36        selectedItemColor: Theme.of(context).accentColor,
37        currentIndex: _selectedPageIndex,
38        items: [
39          BottomNavigationBarItem(
40            icon: Icon(Icons.category),
41            title: Text('Categories'),
42          ),
43          BottomNavigationBarItem(
44            icon: Icon(Icons.star),
45            title: Text('Favorites'),
46          ),
47        ],
48      ),
49    );
50  }
51}
 1import 'package:flutter/material.dart';
 2
 3class FavoritesScreen extends StatelessWidget {
 4  @override
 5  Widget build(BuildContext context) {
 6    return Center(
 7      child: Text('Favorites'),
 8    );
 9  }
10}
  • 13:00 - 15:00 : Drawers

Code jusqu’ici (2)

 1import 'package:delimeals/screens/category_meals_screen.dart';
 2import 'package:delimeals/screens/meal_detail_screen.dart';
 3import 'package:delimeals/screens/tabs_screen.dart';
 4import 'package:delimeals/widgets/filters_screen.dart';
 5import 'package:flutter/material.dart';
 6
 7void main() {
 8  runApp(MyApp());
 9}
10
11class MyApp extends StatelessWidget {
12  @override
13  Widget build(BuildContext context) {
14    return MaterialApp(
15      title: 'DeliMeals',
16      theme: ThemeData(
17        primarySwatch: Colors.pink,
18        accentColor: Colors.amber,
19        canvasColor: Color.fromRGBO(255, 254, 229, 1),
20        fontFamily: 'Raleway',
21        textTheme: TextTheme(
22          headline6: TextStyle(
23            fontWeight: FontWeight.bold,
24          ),
25        ),
26      ),
27      routes: {
28        '/': (context) => TabsScreen(),
29        CategoryMealsScreen.routeName: (context) => CategoryMealsScreen(),
30        MealDetailScreen.routeName: (context) => MealDetailScreen(),
31        FiltersScreen.routeName: (context) => FiltersScreen(),
32      },
33    );
34  }
35}
36
37class MyHomePage extends StatefulWidget {
38  @override
39  _MyHomePageState createState() => _MyHomePageState();
40}
41
42class _MyHomePageState extends State<MyHomePage> {
43  @override
44  Widget build(BuildContext context) {
45    return Scaffold(
46      appBar: AppBar(
47        title: Text('DeliMeals'),
48      ),
49      body: Center(
50        child: Text('Navigation Time!'),
51      ),
52    );
53  }
54}
  1import 'package:delimeals/dummy_data.dart';
  2import 'package:flutter/material.dart';
  3
  4class MealDetailScreen extends StatelessWidget {
  5  static const routeName = '/meal-detail';
  6
  7  Widget buildSectionTitle(BuildContext context, String text) {
  8    return Container(
  9      margin: EdgeInsets.symmetric(vertical: 10),
 10      child: Text(
 11        text,
 12        style: Theme.of(context).textTheme.headline6,
 13      ),
 14    );
 15  }
 16
 17  Widget buildContainer({Widget child}) {
 18    return Container(
 19      decoration: BoxDecoration(
 20        color: Colors.white,
 21        border: Border.all(color: Colors.grey),
 22        borderRadius: BorderRadius.circular(10),
 23      ),
 24      padding: EdgeInsets.all(10),
 25      margin: EdgeInsets.all(10),
 26      height: 150,
 27      width: 300,
 28      child: child,
 29    );
 30  }
 31
 32  @override
 33  Widget build(BuildContext context) {
 34    final mealId = ModalRoute.of(context).settings.arguments as String;
 35    final selectedMeal = DUMMY_MEALS.firstWhere((meal) => meal.id == mealId);
 36
 37    return Scaffold(
 38      floatingActionButton: FloatingActionButton(
 39        child: Icon(
 40          Icons.delete,
 41        ),
 42        onPressed: () {
 43          Navigator.of(context).pop(mealId);
 44        },
 45      ),
 46      appBar: AppBar(
 47        title: Text('${selectedMeal.title}'),
 48      ),
 49      body: SingleChildScrollView(
 50        child: Column(
 51          children: <Widget>[
 52            Container(
 53              height: 300,
 54              width: double.infinity,
 55              child: Image.network(
 56                selectedMeal.imageUrl,
 57                fit: BoxFit.cover,
 58              ),
 59            ),
 60            buildSectionTitle(context, 'Ingredients'),
 61            buildContainer(
 62              child: ListView.builder(
 63                itemBuilder: (context, index) => Card(
 64                  color: Theme.of(context).accentColor,
 65                  child: Padding(
 66                    padding: const EdgeInsets.symmetric(
 67                      vertical: 5,
 68                      horizontal: 10,
 69                    ),
 70                    child: Text(selectedMeal.ingredients[index]),
 71                  ),
 72                ),
 73                itemCount: selectedMeal.ingredients.length,
 74              ),
 75            ),
 76            buildSectionTitle(context, 'Steps'),
 77            buildContainer(
 78              child: ListView.builder(
 79                itemBuilder: (context, index) => Column(
 80                  children: <Widget>[
 81                    ListTile(
 82                      leading: CircleAvatar(
 83                        child: Text('# ${(index + 1)}'),
 84                      ),
 85                      title: Text(
 86                        selectedMeal.steps[index],
 87                      ),
 88                    ),
 89                    Divider(),
 90                  ],
 91                ),
 92                itemCount: selectedMeal.steps.length,
 93              ),
 94            ),
 95          ],
 96        ),
 97      ),
 98    );
 99  }
100}
 1import 'package:delimeals/dummy_data.dart';
 2import 'package:delimeals/models/meal.dart';
 3import 'package:delimeals/widgets/meal_item.dart';
 4import 'package:flutter/material.dart';
 5
 6class CategoryMealsScreen extends StatefulWidget {
 7  static const routeName = '/category-meals';
 8
 9  @override
10  _CategoryMealsScreenState createState() => _CategoryMealsScreenState();
11}
12
13class _CategoryMealsScreenState extends State<CategoryMealsScreen> {
14  String categoryTitle;
15  List<Meal> displayedMeals;
16  var _loadedInitData = false;
17
18  @override
19  void initState() {
20    super.initState();
21  }
22
23  @override
24  void didChangeDependencies() {
25    if (!_loadedInitData) {
26      final routeArgs =
27          ModalRoute.of(context).settings.arguments as Map<String, String>;
28      final categoryId = routeArgs['id'];
29
30      categoryTitle = routeArgs['title'];
31      displayedMeals = DUMMY_MEALS.where((meal) {
32        return meal.categories.contains(categoryId);
33      }).toList();
34
35      _loadedInitData = true;
36    }
37
38    super.didChangeDependencies();
39  }
40
41  void _removeMeal(String mealId) {
42    setState(() {
43      displayedMeals.removeWhere((meal) => meal.id == mealId);
44    });
45  }
46
47  @override
48  Widget build(BuildContext context) {
49    return Scaffold(
50      appBar: AppBar(
51        title: Text(
52          categoryTitle,
53        ),
54      ),
55      body: ListView.builder(
56        itemBuilder: (context, index) {
57          return MealItem(
58            id: displayedMeals[index].id,
59            title: displayedMeals[index].title,
60            imageUrl: displayedMeals[index].imageUrl,
61            duration: displayedMeals[index].duration,
62            complexity: displayedMeals[index].complexity,
63            affordability: displayedMeals[index].affordability,
64            removeItem: _removeMeal,
65          );
66        },
67        itemCount: displayedMeals.length,
68      ),
69    );
70  }
71}
 1import 'package:delimeals/widgets/main_drawer.dart';
 2import 'package:flutter/material.dart';
 3
 4class FiltersScreen extends StatelessWidget {
 5  static const routeName = '/filters';
 6
 7  @override
 8  Widget build(BuildContext context) {
 9    return Scaffold(
10      drawer: MainDrawer(),
11      appBar: AppBar(
12        title: Text('Your Filters'),
13      ),
14      body: Center(
15        child: Text('Filters'),
16      ),
17    );
18  }
19}
 1import 'package:delimeals/widgets/filters_screen.dart';
 2import 'package:flutter/material.dart';
 3
 4class MainDrawer extends StatelessWidget {
 5  Widget buildListTile(String title, IconData icon, Function tapHandler) {
 6    return ListTile(
 7      leading: Icon(
 8        icon,
 9        size: 26,
10      ),
11      title: Text(
12        title,
13        style: TextStyle(
14          fontFamily: 'RobotoCondensed',
15          fontSize: 24,
16          fontWeight: FontWeight.bold,
17        ),
18      ),
19      onTap: tapHandler,
20    );
21  }
22
23  @override
24  Widget build(BuildContext context) {
25    return Drawer(
26      child: Column(
27        children: [
28          Container(
29            height: 120,
30            width: double.infinity,
31            padding: EdgeInsets.all(20),
32            alignment: Alignment.centerLeft,
33            color: Theme.of(context).accentColor,
34            child: Text(
35              'Cooking Up !',
36              style: TextStyle(
37                fontWeight: FontWeight.w900,
38                fontSize: 30,
39                color: Theme.of(context).primaryColor,
40              ),
41            ),
42          ),
43          SizedBox(
44            height: 20,
45          ),
46          buildListTile('Meals', Icons.restaurant, () {
47            Navigator.of(context).pushReplacementNamed('/');
48          }),
49          buildListTile('Filters', Icons.settings, () {
50            Navigator.of(context).pushReplacementNamed(FiltersScreen.routeName);
51          }),
52        ],
53      ),
54    );
55  }
56}
  1import 'package:delimeals/models/meal.dart';
  2import 'package:delimeals/screens/meal_detail_screen.dart';
  3import 'package:flutter/material.dart';
  4
  5class MealItem extends StatelessWidget {
  6  final String id;
  7  final String title;
  8  final String imageUrl;
  9  final int duration;
 10  final Complexity complexity;
 11  final Affordability affordability;
 12  final Function removeItem;
 13
 14  MealItem({
 15    @required this.id,
 16    @required this.title,
 17    @required this.imageUrl,
 18    @required this.duration,
 19    @required this.complexity,
 20    @required this.affordability,
 21    @required this.removeItem,
 22  });
 23
 24  String get complexityText {
 25    switch (complexity) {
 26      case Complexity.Simple:
 27        return 'Simple';
 28        break;
 29      case Complexity.Challenging:
 30        return 'Challenging';
 31        break;
 32      case Complexity.Hard:
 33        return 'Hard';
 34        break;
 35      default:
 36        return 'Unknown';
 37    }
 38  }
 39
 40  String get affordabilityText {
 41    switch (affordability) {
 42      case Affordability.Affordable:
 43        return 'Affordable';
 44        break;
 45      case Affordability.Pricey:
 46        return 'Pricey';
 47        break;
 48      case Affordability.Luxurious:
 49        return 'Luxurious';
 50        break;
 51      default:
 52        return 'Unknown';
 53    }
 54  }
 55
 56  void selectMeal(BuildContext context) {
 57    Navigator.of(context)
 58        .pushNamed(
 59      MealDetailScreen.routeName,
 60      arguments: id,
 61    )
 62        .then((result) {
 63      if (result != null) {
 64        removeItem(result);
 65      }
 66    });
 67  }
 68
 69  @override
 70  Widget build(BuildContext context) {
 71    return InkWell(
 72      onTap: () => selectMeal(context),
 73      child: Card(
 74        shape: RoundedRectangleBorder(
 75          borderRadius: BorderRadius.circular(15),
 76        ),
 77        elevation: 4,
 78        margin: EdgeInsets.all(10),
 79        child: Column(
 80          children: <Widget>[
 81            Stack(
 82              children: <Widget>[
 83                ClipRRect(
 84                  borderRadius: BorderRadius.only(
 85                    topLeft: Radius.circular(15),
 86                    topRight: Radius.circular(15),
 87                  ),
 88                  child: Image.network(
 89                    imageUrl,
 90                    height: 250,
 91                    width: double.infinity,
 92                    fit: BoxFit.cover,
 93                  ),
 94                ),
 95                Positioned(
 96                  bottom: 20,
 97                  right: 10,
 98                  child: Container(
 99                    width: 300,
100                    color: Colors.black54,
101                    padding: EdgeInsets.symmetric(
102                      vertical: 5,
103                      horizontal: 20,
104                    ),
105                    child: Text(
106                      title,
107                      style: TextStyle(
108                        fontSize: 26,
109                        color: Colors.white,
110                        fontFamily: 'Raleway',
111                      ),
112                      softWrap: true,
113                      overflow: TextOverflow.fade,
114                    ),
115                  ),
116                ),
117              ],
118            ),
119            Padding(
120              padding: EdgeInsets.all(20),
121              child: Row(
122                mainAxisAlignment: MainAxisAlignment.spaceAround,
123                children: <Widget>[
124                  Row(
125                    children: <Widget>[
126                      Icon(Icons.schedule),
127                      SizedBox(width: 6),
128                      Text('$duration min'),
129                    ],
130                  ),
131                  Row(
132                    children: <Widget>[
133                      Icon(Icons.work),
134                      SizedBox(width: 6),
135                      Text(complexityText),
136                    ],
137                  ),
138                  Row(
139                    children: <Widget>[
140                      Icon(Icons.attach_money),
141                      SizedBox(width: 6),
142                      Text(affordabilityText)
143                    ],
144                  )
145                ],
146              ),
147            ),
148          ],
149        ),
150      ),
151    );
152  }
153}