diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/custom/custom_app_bar.dart b/lib/custom/custom_app_bar.dart new file mode 100644 index 0000000..059a16e --- /dev/null +++ b/lib/custom/custom_app_bar.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:primer_practica/src/delegates/article_search_delegate.dart'; +import 'package:provider/provider.dart'; +import 'package:primer_practica/src/providers/carrito_providers.dart'; +import 'package:badges/badges.dart' as badges; + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + @override + Widget build(BuildContext context) { + var carrito = Provider.of(context, listen: true); + + return AppBar( + title: const Text('Home'), + backgroundColor: Colors.blue, + actions: [ + IconButton( + icon: const Icon(Icons.search), + onPressed: () { + showSearch( + context: context, + delegate: ArticleSearchDelegate(), + ); + }, + ), + Padding( + padding: const EdgeInsets.only(right: 16.0), + child: badges.Badge( + badgeContent: Text( + '${carrito.totalCarrito}', + style: const TextStyle(color: Colors.white), + ), + child: IconButton( + icon: const Icon(Icons.shopping_cart), + onPressed: () { + Navigator.pushNamed(context, 'carrito'); + }, + ), + ), + ), + ], + ); + } + + @override + Size get preferredSize => Size.fromHeight(kToolbarHeight); +} diff --git a/lib/main.dart b/lib/main.dart index 7662203..46759c0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:primer_practica/src/providers/carrito_providers.dart'; import 'package:primer_practica/src/config/routes.dart'; void main() { @@ -10,14 +12,21 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Primer practica', - theme: ThemeData( - primarySwatch: Colors.blue, + return MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => CarritoProvider(), + ), + ], + child: MaterialApp( + title: 'Primer practica', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + initialRoute: 'login', + debugShowCheckedModeBanner: false, + routes: getApplicationRoutes(), ), - initialRoute: 'login', // Establece la ruta inicial - debugShowCheckedModeBanner: false, - routes: getApplicationRoutes(), // Usa las rutas definidas ); } } diff --git a/lib/src/config/routes.dart b/lib/src/config/routes.dart index eea585e..3b74c15 100644 --- a/lib/src/config/routes.dart +++ b/lib/src/config/routes.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:primer_practica/src/pages/formulario_categoria.dart'; import 'package:primer_practica/src/pages/login_page.dart'; import 'package:primer_practica/src/pages/home_page.dart'; - +import 'package:primer_practica/src/pages/carrito_page.dart'; Map getApplicationRoutes() { return { - 'login': (BuildContext context) => const LoginPage(), - 'home': (BuildContext context) => const HomePage(), - //'articles': (BuildContext context) => const ArticlesPage(), - 'formulario': (BuildContext context) => const FormularioCategoria(), + 'login': (BuildContext context) => LoginPage(), + 'home': (BuildContext context) => HomePage(), + 'carrito': (BuildContext context) => CarritoPage(), + 'formulario': (BuildContext context) => FormularioCategoria(), }; } diff --git a/lib/src/delegates/article_search_delegate.dart b/lib/src/delegates/article_search_delegate.dart index 0d9a391..b672b0e 100644 --- a/lib/src/delegates/article_search_delegate.dart +++ b/lib/src/delegates/article_search_delegate.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:primer_practica/src/controllers/articles_controller.dart'; import 'package:primer_practica/src/models/articles_model.dart'; +import 'package:primer_practica/src/providers/carrito_providers.dart'; // Importa CarritoProvider class ArticleSearchDelegate extends SearchDelegate { final ArticleController articleCtrl = ArticleController(); @@ -67,7 +69,8 @@ class ArticleSearchDelegate extends SearchDelegate { trailing: IconButton( icon: Icon(Icons.shopping_cart), onPressed: () { - + final carritoProvider = Provider.of(context, listen: false); + carritoProvider.agregarCarrito(article); // Llama al método correcto close(context, null); // Cierra el SearchDelegate }, ), diff --git a/lib/src/http_api/category_service.dart b/lib/src/http_api/category_service.dart index f2f2d97..678c9e7 100644 --- a/lib/src/http_api/category_service.dart +++ b/lib/src/http_api/category_service.dart @@ -1,4 +1,4 @@ -import 'dart:convert'; // Para convertir datos a JSON +import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:primer_practica/environments/urls.dart' as api; diff --git a/lib/src/models/articles_model.dart b/lib/src/models/articles_model.dart index 72915e4..d3fe484 100644 --- a/lib/src/models/articles_model.dart +++ b/lib/src/models/articles_model.dart @@ -1,4 +1,4 @@ -class ArticlesModel{ +class ArticlesModel { final int id; final String clave; final String nombre; @@ -29,6 +29,17 @@ class ArticlesModel{ ); } + Map toJson() { + return { + 'id': id, + 'clave': clave, + 'nombre': nombre, + 'categoriaId': categoriaId, + 'precios': precios.map((price) => price.toJson()).toList(), + 'activo': activo, + }; + } + static List fromJsonArray(List jsonArray) { return jsonArray.map((json) => ArticlesModel.fromJson(json)).toList(); } @@ -49,4 +60,11 @@ class Price { precio: json['precio'].toDouble(), ); } -} \ No newline at end of file + + Map toJson() { + return { + 'id': id, + 'precio': precio, + }; + } +} diff --git a/lib/src/models/carrito_model.dart b/lib/src/models/carrito_model.dart new file mode 100644 index 0000000..ee9f71d --- /dev/null +++ b/lib/src/models/carrito_model.dart @@ -0,0 +1,29 @@ +import 'package:primer_practica/src/models/articles_model.dart'; + +class CarritoModel { + final ArticlesModel articulo; + int cantidad; + double precio; + + CarritoModel({ + required this.articulo, + this.cantidad = 1, + double? precio, + }) : precio = precio ?? (articulo.precios.isNotEmpty ? articulo.precios[0].precio : 0.0); + + Map toJson() { + return { + 'articulo': articulo.toJson(), + 'cantidad': cantidad, + 'precio': precio, + }; + } + + factory CarritoModel.fromJson(Map json) { + return CarritoModel( + articulo: ArticlesModel.fromJson(json['articulo']), + cantidad: json['cantidad'], + precio: json['precio'], // Asegúrate de que se maneje correctamente el precio + ); + } +} diff --git a/lib/src/pages/articles_page.dart b/lib/src/pages/articles_page.dart index 05b3404..86c2cfd 100644 --- a/lib/src/pages/articles_page.dart +++ b/lib/src/pages/articles_page.dart @@ -6,7 +6,6 @@ class ArticlePage extends StatefulWidget { final int categoryId; - // creamos un constructor de ArticlePage que recibe categoryId como parámetro. const ArticlePage({Key? key, required this.categoryId}) : super(key: key); @override _ArticlePageState createState() => _ArticlePageState(); @@ -20,8 +19,6 @@ class _ArticlePageState extends State { @override void initState() { super.initState(); - // Inicializa _articlesFuture llamando al método getArticles del controlador de - // artículos con el categoryId. _articlesFuture = _articleController.getArticles(widget.categoryId); } @@ -43,7 +40,6 @@ class _ArticlePageState extends State { List articles = snapshot.data!['data'] as List; //Lista de articulos return ListView.builder( - // Definimos el número de elementos en la lista. itemCount: articles.length, itemBuilder: (context, index) { final article = articles[index]; diff --git a/lib/src/pages/carrito_page.dart b/lib/src/pages/carrito_page.dart new file mode 100644 index 0000000..73d0bf6 --- /dev/null +++ b/lib/src/pages/carrito_page.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:primer_practica/src/providers/carrito_providers.dart'; +import 'package:primer_practica/src/models/carrito_model.dart'; + +class CarritoPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + var carritoProvider = Provider.of(context); + + return Scaffold( + appBar: AppBar( + title: Text('Carrito'), + ), + body: Column( + children: [ + Expanded( + child: ListView.builder( + itemCount: carritoProvider.carrito.length, // Número de elementos en el carrito + itemBuilder: (context, index) { + final item = carritoProvider.carrito[index]; + return CarritoItemWidget(item: item); // Construte los elementos + }, + ), + ), + Padding( + padding: const EdgeInsets.all(20.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '\$ ${carritoProvider.totalPrecio.toStringAsFixed(2)}', // Total de todos los articulos + style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700), + ), + ElevatedButton( + onPressed: () { + + }, + child: Text('COMPRAR'), + ), + ], + ), + ), + ], + ), + ); + } +} + +class CarritoItemWidget extends StatelessWidget { + final CarritoModel item; + + const CarritoItemWidget({Key? key, required this.item}) : super(key: key); + + @override + Widget build(BuildContext context) { + var carritoProvider = Provider.of(context, listen: false); + + return Card( + margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), + child: Padding( + padding: const EdgeInsets.all(30.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.articulo.nombre, // Muestra el nombre del artículo + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Precio: \$${item.precio.toStringAsFixed(2)}'), // Muestra el precio del artículo + Row( + children: [ + IconButton( + icon: Icon(Icons.remove), + onPressed: () { + if (item.cantidad > 1) { + carritoProvider.actualizarArticulo(item, item.cantidad - 1); // Disminuye la cantidad del artículo + } + }, + ), + Text('${item.cantidad}'), + IconButton( + icon: Icon(Icons.add), + onPressed: () { + carritoProvider.actualizarArticulo(item, item.cantidad + 1); // Aumenta la cantidad del artículo + }, + ), + ], + ), + Text('Total: \$${(item.precio * item.cantidad).toStringAsFixed(2)}'), // Muestra el total por artículo + ], + ), + IconButton( + icon: Icon(Icons.delete, color: Colors.red), + onPressed: () { + carritoProvider.eliminarDelCarrito(item); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/pages/formulario_categoria.dart b/lib/src/pages/formulario_categoria.dart index 3a2dde6..2405ced 100644 --- a/lib/src/pages/formulario_categoria.dart +++ b/lib/src/pages/formulario_categoria.dart @@ -46,10 +46,9 @@ class _FormularioCategoriaState extends State { if (result['ok']) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(result['message'])), // Muestra mensaje de éxito + SnackBar(content: Text(result['message'])), ); print("hola"); - //Navigator.pop(context); Navigator.pushNamed(context, 'home'); } else { ScaffoldMessenger.of(context).showSnackBar( @@ -79,7 +78,7 @@ class _FormularioCategoriaState extends State { decoration: const InputDecoration(labelText: 'Nombre'), keyboardType: TextInputType.text, ), - const SizedBox(height: 60), // Ajuste del espacio entre campos y botón + const SizedBox(height: 60), ElevatedButton( onPressed: _guardarCategoria, child: const Text('GUARDAR'), diff --git a/lib/src/pages/home_page.dart b/lib/src/pages/home_page.dart index e4c1aa2..5f62e1e 100644 --- a/lib/src/pages/home_page.dart +++ b/lib/src/pages/home_page.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:primer_practica/src/pages/category/lista_categorias.dart'; -import 'package:primer_practica/src/delegates/article_search_delegate.dart'; +import 'package:primer_practica/custom/custom_app_bar.dart'; +import 'package:primer_practica/src/providers/carrito_providers.dart'; +import 'package:provider/provider.dart'; class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); @@ -8,21 +10,7 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Home'), - backgroundColor: Colors.blue, - actions: [ - IconButton( - icon: const Icon(Icons.search), - onPressed: () { - showSearch( - context: context, - delegate: ArticleSearchDelegate(), - ); - }, - ) - ], - ), + appBar: CustomAppBar(), drawer: Drawer( child: ListView( padding: EdgeInsets.zero, diff --git a/lib/src/providers/carrito_providers.dart b/lib/src/providers/carrito_providers.dart new file mode 100644 index 0000000..80ac0da --- /dev/null +++ b/lib/src/providers/carrito_providers.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:primer_practica/src/models/carrito_model.dart'; +import 'package:primer_practica/src/models/articles_model.dart'; + +class CarritoProvider with ChangeNotifier { + List _carrito = []; + + List get carrito => _carrito; + + void agregarCarrito(ArticlesModel article) { + final carritoItem = CarritoModel( + articulo: article, + cantidad: 1, + precio: article.precios.isNotEmpty ? article.precios[0].precio : 0.0, + ); + _carrito.add(carritoItem); + notifyListeners(); + } + + void eliminarDelCarrito(CarritoModel item) { + _carrito.remove(item); + notifyListeners(); + } + + void actualizarArticulo(CarritoModel item, int quantity) { + int index = _carrito.indexOf(item); + if (index != -1) { + _carrito[index].cantidad = quantity; + notifyListeners(); + } + } + + double get totalPrecio { + double total = 0.0; + for (var item in _carrito) { + total += item.precio * item.cantidad; + } + return total; + } + + int get totalCarrito => _carrito.length; +} diff --git a/pubspec.lock b/pubspec.lock index 38c54c6..b486ed1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + badges: + dependency: "direct main" + description: + name: badges + sha256: "727580d938b7a1ff47ea42df730d581415606b4224cfa708671c10287f8d3fe6" + url: "https://pub.dev" + source: hosted + version: "2.0.3" boolean_selector: dependency: transitive description: @@ -232,6 +240,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.12.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" nm: dependency: transitive description: @@ -264,6 +280,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 2b2cd2b..6309ac9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,6 +32,8 @@ dependencies: sdk: flutter http: ^1.2.2 connectivity_plus: ^2.3.6 + provider: ^6.1.2 + badges: ^2.0.1 # The following adds the Cupertino Icons font to your application.