Avances de información de usuario

parent a3c2ebea
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:practica1_flutter/src/config/routes.dart'; import 'package:practica1_flutter/src/config/routes.dart';
import 'package:practica1_flutter/src/config/routes.dart'; // Importa el archivo de rutas import 'package:practica1_flutter/src/providers/carrito_provider.dart';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
...@@ -11,17 +12,21 @@ class MyApp extends StatelessWidget { ...@@ -11,17 +12,21 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MultiProvider(
debugShowCheckedModeBanner: false, providers: [
title: 'Practica Flutter', ChangeNotifierProvider(create: (_) => CarritoProvider()),
theme: ThemeData( ],
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), child: MaterialApp(
useMaterial3: true, debugShowCheckedModeBanner: false,
title: 'Practica Flutter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
//home: myWidget() : Se quita la propiedad home para permitir la navegación basada en rutas
initialRoute: 'login', // se define la ruta inicial
routes: getApplicationRoutes(),// Se usa las rutas definidas en routes.dart y se quito el myWidget
), ),
//home: myWidget() : Se quita la propiedad home para permitir la navegación basada en rutas
initialRoute: 'login', // se define la ruta inicial
routes: getApplicationRoutes(), // Se usa las rutas definidas en routes.dart y se quito el myWidget
); );
} }
} }
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/config/routes.dart'; import 'package:practica1_flutter/src/config/routes.dart';
import 'package:practica1_flutter/src/pages/article_form.dart';
import '../pages/category/category_widget.dart'; import '../pages/category/category_widget.dart';
import '../pages/category_form.dart'; import '../pages/category_form.dart';
import '../pages/home_page.dart'; import '../pages/home_page.dart';
...@@ -9,9 +10,11 @@ import '../pages/articles_page.dart'; ...@@ -9,9 +10,11 @@ import '../pages/articles_page.dart';
Map<String, WidgetBuilder> getApplicationRoutes() { Map<String, WidgetBuilder> getApplicationRoutes() {
return <String, WidgetBuilder>{ return <String, WidgetBuilder>{
'login': (BuildContext context) => const LoginPage(), 'login': (BuildContext context) => const LoginPage(),
'homeSeller': (BuildContext context) => const HomeSellerPage(), 'homeSeller': (BuildContext context) => HomeSellerPage(),
//'articulos': (BuildContext context) => const ArticlesPage(), //'articulos': (BuildContext context) => const ArticlesPage(),
'category_form': (BuildContext context) => CategoryForm(), 'category_form': (BuildContext context) => CategoryForm(),
//'cart': (BuildContext context) => CarritoPage(),
'article_form': (BuildContext context) => ArticleFormPage(), // Define la ruta
}; };
} }
...@@ -3,6 +3,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; ...@@ -3,6 +3,7 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:practica1_flutter/src/models/article_model.dart'; import 'package:practica1_flutter/src/models/article_model.dart';
import '../http_api/articles_api.dart'; import '../http_api/articles_api.dart';
import '../models/article.dart';
class ArticleController { class ArticleController {
final Connectivity _connectivity = Connectivity(); final Connectivity _connectivity = Connectivity();
......
...@@ -4,22 +4,22 @@ import 'package:connectivity_plus/connectivity_plus.dart'; ...@@ -4,22 +4,22 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:practica1_flutter/src/http_api/category_api.dart'; import 'package:practica1_flutter/src/http_api/category_api.dart';
import 'package:practica1_flutter/src/models/category_model.dart'; import 'package:practica1_flutter/src/models/category_model.dart';
//en este manejaremos la información que vamos a presentar visualmente al usuario.
class CategoryController { class CategoryController {
final Connectivity _connectivity = Connectivity(); final Connectivity _connectivity = Connectivity();
final CategoryService _categoryService = CategoryService();
// Método para obtener las categorías
Future<Map<String, dynamic>> getCategories() async { Future<Map<String, dynamic>> getCategories() async {
Map<String, dynamic> mapResp = { Map<String, dynamic> mapResp = {
'ok': false, 'ok': false,
'message': 'No hay categorías', 'message': 'No hay categorías',
'data': null 'data': null
}; };
ConnectivityResult connectivityResult = await _connectivity.checkConnectivity(); ConnectivityResult connectivityResult = await _connectivity.checkConnectivity();
if (connectivityResult != ConnectivityResult.none) { if (connectivityResult != ConnectivityResult.none) {
if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) { if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) {
CategoryService categoryApi = CategoryService(); Map<String, dynamic> respGet = await _categoryService.getCategories();
Map<String, dynamic> respGet = await categoryApi.getCategories();
if (respGet['statusCode'] == 200) { if (respGet['statusCode'] == 200) {
try { try {
var decodeResp = json.decode(respGet['body']); var decodeResp = json.decode(respGet['body']);
...@@ -36,7 +36,30 @@ class CategoryController { ...@@ -36,7 +36,30 @@ class CategoryController {
} }
} }
} }
return mapResp;
}
// Método para agregar una categoría
Future<Map<String, dynamic>> addCategory(CategoryModel category) async {
Map<String, dynamic> mapResp = {
'ok': false,
'message': 'No se pudo agregar la categoría',
'data': null
};
ConnectivityResult connectivityResult = await _connectivity.checkConnectivity();
if (connectivityResult != ConnectivityResult.none) {
if (connectivityResult == ConnectivityResult.wifi || connectivityResult == ConnectivityResult.mobile) {
Map<String, dynamic> respPost = await _categoryService.addCategory(category);
if (respPost['statusCode'] == 200) {
mapResp['ok'] = true;
mapResp['message'] = "Categoría agregada exitosamente";
mapResp['data'] = json.decode(respPost['body']);
} else {
mapResp['message'] = "${respPost['body']}";
}
}
}
return mapResp; return mapResp;
} }
} }
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:practica1_flutter/src/models/category_model.dart';
class CategoryService {
static const String _baseUrl = 'https://basic2.visorus.com.mx';
Future<Map<String, dynamic>> addCategory(CategoryModel category) async {
final String url = '$_baseUrl/categoria';
final Map<String, dynamic> categoryData = category.toJson();
try {
final http.Response response = await http.post(
Uri.parse(url),
body: json.encode({"data": [categoryData]}),
headers: {'Content-Type': 'application/json'},
);
if (response.statusCode == 200) {
return {"statusCode": response.statusCode, "body": json.decode(response.body)};
} else {
return {"statusCode": response.statusCode, "body": response.body};
}
} catch (e) {
return {"statusCode": 501, "body": '$e'};
}
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/article_model.dart';
import '../providers/carrito_provider.dart';
class CustomSearchDelegate extends SearchDelegate<String> {
final List<ArticleModel> searchList;
CustomSearchDelegate(this.searchList);
@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
);
}
@override
Widget buildResults(BuildContext context) {
final List<ArticleModel> searchResults = searchList
.where((item) => item.nombre.toLowerCase().contains(query.toLowerCase()))
.toList();
//AGREGAR PRODUCTO AL CARRITO
return Scaffold(
body: ListView.builder(
itemCount: searchResults.length,
itemBuilder: (context, index) {
final article = searchResults[index];
return ListTile(
title: Text(article.nombre),
subtitle: Text('Precio: ${article.precios[0].precio}'),
trailing: IconButton(
//icono del carrito
icon: Icon(Icons.add_shopping_cart, color: Colors.indigo),
onPressed: () {
//Llamamos el provider carrito para agregar un articulo
final carritoProvider = Provider.of<CarritoProvider>(context, listen: false);
carritoProvider.addToCart(article);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Artículo agregado al carrito')),
);
},
),
);
},
),
);
}
@override
Widget buildSuggestions(BuildContext context) {
final List<ArticleModel> suggestionList = query.isEmpty
? []
: searchList
.where((item) => item.nombre.toLowerCase().contains(query.toLowerCase()))
.toList();
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context, index) {
return Container(
color: Colors.grey[200],
child: ListTile(
leading: Icon(Icons.search, color: Colors.blue),
title: Text(
suggestionList[index].nombre,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
onTap: () {
query = suggestionList[index].nombre;
showResults(context);
},
),
);
},
);
}
}
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../models/article_model.dart';
//METODO DE AGREGAR NUEVOS ARTICULOS
class ArticleService {
final String _baseUrl = 'https://basic2.visorus.com.mx/articulo';
Future<bool> addArticle(Map<String, dynamic> articleData) async {
final response = await http.post(
Uri.parse(_baseUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode(articleData),
);
if (response.statusCode == 200) {
return true;
} else {
return false;
}
}
}
class ArticleService1 {
Future<List<ArticleModel>> getArticles() async {
final response = await http.get(
Uri.parse('https://basic2.visorus.com.mx/articulo?offset=0&max=100'));
if (response.statusCode == 200) {
final Map<String, dynamic> data = json.decode(response.body);
List<dynamic> articlesData = data['data'];
return articlesData.map((json) => ArticleModel.fromJson(json)).toList();
} else {
throw Exception('Error al cargar los artículos');
}
}
}
\ No newline at end of file
...@@ -3,6 +3,7 @@ import 'package:http/http.dart' as http; ...@@ -3,6 +3,7 @@ import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import '../../environments/api_app.dart'; import '../../environments/api_app.dart';
import '../models/article_model.dart';
class ArticleApi { class ArticleApi {
Future<Map<String, dynamic>> getArticles() async{ Future<Map<String, dynamic>> getArticles() async{
...@@ -18,4 +19,4 @@ class ArticleApi { ...@@ -18,4 +19,4 @@ class ArticleApi {
return {"statusCode": 501, "body": '$e'}; return {"statusCode": 501, "body": '$e'};
} }
} }
} }
\ No newline at end of file
...@@ -4,17 +4,16 @@ import 'package:http/http.dart' as http; ...@@ -4,17 +4,16 @@ import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:practica1_flutter/environments/api_app.dart'; import 'package:practica1_flutter/environments/api_app.dart';
import 'package:practica1_flutter/src/models/category_model.dart';
class CategoryService { class CategoryService {
// Método para obtener las categorías desde la API
Future<Map<String, dynamic>> getCategories() async { Future<Map<String, dynamic>> getCategories() async {
final ConnectivityResult connectivityResult = await (Connectivity().checkConnectivity()); final ConnectivityResult connectivityResult = await (Connectivity().checkConnectivity());
//El Connectivity().checkConnectivity() verifica el estado actual de la conexión a Internet
if (connectivityResult == ConnectivityResult.none) { if (connectivityResult == ConnectivityResult.none) {
return {"statusCode": 0, "body": "No internet connection"}; return {"statusCode": 0, "body": "No internet connection"};
} // y Si no hay conexión a Internet se retorna }
// statusCode 0 y un mensaje de sin conexion a internet
String url = '${apiApp}/categoria?offset=0&max=100'; String url = '${apiApp}/categoria?offset=0&max=100';
if (kDebugMode) { if (kDebugMode) {
...@@ -27,4 +26,30 @@ class CategoryService { ...@@ -27,4 +26,30 @@ class CategoryService {
return {"statusCode": 501, "body": '$e'}; return {"statusCode": 501, "body": '$e'};
} }
} }
// metodo para agregar una nueva categoría a la API
Future<Map<String, dynamic>> addCategory(CategoryModel category) async {
final ConnectivityResult connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.none) {
return {"statusCode": 0, "body": "No internet connection"};
}
String url = '${apiApp}/categoria';
if (kDebugMode) {
print('Url -> $url');
}
try {
final response = await http.post(
Uri.parse(url),
headers: {"Content-Type": "application/json"},
body: json.encode(category.toJson()), // Enviar directamente el JSON del objeto
);
return {"statusCode": response.statusCode, "body": response.body};
} catch (e) {
return {"statusCode": 501, "body": '$e'};
}
}
} }
class Article {
final String name;
final List<double> prices;
//Para mi carrito
Article({required this.name, required this.prices});
factory Article.fromJson(Map<String, dynamic> json) {
return Article(
name: json['name'],
prices: List<double>.from(json['prices'].map((x) => x.toDouble())),
);
}
}
import 'article.dart';
class CarritoModel {
final Article articulo;
int cantidad;
double precio;
CarritoModel({required this.articulo, required this.cantidad, required this.precio});
}
...@@ -44,18 +44,18 @@ class CategoryModel { ...@@ -44,18 +44,18 @@ class CategoryModel {
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return { return {
"id": id, "id": id,
"version": version, "clave": key ?? '',
"clave": key, "nombre": name ?? '',
"nombre": name, "fechaCreado": createdDate ?? 0,
"fechaCreado": createdDate,
"categoria": parentCategory?.toJson(),
"categorias": subCategories != null "categorias": subCategories != null
? CategoryModel.toJsonArray(subCategories!) ? CategoryModel.toJsonArray(subCategories!)
: [], : [],
"activo": active, "activo": active ?? true,
}; };
} }
// Método para crear una lista de CategoryModel desde un JSON // Método para crear una lista de CategoryModel desde un JSON
static List<CategoryModel> fromJsonArray(json) { static List<CategoryModel> fromJsonArray(json) {
if (json == null) return []; if (json == null) return [];
......
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../http_api/article_service.dart';
class ArticleFormPage extends StatefulWidget {
@override
_ArticleFormPageState createState() => _ArticleFormPageState();
}
class _ArticleFormPageState extends State<ArticleFormPage> {
final _formKey = GlobalKey<FormState>();
String clave = '';
int categoria = 1;
String nombre = '';
List<double> precios = [0.0, 0.0];
bool activo = true;
Future<void> _submitForm() async {
if (_formKey.currentState?.validate() ?? false) {
_formKey.currentState?.save();
final payload = {
"clave": clave,
"categoria": categoria,
"nombre": nombre,
"precios": precios.map((precio) => {"precio": precio}).toList(),
"activo": activo,
};
final articleService = ArticleService();
final success = await articleService.addArticle(payload);
if (success) {
Navigator.pop(context); // Regresa a la lista de artículos
} else {
// Maneja el error
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error al guardar el artículo')),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Formulario de Artículo'),
centerTitle: true,
foregroundColor: Colors.white,
backgroundColor: Colors.teal,
),
body: Padding(
padding: EdgeInsets.all(60.0),
child: Form(
key: _formKey,
child: ListView(
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Clave',
border: OutlineInputBorder()),
//
onSaved: (value) => clave = value ?? '',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa la clave';
}
return null;
},
),
SizedBox(height: 20),
TextFormField(
decoration: InputDecoration(labelText: 'Categoría',
border: OutlineInputBorder()),
keyboardType: TextInputType.number,
//
onSaved: (value) => categoria = int.parse(value ?? '1'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa la categoría';
}
return null;
},
),
SizedBox(height: 20),
TextFormField(
decoration: InputDecoration(labelText: 'Nombre', border: OutlineInputBorder()),
onSaved: (value) => nombre = value ?? '',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa el nombre';
}
return null;
},
),
SizedBox(height: 20),
TextFormField(
decoration: InputDecoration(labelText: 'Precio 1', border: OutlineInputBorder()),
keyboardType: TextInputType.number,
onSaved: (value) => precios[0] = double.parse(value ?? '0.0'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa el precio';
}
return null;
},
),
SizedBox(height: 20),
TextFormField(
decoration: InputDecoration(labelText: 'Precio 2', border: OutlineInputBorder()),
keyboardType: TextInputType.number,
onSaved: (value) => precios[1] = double.parse(value ?? '0.0'),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Por favor ingresa el precio';
}
return null;
},
),
SizedBox(height: 20),
SwitchListTile(
title: Text('Activo'),
value: activo,
onChanged: (value) {
setState(() {
activo = value;
});
},
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _submitForm,
child: Text('Guardar'),
),
],
),
),
),
);
}
}
...@@ -32,8 +32,16 @@ class _ArticlePageState extends State<ArticlePage> { ...@@ -32,8 +32,16 @@ class _ArticlePageState extends State<ArticlePage> {
title: const Text('Artículos'), title: const Text('Artículos'),
backgroundColor: Colors.indigoAccent, backgroundColor: Colors.indigoAccent,
foregroundColor: Colors.white, foregroundColor: Colors.white,
),
),
//BOTON DE AGREGAR ARTICULO
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.pushNamed(context, 'article_form');
},
child: Icon(Icons.add),
tooltip: 'Agregar Artículo',
),
body: FutureBuilder<Map<String, dynamic>>( body: FutureBuilder<Map<String, dynamic>>(
future: _articlesFuture, future: _articlesFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
...@@ -42,7 +50,7 @@ class _ArticlePageState extends State<ArticlePage> { ...@@ -42,7 +50,7 @@ class _ArticlePageState extends State<ArticlePage> {
if (snapshot.data!['data'] != null && (snapshot.data!['data'] as List).isNotEmpty) { if (snapshot.data!['data'] != null && (snapshot.data!['data'] as List).isNotEmpty) {
List<ArticleModel> articles = snapshot.data!['data'] as List<ArticleModel>; List<ArticleModel> articles = snapshot.data!['data'] as List<ArticleModel>;
//Lista de articulos //Lista de articulos
return ListView.builder( return ListView.builder(
// Definimos el número de elementos en la lista. // Definimos el número de elementos en la lista.
itemCount: articles.length, itemCount: articles.length,
...@@ -51,7 +59,7 @@ class _ArticlePageState extends State<ArticlePage> { ...@@ -51,7 +59,7 @@ class _ArticlePageState extends State<ArticlePage> {
return Card( return Card(
child: ListTile( child: ListTile(
title: Text(article.nombre), title: Text(article.nombre),
subtitle: Text('id :${article.categoriaId}'), subtitle: Text('id :${article.clave}'),
//subtitle: Text('Clave: ${article.clave}'), //subtitle: Text('Clave: ${article.clave}'),
trailing: Text('\$${article.precios.isNotEmpty ? article.precios.first.precio.toStringAsFixed(2) : 'N/A'}'), trailing: Text('\$${article.precios.isNotEmpty ? article.precios.first.precio.toStringAsFixed(2) : 'N/A'}'),
), ),
......
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/carrito_provider.dart';
class CarritoPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Mi carrito de compras'),
backgroundColor: Colors.amber
),
body: Consumer<CarritoProvider>(
builder: (context, carritoProvider, child) {
final carrito = carritoProvider.carrito;
return ListView.builder(
itemCount: carrito.length,
itemBuilder: (context, index) {
final item = carrito[index];
return Card(
child: ListTile(
leading:Image.asset('assets/img/logo_visorus.jpg', height: 15), // Imagen del artículo
title: Text('${item.articulo.nombre}'),// TITULO DEL ARTICULO
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
//BOTON DE ELIMINAR -
IconButton(
icon: Icon(Icons.remove,color: Colors.red),
onPressed: () {
carritoProvider.updateQuantity(item.articulo, item.cantidad - 1);
},
),
Text('${item.cantidad}'),
//AGREGAR + PRODUCTO
IconButton(
icon: Icon(Icons.add, color: Colors.blue),
onPressed: () {
carritoProvider.updateQuantity(item.articulo, item.cantidad + 1);
},
),
//CALCULAR PERCIO TOTAL DEL PRODUCTO
Text('Subtotal: \$${(item.precio * item.cantidad).toStringAsFixed(2)}'),
],
),
],
),
trailing: IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
carritoProvider.removeFromCart(item.articulo);
},
),
),
);
},
);
},
),
bottomNavigationBar: Consumer<CarritoProvider>(
builder: (context, carritoProvider, child) {
return Container(
padding: EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Total: \$${carritoProvider.totalAmount.toStringAsFixed(2)}',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Compra confirmada')),
);
},
child: Text('COMPRAR'),
),
],
),
);
},
),
);
}
}
...@@ -12,6 +12,7 @@ class _CategoryWidgetState extends State<CategoryWidget> { ...@@ -12,6 +12,7 @@ class _CategoryWidgetState extends State<CategoryWidget> {
final CategoryController _categoryCtrl = CategoryController(); final CategoryController _categoryCtrl = CategoryController();
@override @override
//MOSTRAR ARTICULOS
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
child: Padding( child: Padding(
...@@ -34,8 +35,7 @@ class _CategoryWidgetState extends State<CategoryWidget> { ...@@ -34,8 +35,7 @@ class _CategoryWidgetState extends State<CategoryWidget> {
// Navegar al listado de artículos y pasa el ID de la categoría // Navegar al listado de artículos y pasa el ID de la categoría
Navigator.push( Navigator.push(
//El push lo que hace es empujar la ruta dada por medio de la clase //El push lo que hace es empujar la ruta dada por medio de la clase
// MaterialPageRoute, en esta clase se envía el contexto y el widget // MaterialPageRoute, en esta clase se envía el contexto y el widget de la nueva pantalla que va a mostrar.
// de la nueva pantalla que va a mostrar.
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ArticlePage( builder: (context) => ArticlePage(
......
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/models/category_model.dart';
import '../http_api/category_api.dart';
class CategoryForm extends StatelessWidget { class CategoryForm extends StatelessWidget {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
...@@ -6,24 +10,67 @@ class CategoryForm extends StatelessWidget { ...@@ -6,24 +10,67 @@ class CategoryForm extends StatelessWidget {
final TextEditingController _fechaCreadoController = TextEditingController(); final TextEditingController _fechaCreadoController = TextEditingController();
final TextEditingController _nombreController = TextEditingController(); final TextEditingController _nombreController = TextEditingController();
// Función para obtener la fecha en milisegundos
int _getFechaCreadoEnMilisegundos(String fecha) { int _getFechaCreadoEnMilisegundos(String fecha) {
try { try {
// convertimos el String a DateTime y luego obtenemos el tiempo en milisegundos
DateTime fechaCreado = DateTime.parse(fecha); DateTime fechaCreado = DateTime.parse(fecha);
return fechaCreado.millisecondsSinceEpoch; return fechaCreado.millisecondsSinceEpoch;
} catch (e) { } catch (e) {
// Si hay un error en la conversión, devolvemos 0
return 0; return 0;
} }
} }
// Función para guardar la categoría
void _saveCategory(BuildContext context) async {
if (_formKey.currentState!.validate()) {
final clave = _claveController.text.trim();
final nombre = _nombreController.text.trim();
final fechaCreadoStr = _fechaCreadoController.text.trim();
if (clave.isEmpty || nombre.isEmpty || fechaCreadoStr.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Todos los campos deben estar completos')),
);
return;
}
final fechaCreado = _getFechaCreadoEnMilisegundos(fechaCreadoStr);
CategoryModel category = CategoryModel(
id: 0,
version: 0,
key: clave,
name: nombre,
createdDate: fechaCreado,
active: true,
);
final categoryService = CategoryService();
final response = await categoryService.addCategory(category);
print('Response Status Code: ${response['statusCode']}');
print('Response Body: ${response['body']}');
if (response['statusCode'] == 200) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Categoría guardada exitosamente')),
);
Navigator.pushReplacementNamed(context, 'homeSeller');
} else {
final responseBody = json.decode(response['body']);
final errorMessage = responseBody['error'] ?? 'Error desconocido';
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error al guardar categoría: $errorMessage')),
);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Agregar Categoría'), title: const Text('Agregar Categoría'),
backgroundColor: Colors.red
), ),
body: Padding( body: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
...@@ -31,11 +78,11 @@ class CategoryForm extends StatelessWidget { ...@@ -31,11 +78,11 @@ class CategoryForm extends StatelessWidget {
key: _formKey, key: _formKey,
child: Column( child: Column(
children: [ children: [
// ----- AGREGAR CLAVE ----
TextFormField( TextFormField(
controller: _claveController, controller: _claveController,
decoration: InputDecoration(labelText: 'Clave', decoration: InputDecoration(labelText: 'Clave',
border: OutlineInputBorder() border: OutlineInputBorder()),
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Por favor ingrese la clave'; return 'Por favor ingrese la clave';
...@@ -45,16 +92,15 @@ class CategoryForm extends StatelessWidget { ...@@ -45,16 +92,15 @@ class CategoryForm extends StatelessWidget {
), ),
SizedBox(height: 20), SizedBox(height: 20),
// ----- AGREGAR FECHA ----
TextFormField( TextFormField(
controller: _fechaCreadoController, controller: _fechaCreadoController,
decoration: InputDecoration( decoration: InputDecoration(labelText: 'Fecha FORMATO(YYYY-MM-DD)',
labelText: 'Fecha Creado (YYYY-MM-DD)',
border: OutlineInputBorder()), border: OutlineInputBorder()),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Por favor ingrese la fecha en formato YYYY-MM-DD'; return 'Por favor ingrese la fecha en formato YYYY-MM-DD';
} }
// Validación de la fecha
try { try {
DateTime.parse(value); DateTime.parse(value);
} catch (e) { } catch (e) {
...@@ -64,14 +110,11 @@ class CategoryForm extends StatelessWidget { ...@@ -64,14 +110,11 @@ class CategoryForm extends StatelessWidget {
}, },
), ),
SizedBox(height: 20), SizedBox(height: 20),
// ----- AGREGAR NOMBRE DEL ARTICULO ----
TextFormField( TextFormField(
//padding: const EdgeInsets.symmetric(horizontal: 80.0), // añado mi margen horizontal
controller: _nombreController, controller: _nombreController,
decoration: InputDecoration( decoration: InputDecoration(labelText: 'Nombre',
labelText: 'Nombre', border: OutlineInputBorder()),
border: OutlineInputBorder(),
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'Por favor ingrese el nombre'; return 'Por favor ingrese el nombre';
...@@ -81,16 +124,7 @@ class CategoryForm extends StatelessWidget { ...@@ -81,16 +124,7 @@ class CategoryForm extends StatelessWidget {
), ),
SizedBox(height: 20), SizedBox(height: 20),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () => _saveCategory(context),
if (_formKey.currentState!.validate()) {
// Guardar los valores y mostrar mensaje en consola
print({
"clave": _claveController.text,
"fechaCreado": _getFechaCreadoEnMilisegundos(_fechaCreadoController.text),
"nombre": _nombreController.text,
});
}
},
child: Text('GUARDAR'), child: Text('GUARDAR'),
), ),
], ],
......
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:practica1_flutter/src/pages/category/category_widget.dart'; // Asegúrate de importar la página de categorías import 'package:badges/badges.dart' as badges;
import 'package:practica1_flutter/src/pages/category_form.dart'; // Asegúrate de importar la página del formulario import 'package:provider/provider.dart';
import 'package:practica1_flutter/src/pages/category/category_widget.dart';
import '../delegates/custom_search_delegate.dart';
import '../http_api/article_service.dart';
import '../http_api/articles_api.dart';
import '../models/article_model.dart';
import '../providers/carrito_provider.dart';
import 'carrito_page.dart';
class HomeSellerPage extends StatelessWidget { class HomeSellerPage extends StatefulWidget {
const HomeSellerPage({Key? key}) : super(key: key); @override
_HomeSellerPageState createState() => _HomeSellerPageState();
}
class _HomeSellerPageState extends State<HomeSellerPage> {
List<ArticleModel> articles = [];
@override
void initState() {
super.initState();
_fetchArticles();
}
_fetchArticles() async {
var fetchedArticles = await ArticleService1().getArticles();
setState(() {
articles = fetchedArticles;
});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -11,10 +36,43 @@ class HomeSellerPage extends StatelessWidget { ...@@ -11,10 +36,43 @@ class HomeSellerPage extends StatelessWidget {
length: 2, length: 2,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("ShopMaster"), title: const Text("Tienda Prueba"),
centerTitle: true, centerTitle: true,
foregroundColor: Colors.white, foregroundColor: Colors.white,
backgroundColor: Colors.indigoAccent, backgroundColor: Colors.indigoAccent,
actions: [
IconButton(
icon: Icon(Icons.search, color: Colors.green),
onPressed: () {
showSearch(
context: context,
delegate: CustomSearchDelegate(articles), // Pasando la lista de artículos
);
},
),
// AGREGAR PRODUCTO AL CARRITO
Consumer<CarritoProvider>(
builder: (context, carrito, child) {
return badges.Badge(
badgeContent: Text(
carrito.totalItems.toString(),
style: TextStyle(color: Colors.white)//Color num notificación
),
position: badges.BadgePosition.topEnd(top: 0, end: 3),
child: IconButton(
icon: Icon(Icons.card_travel_outlined),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => CarritoPage()),
);
},
),
);
},
),
],
bottom: const TabBar( bottom: const TabBar(
tabs: [ tabs: [
Tab(icon: Icon(Icons.article_outlined), text: "Artículos"), Tab(icon: Icon(Icons.article_outlined), text: "Artículos"),
...@@ -23,9 +81,10 @@ class HomeSellerPage extends StatelessWidget { ...@@ -23,9 +81,10 @@ class HomeSellerPage extends StatelessWidget {
), ),
), ),
drawer: MenuLateral(), drawer: MenuLateral(),
body: TabBarView( body: TabBarView(
children: [ children: [
CategoryWidget(), // Página de categorías CategoryWidget(),
], ],
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
...@@ -33,14 +92,13 @@ class HomeSellerPage extends StatelessWidget { ...@@ -33,14 +92,13 @@ class HomeSellerPage extends StatelessWidget {
Navigator.pushNamed(context, 'category_form'); Navigator.pushNamed(context, 'category_form');
}, },
child: Icon(Icons.add), child: Icon(Icons.add),
tooltip: 'Agregar Categoría', // Mensaje para el botón tooltip: 'Agregar Categoría',
), ),
), ),
); );
} }
} }
// Clase del menú lateral
class MenuLateral extends StatelessWidget { class MenuLateral extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
......
...@@ -8,7 +8,7 @@ class LoginPage extends StatelessWidget { ...@@ -8,7 +8,7 @@ class LoginPage extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Estoy en Login'), title: const Text('Estoy en Login'),
backgroundColor: Colors.lightBlue, // Agregar color en la appBar backgroundColor: Colors.lightBlue,
), ),
body: Center( body: Center(
child: Column( child: Column(
...@@ -29,7 +29,7 @@ class LoginPage extends StatelessWidget { ...@@ -29,7 +29,7 @@ class LoginPage extends StatelessWidget {
const SizedBox(height: 20), const SizedBox(height: 20),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 80.0), // Añado mi margen horizontal padding: const EdgeInsets.symmetric(horizontal: 80.0),
child: TextField( child: TextField(
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Contraseña', labelText: 'Contraseña',
...@@ -46,6 +46,7 @@ class LoginPage extends StatelessWidget { ...@@ -46,6 +46,7 @@ class LoginPage extends StatelessWidget {
}, },
child: const Text('Acceder'), child: const Text('Acceder'),
), ),
], ],
), ),
), ),
......
import 'package:flutter/material.dart';
import '../models/article_model.dart';
class CarritoModel {
final ArticleModel articulo;
int cantidad;
double precio;
CarritoModel({required this.articulo, required this.cantidad, required this.precio});
}
class CarritoProvider with ChangeNotifier {
List<CarritoModel> _carrito = [];
List<CarritoModel> get carrito => _carrito;
void addToCart(ArticleModel article) {
var existingItem = _carrito.firstWhere( // firstWhere busca un artículo en el carrito que tenga el mismo ID que el artículo
(item) => item.articulo.id == article.id,
orElse: () => CarritoModel(articulo: article, cantidad: 0, precio: 0),
);
if (existingItem.cantidad == 0) {
CarritoModel item = CarritoModel(
articulo: article,
cantidad: 1,
precio: article.precios[0].precio,
);
_carrito.add(item);
} else {
existingItem.cantidad += 1;
}
notifyListeners(); // esto nos notifica los widgets que escuchan cambios en el carrito de compras, hace que se actualicen y reflejen los cambios.
}
void removeFromCart(ArticleModel article) {
_carrito.removeWhere((item) => item.articulo.id == article.id);
notifyListeners();
}
void updateQuantity(ArticleModel article, int quantity) {
var existingItem = _carrito.firstWhere(
(item) => item.articulo.id == article.id,
orElse: () => CarritoModel(articulo: article, cantidad: 0, precio: 0),
);
if (existingItem.cantidad != 0) {
existingItem.cantidad = quantity;
if (existingItem.cantidad <= 0) {
removeFromCart(article);
}
}
notifyListeners();
}
double get totalAmount => _carrito.fold(0.0, (total, current) => total + (current.precio * current.cantidad));
int get totalItems => _carrito.fold(0, (total, current) => total + current.cantidad);
}
...@@ -17,6 +17,14 @@ packages: ...@@ -17,6 +17,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.11.0" version: "2.11.0"
badges:
dependency: "direct main"
description:
name: badges
sha256: a7b6bbd60dce418df0db3058b53f9d083c22cdb5132a052145dc267494df0b84
url: "https://pub.dev"
source: hosted
version: "3.1.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
...@@ -232,6 +240,14 @@ packages: ...@@ -232,6 +240,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.12.0" version: "1.12.0"
nested:
dependency: transitive
description:
name: nested
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nm: nm:
dependency: transitive dependency: transitive
description: description:
...@@ -264,6 +280,14 @@ packages: ...@@ -264,6 +280,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
provider:
dependency: "direct dev"
description:
name: provider
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
url: "https://pub.dev"
source: hosted
version: "6.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
......
...@@ -27,6 +27,7 @@ dependencies: ...@@ -27,6 +27,7 @@ dependencies:
sdk: flutter sdk: flutter
http: ^1.2.2 http: ^1.2.2
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
badges: ^3.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
...@@ -34,6 +35,7 @@ dev_dependencies: ...@@ -34,6 +35,7 @@ dev_dependencies:
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
connectivity_plus: ^2.3.0 connectivity_plus: ^2.3.0
provider: ^6.0.2
flutter: flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment