Tienda: El total
Parte de la sección Proyectos del Journey de Flutter de Coddy. Lección 24 de 36.
Antes de pagar, quieres una cifra: el total. Pertenece al modelo y se calcula a partir de los artículos, igual que el recuento:
double get total => _items.fold(0, (sum, product) => sum + product.price);fold recorre la lista con un valor acumulado. Empieza en 0 y, para cada producto, te proporciona el valor acumulado hasta ese momento, sum, y el producto; lo que devuelvas se convierte en el siguiente sum. Después del último producto, ese es el total.
Muéstralo en la parte inferior de la pantalla del carrito, donde suelen estar los botones para finalizar la compra en las aplicaciones de compras. El bottomNavigationBar de un Scaffold puede ser cualquier widget fijado en la parte inferior; envuélvelo en un SafeArea para que no se superponga a la barra de inicio del teléfono. Dentro, un Row con un Spacer empuja el importe hasta el extremo derecho.
El total pasa por el mismo formateador money que los demás precios, para que tenga el mismo aspecto.
Desafío
IntermedioMuestra el total:
- añade el getter
totalaCartModel - dale al
Scaffolddel carrito unabottomNavigationBar: unSafeAreaque contenga 16 píxeles de padding alrededor de unRowconTotal, unSpacery el total formateado - ambos textos con el
titleLargedel tema; el importe también en negrita
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 List<Product> _items = [];
List<Product> get items => _items;
int get count => _items.length;
void add(Product product) {
_items.add(product);
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')),
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),
trailing: Text(money.format(product.price)),
),
],
),
);
}
}
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