المتجر: أضف إلى السلة
جزء من قسم المشاريع في رحلة Flutter على Coddy. الدرس 22 من 36.
نريد سلة التسوق في شاشتين: تضيف صفحة المنتج إليها، وتعرض الشبكة عدد العناصر فيها. توضع البيانات التي تحتاج إليها أكثر من شاشة في نموذج يُشارك عبر Provider.
class CartModel extends ChangeNotifier {
final List<Product> _items = [];
List<Product> get items => _items;
int get count => _items.length;
void add(Product product) {
_items.add(product);
notifyListeners();
}
}أحط التطبيق بأكمله بـ ChangeNotifierProvider في main، حتى تتمكن كل شاشة من الوصول إلى سلة التسوق. لا يغيّر الزر سوى سلة التسوق، لذا يستخدم context.read<CartModel>().add(product). تعرض الشبكة العدد، لذا تستخدم context.watch، الذي يعيد بناء الشبكة في كل مرة يُشغَّل فيها notifyListeners.
يظهر العدد على أيقونة سلة التسوق على شكل Badge، وهي الفقاعة الصغيرة التي تستخدمها التطبيقات لعرض الرسائل غير المقروءة. يخفي isLabelVisible: count > 0 الفقاعة عندما تكون سلة التسوق فارغة.
التحدي
صعبأضف إلى سلة مشتركة:
- استورد
package:provider/provider.dart، واكتبCartModelكما ورد في الشرح النظري، وأحطMyAppبعنصرChangeNotifierProviderينشئه - في صفحة المنتج، أضف بعد مسافة قدرها 32 بكسل
FilledButton.iconمعIcons.add_shopping_cartوAdd to cart: يضيف المنتج، ويخفي أي رسالة معروضة بالفعل، ويعرضSnackBarيقولAdded Backpack to your cart(مع اسم المنتج) - في
actionsضمنAppBarالخاص بالشبكة، اعرضIcons.shopping_cartداخلBadgeمع العدد، على بُعد 24 بكسل من اليمين
جرّب بنفسك
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: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductScreen(product: product),
),
);
},
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,
),
),
],
),
),
],
),
),
);
}
}
class ProductScreen extends StatelessWidget {
const ProductScreen({super.key, required this.product});
final Product product;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: AppBar(title: Text(product.name)),
body: ListView(
padding: const EdgeInsets.all(24),
children: [
Container(
height: 240,
alignment: Alignment.center,
decoration: BoxDecoration(
color: product.color.shade50,
borderRadius: BorderRadius.circular(24),
),
child: Icon(product.icon, size: 120, color: product.color),
),
const SizedBox(height: 24),
Text(
money.format(product.price),
style: textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
const SizedBox(height: 12),
Text(product.description, style: textTheme.bodyLarge),
],
),
);
}
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس المشاريع
تدرّب بنفسك: مترجم Flutter عبر الإنترنت