Tienda: Eliminar artículos
Parte de la sección Proyectos del Journey de Flutter de Coddy. Lección 26 de 36.
La gente cambia de opinión. Cada fila del carrito tiene un botón para quitar una unidad de ese producto, y la fila desaparece cuando ya no queda ninguna unidad.
void remove(Product product) {
if (quantityOf(product) > 1) {
_quantities[product] = quantityOf(product) - 1;
} else {
_quantities.remove(product);
}
notifyListeners();
}Quitar la clave del mapa cuando la cantidad es 1, en lugar de establecerla en 0, es lo que hace que desaparezca la fila: items son las claves del mapa.
Ahora una fila termina con dos elementos: el total y el botón. trailing acepta un widget, así que coloca ambos en un Row con mainAxisSize: MainAxisSize.min, que hace que la fila solo tenga el ancho de su contenido.
Un carrito vacío merece un estado vacío, como el que tenía la lista de tareas pendientes: un icono de carrito tenue y una línea de texto en lugar de la lista.
Desafío
IntermedioPermite que las personas quiten artículos:
- añade el método
removeaCartModel - haz que el
trailingde cada fila sea unRowcon su total y unIconButtonconIcons.remove_circle_outlineque quite una unidad - cuando el carrito esté vacío, muestra un
Icons.shopping_cart_outlinedcentrado (tamaño 64, coloroutlinedel tema), 12 píxeles de espacio yYour cart is empty
Pruébalo tú mismo
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: 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 CartModel extends ChangeNotifier {
final Map<Product, int> _quantities = {};
List<Product> get items => _quantities.keys.toList();
int quantityOf(Product product) => _quantities[product] ?? 0;
int get count => _quantities.values.fold(0, (sum, quantity) => sum + quantity);
double get total => items.fold(0, (sum, product) => sum + product.price * quantityOf(product));
void add(Product product) {
_quantities[product] = quantityOf(product) + 1;
notifyListeners();
}
}
class ShopScreen extends StatelessWidget {
const ShopScreen({super.key});
@override
Widget build(BuildContext context) {
final count = context.watch<CartModel>().count;
return Scaffold(
appBar: AppBar(
title: const Text('Coddy Shop'),
actions: [
Padding(
padding: const EdgeInsets.only(right: 8),
child: IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const CartScreen()),
);
},
icon: Badge(
isLabelVisible: count > 0,
label: Text('$count'),
child: const Icon(Icons.shopping_cart),
),
),
),
],
),
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),
const SizedBox(height: 32),
FilledButton.icon(
onPressed: () {
context.read<CartModel>().add(product);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Added ${product.name} to your cart')),
);
},
icon: const Icon(Icons.add_shopping_cart),
label: const Text('Add to cart'),
),
],
),
);
}
}
class CartScreen extends StatelessWidget {
const CartScreen({super.key});
@override
Widget build(BuildContext context) {
final cart = context.watch<CartModel>();
return Scaffold(
appBar: AppBar(title: const Text('Your Cart')),
bottomNavigationBar: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Text('Total', style: Theme.of(context).textTheme.titleLarge),
const Spacer(),
Text(
money.format(cart.total),
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
],
),
),
),
body: ListView(
children: [
for (Product product in cart.items)
ListTile(
leading: CircleAvatar(
backgroundColor: product.color.shade50,
child: Icon(product.icon, color: product.color),
),
title: Text(product.name),
subtitle: Text('${cart.quantityOf(product)} × ${money.format(product.price)}'),
trailing: Text(money.format(product.price * cart.quantityOf(product))),
),
],
),
);
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Proyectos
1Proyecto: Lista de tareas
Lista de tareas: La lista de tareasLista de tareas: Añadir una tarea4Proyecto: Rastreador de hábitos
Rastreador de hábitos: Tus hábitosRastreador de hábitos: La semana2Proyecto: Cuestionario
Cuestionario: La preguntaCuestionario: Correcto o incorrectoPractica por tu cuenta: Compilador de Flutter online