Loja: Remover Itens
Parte da seção Projetos do Journey de Flutter da Coddy. Lição 26 de 36.
As pessoas mudam de ideia. Cada linha do carrinho ganha um botão de remoção que tira uma unidade daquele produto, e a linha desaparece quando não restar nenhuma.
void remove(Product product) {
if (quantityOf(product) > 1) {
_quantities[product] = quantityOf(product) - 1;
} else {
_quantities.remove(product);
}
notifyListeners();
}Remover a chave do mapa quando a quantidade chega a 1, em vez de defini-la como 0, é o que faz a linha desaparecer: items são as chaves do mapa.
Agora, uma linha termina com duas coisas: o total e o botão. trailing recebe um widget, então coloque ambos em uma Row com mainAxisSize: MainAxisSize.min, que mantém a linha apenas tão larga quanto o conteúdo.
Um carrinho vazio merece um estado vazio, como a lista de tarefas tinha: um ícone suave de carrinho e uma linha de texto no lugar da lista.
Desafio
MédioPermita que as pessoas removam itens:
- adicione o método
removeaCartModel - faça com que o
trailingde cada linha seja umaRowcom o total e umIconButtoncomIcons.remove_circle_outlineque remova uma unidade - quando o carrinho estiver vazio, exiba um
Icons.shopping_cart_outlinedcentralizado (tamanho 64, com a coroutlinedo tema), 12 pixels de espaço eYour cart is empty
Experimente você mesmo
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 lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Projetos
1Projeto: Lista de tarefas
Lista de tarefas: A lista de tarefasLista de tarefas: Adicione uma tarefa4Projeto: Rastreador de Hábitos
Rastreador de Hábitos: Seus hábitosRastreador de Hábitos: A semanaPratique por conta própria: Compilador de Flutter online