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

المتجر: تفاصيل المنتج

جزء من قسم المشاريع في رحلة Flutter على Coddy. الدرس 21 من 36.

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

لجعل البطاقة بأكملها قابلة للضغط، ضع InkWell داخل Card، محيطًا بمحتواها. ينفّذ onTap ويرسم تأثير التموج الذي تراه في تطبيقات Material؛ ويحافظ Clip.antiAlias الخاص بالبطاقة على بقاء التموج داخل الزوايا المستديرة.

Card(
  clipBehavior: Clip.antiAlias,
  child: InkWell(
    onTap: () {
      Navigator.push(context, MaterialPageRoute(
        builder: (context) => ProductScreen(product: product),
      ));
    },
    child: Column(...),
  ),
)

صفحة المنتج عبارة عن ListView، لذا يمكن تمرير الوصف الطويل. يعرض AppBar اسم المنتج وسهم الرجوع، الذي يضيفه Flutter لك عند الانتقال إلى شاشة جديدة.

لإظهار السعر بحجم كبير، ابدأ من headlineSmall في السمة وغيّر ما تحتاج إليه فقط باستخدام copyWith: textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold). وُضعت ?. لأن نمط السمة قد يكون null.

challenge icon

التحدي

متوسط

أنشئ صفحة لكل منتج:

  • اكتب ProductScreen تستقبل required product، مع الاسم في AppBar
  • يكون محتواها ListView مع حشوة قدرها 24 بكسل: مساحة بارتفاع 240 بكسل بلون product.color.shade50 وزوايا مستديرة بمقدار 24، مع الأيقونة بحجم 120، ثم مسافة 24 بكسل، والسعر بخط عريض باستخدام headlineSmall باللون الأساسي، ثم مسافة 12 بكسل والوصف باستخدام bodyLarge
  • أحط Column كل بطاقة بـ InkWell يفتح صفحة المنتج

جرّب بنفسك

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

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

final money = NumberFormat.currency(locale: 'en_US', symbol: '\$');

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
      ),
      home: const ShopScreen(),
    );
  }
}

class Product {
  const Product({
    required this.name,
    required this.price,
    required this.icon,
    required this.color,
    required this.description,
  });

  final String name;
  final double price;
  final IconData icon;
  final MaterialColor color;
  final String description;
}

const List<Product> products = [
  Product(
    name: 'Headphones',
    price: 59,
    icon: Icons.headphones,
    color: Colors.indigo,
    description: 'Wireless headphones with 30 hours of battery.',
  ),
  Product(
    name: 'Watch',
    price: 129,
    icon: Icons.watch,
    color: Colors.teal,
    description: 'A smart watch that counts your steps.',
  ),
  Product(
    name: 'Backpack',
    price: 45.5,
    icon: Icons.backpack,
    color: Colors.orange,
    description: 'Room for a laptop, a lunch and a jacket.',
  ),
  Product(
    name: 'Mug',
    price: 12,
    icon: Icons.coffee,
    color: Colors.brown,
    description: 'Holds a big coffee and keeps it warm.',
  ),
  Product(
    name: 'Camera',
    price: 1299,
    icon: Icons.photo_camera,
    color: Colors.blueGrey,
    description: 'A mirrorless camera for sharp photos.',
  ),
  Product(
    name: 'Sneakers',
    price: 89.99,
    icon: Icons.directions_run,
    color: Colors.red,
    description: 'Light running shoes for every day.',
  ),
];

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Coddy Shop')),
      body: GridView.count(
        crossAxisCount: 2,
        padding: const EdgeInsets.all(16),
        mainAxisSpacing: 12,
        crossAxisSpacing: 12,
        childAspectRatio: 0.8,
        children: [
          for (Product product in products) ProductCard(product: product),
        ],
      ),
    );
  }
}

class ProductCard extends StatelessWidget {
  const ProductCard({super.key, required this.product});

  final Product product;

  @override
  Widget build(BuildContext context) {
    return Card(
      clipBehavior: Clip.antiAlias,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Expanded(
            child: Container(
              width: double.infinity,
              alignment: Alignment.center,
              color: product.color.shade50,
              child: Icon(product.icon, size: 56, color: product.color),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  product.name,
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                const SizedBox(height: 4),
                Text(
                  money.format(product.price),
                  style: TextStyle(
                    fontWeight: FontWeight.bold,
                    color: Theme.of(context).colorScheme.primary,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
quiz iconاختبر نفسك

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

جميع دروس المشاريع

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