Menu
CoddyTech
flag Ar iconالعربيةdown icon

كتاب الوصفات: المفضلة

جزء من قسم القوائم والشاشات في رحلة Flutter على Coddy. الدرس 31 من 33.

هناك وصفات تطهوها كل أسبوع. تشير علامة قلب في نهاية كل صف إلى وصفة مفضلة: قلب فارغ إن لم تكن مفضلة، وقلب أحمر ممتلئ إن كانت كذلك.

كون الوصفة مفضلة هو جزء من الوصفة، لذا يصبح حقلًا. وعلى خلاف الحقول الأخرى، تتغير قيمته، لذا لا يكون final، وتكون قيمته الابتدائية false:

bool favorite = false;

البيانات التي تتغير تحتاج إلى setState، لذا يصبح HomeScreen عنصر واجهة مستخدم ذا حالة. تنتقل الوصفات من build إلى فئة State: فالقائمة التي تُنشأ داخل build يُعاد إنشاؤها عند كل استدعاء لـ setState، وسيصبح كل قلب فارغًا.

القلب هو IconButton. يختار الرمز القلب الممتلئ أو الفارغ باستخدام ? :، والضغط عليه يبدّل قيمة الحقل: recipe.favorite = !recipe.favorite.

challenge icon

التحدي

صعب

أضف قلبًا للمفضلة إلى كل وصفة:

  • أضف إلى Recipe الحقل bool favorite = false;
  • حوّل HomeScreen إلى StatefulWidget وانقل قائمة الوصفات إلى صنف State الخاص به
  • أضف إلى كل عنصر trailing: من نوع IconButton يعرض Icons.favorite_border، أو Icons.favorite باللون الأحمر عندما تكون الوصفة مفضلة. يؤدي الضغط عليه إلى تبديل قيمة favorite

جرّب بنفسك

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final List<Recipe> recipes = [
      Recipe(name: 'Pancakes', minutes: 20, ingredients: ['Flour', 'Eggs', 'Milk', 'Butter']),
      Recipe(name: 'Tomato Soup', minutes: 35, ingredients: ['Tomatoes', 'Onion', 'Garlic', 'Olive oil']),
      Recipe(name: 'Guacamole', minutes: 10, ingredients: ['Avocados', 'Lime', 'Onion', 'Salt']),
      Recipe(name: 'Fried Rice', minutes: 25, ingredients: ['Rice', 'Eggs', 'Peas', 'Soy sauce']),
    ];
    return Scaffold(
      appBar: AppBar(title: const Text('Recipe Book')),
      body: ListView.builder(
        itemCount: recipes.length,
        itemBuilder: (context, index) {
          final Recipe recipe = recipes[index];
          return ListTile(
            leading: const Icon(Icons.restaurant_menu),
            title: Text(recipe.name),
            subtitle: Text('${recipe.minutes} min'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => RecipeScreen(recipe: recipe)),
              );
            },
          );
        },
      ),
    );
  }
}

class RecipeScreen extends StatelessWidget {
  const RecipeScreen({super.key, required this.recipe});

  final Recipe recipe;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(recipe.name)),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Text('Ready in ${recipe.minutes} minutes', style: const TextStyle(fontSize: 18)),
          const SizedBox(height: 24),
          const Text('Ingredients', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          for (String ingredient in recipe.ingredients)
            ListTile(
              leading: const Icon(Icons.check_circle_outline),
              title: Text(ingredient),
            ),
        ],
      ),
    );
  }
}

class Recipe {
  final String name;
  final int minutes;
  final List<String> ingredients;

  Recipe({required this.name, required this.minutes, required this.ingredients});
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس القوائم والشاشات

تدرّب بنفسك: مترجم Flutter عبر الإنترنت