diff --git a/analysis_options.yaml b/analysis_options.yaml index 32a850c..da732ed 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,8 @@ analyzer: errors: + invalid_return_type_for_catch_error: ignore library_private_types_in_public_api: ignore unused_field: ignore + unused_local_variable: ignore use_build_context_synchronously: ignore # include: package:flutter_lints/flutter.yaml diff --git a/lib/api/interfaces/pharmacy_to_product.dart b/lib/api/interfaces/pharmacy_to_product.dart new file mode 100644 index 0000000..cc5a336 --- /dev/null +++ b/lib/api/interfaces/pharmacy_to_product.dart @@ -0,0 +1 @@ +abstract class IPharmacyStockItem {} diff --git a/lib/api/models/pharmacy_to_product_model.dart b/lib/api/models/pharmacy_to_product_model.dart new file mode 100644 index 0000000..7df94e3 --- /dev/null +++ b/lib/api/models/pharmacy_to_product_model.dart @@ -0,0 +1,25 @@ +class PharmacyStockItem { + int? id; + int? pharmacyId; + int? productId; + int? quantity; + + PharmacyStockItem({this.id, this.pharmacyId, this.productId, this.quantity}); + + PharmacyStockItem.fromJson(Map json) { + id = json['id']; + pharmacyId = json['pharmacyId']; + productId = json['productId']; + quantity = json['quantity']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['pharmacyId'] = this.pharmacyId; + data['productId'] = this.productId; + data['quantity'] = this.quantity; + return data; + } + +} diff --git a/lib/api/models/product_models.dart b/lib/api/models/product_models.dart index a4a51d4..e7ec49e 100644 --- a/lib/api/models/product_models.dart +++ b/lib/api/models/product_models.dart @@ -1,56 +1,167 @@ +import 'package:gohealth/api/models/pharmacy_to_product_model.dart'; +import 'package:gohealth/api/models/user_models.dart'; + class ProductModels { int? id; String? name; String? description; double? price; - int? stock; String? image; double? weight; double? dimensions; double? rating; String? createdAt; String? updatedAt; + List? user; + List? pharmacyProduct; + List? reviews; + List? categories; ProductModels( {this.id, this.name, this.description, this.price, - this.stock, this.image, this.weight, this.dimensions, this.rating, this.createdAt, - this.updatedAt}); - - ProductModels.fromJson(Map json) { - id = json['id']; - name = json['name']; - description = json['description']; - price = json['price']; - stock = json['stock']; - image = json['image']; - weight = json['weight']; - dimensions = json['dimensions']; - rating = json['rating']; - createdAt = json['createdAt']; - updatedAt = json['updatedAt']; + this.updatedAt, + this.user, + this.pharmacyProduct, + this.reviews, + this.categories}); + + factory ProductModels.fromJson(Map json) { + return ProductModels( + id: json['id'], + name: json['name'], + description: json['description'], + price: json['price'], + image: json['image'], + weight: json['weight'], + dimensions: json['dimensions'], + rating: json['rating'], + createdAt: json['createdAt'], + updatedAt: json['updatedAt'], + categories: json['categories'] != null + ? (json['categories'] as List).map((i) => i.toString()).toList() + : null, + user: json['user'] != null + ? (json['user'] as List).map((i) => UserModels.fromJson(i)).toList() + : null, + pharmacyProduct: json['PharmacyProduct'] != null + ? (json['PharmacyProduct'] as List) + .map((i) => PharmacyStockItem.fromJson(i)) + .toList() + : null, + reviews: json['review'] != null + ? (json['review'] as List).map((i) => Review.fromJson(i)).toList() + : null, + ); + } + + Map toJson() { + return { + 'id': id, + 'name': name, + 'description': description, + 'price': price, + 'image': image, + 'weight': weight, + 'dimensions': dimensions, + 'rating': rating, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'user': user?.map((v) => v.toJson()).toList(), + 'PharmacyProduct': pharmacyProduct?.map((v) => v.toJson()).toList(), + 'review': reviews?.map((v) => v.toJson()).toList(), + }; } +} + +class Order { + String id; + int userId; + int productId; + int quantity; + DateTime createdAt; + DateTime updatedAt; + ProductModels product; + UserModels user; + + Order({ + required this.id, + required this.userId, + required this.productId, + required this.quantity, + required this.createdAt, + required this.updatedAt, + required this.product, + required this.user, + }); + + factory Order.fromJson(Map json) { + return Order( + id: json['id'], + userId: json['userId'], + productId: json['productId'], + quantity: json['quantity'], + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + product: ProductModels.fromJson(json['product']), + user: UserModels.fromJson(json['user']), + ); + } + + Map toJson() { + return { + 'id': id, + 'userId': userId, + 'productId': productId, + 'quantity': quantity, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'product': product.toJson(), + 'user': user.toJson(), + }; + } +} + +class Review { + int id; + DateTime createdAt; + DateTime updatedAt; + String title; + String body; + double rating; + + Review({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.title, + required this.body, + required this.rating, + }); + + Review.fromJson(Map json) + : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), + updatedAt = DateTime.parse(json['updatedAt']), + title = json['title'], + body = json['body'], + rating = json['rating']; Map toJson() { - final Map data = {}; - data['id'] = id; - data['name'] = name; - data['description'] = description; - data['price'] = price; - data['stock'] = stock; - data['image'] = image; - data['weight'] = weight; - data['dimensions'] = dimensions; - data['rating'] = rating; - data['createdAt'] = createdAt; - data['updatedAt'] = updatedAt; - return data; + return { + 'id': id, + 'createdAt': createdAt.toIso8601String(), + 'updatedAt': updatedAt.toIso8601String(), + 'title': title, + 'body': body, + 'rating': rating, + }; } } diff --git a/lib/api/models/user_models.dart b/lib/api/models/user_models.dart index 0aaa4c5..bb46494 100644 --- a/lib/api/models/user_models.dart +++ b/lib/api/models/user_models.dart @@ -1,48 +1,76 @@ +import 'package:gohealth/api/models/product_models.dart'; + class UserModels { int? id; - String? createdAt; - String? updatedAt; + DateTime? createdAt; + DateTime? updatedAt; String? email; + String? password; String? name; String? avatar; - Map? product; String? bio; - String? role; + Role? role; + List? products; + List? reviews; + List? orders; UserModels({ this.id, this.createdAt, this.updatedAt, this.email, + this.password, this.name, - this.role, this.avatar, - this.product, this.bio, + this.role, + this.products, + this.reviews, + this.orders, }); - UserModels.fromJson(Map json) - : id = json['id'], - createdAt = json['createdAt'], - updatedAt = json['updatedAt'], - email = json['email'], - name = json['name'], - avatar = json['avatar'], - product = json['product'], - bio = json['bio'], - role = json['role']; + factory UserModels.fromJson(Map json) { + return UserModels( + id: json['id'], + createdAt: DateTime.parse(json['createdAt']), + updatedAt: DateTime.parse(json['updatedAt']), + email: json['email'], + password: json['password'], + name: json['name'], + avatar: json['avatar'], + bio: json['bio'], + role: + Role.values.firstWhere((e) => e.toString() == 'Role.' + json['role']), + products: json['products'] != null + ? (json['products'] as List) + .map((i) => ProductModels.fromJson(i)) + .toList() + : null, + reviews: json['reviews'] != null + ? (json['reviews'] as List).map((i) => Review.fromJson(i)).toList() + : null, + orders: json['orders'] != null + ? (json['orders'] as List).map((i) => Order.fromJson(i)).toList() + : null, + ); + } Map toJson() { - final Map data = {}; - data['id'] = id; - data['createdAt'] = createdAt; - data['updatedAt'] = updatedAt; - data['email'] = email; - data['name'] = name; - data['avatar'] = avatar; - data['product'] = product; - data['bio'] = bio; - data['role'] = role; - return data; + return { + 'id': id, + 'createdAt': createdAt?.toIso8601String(), + 'updatedAt': updatedAt?.toIso8601String(), + 'email': email, + 'password': password, + 'name': name, + 'avatar': avatar, + 'bio': bio, + 'role': role.toString().split('.').last, + 'products': products?.map((i) => i.toJson()).toList(), + 'reviews': reviews?.map((i) => i.toJson()).toList(), + 'orders': orders?.map((i) => i.toJson()).toList(), + }; } } + +enum Role { USER, ADMIN } diff --git a/lib/api/repositories/pharmacy_to_product.dart b/lib/api/repositories/pharmacy_to_product.dart new file mode 100644 index 0000000..db6103d --- /dev/null +++ b/lib/api/repositories/pharmacy_to_product.dart @@ -0,0 +1,22 @@ +import 'package:gohealth/api/interfaces/pharmacy_to_product.dart'; +import 'package:gohealth/api/services/http_client.dart'; + +class PharmacyUserRepository implements IPharmacyStockItem { + late HttpClient stockItemHttpClient; + + PharmacyUserRepository() { + stockItemHttpClient = HttpClient(); + } + + getAvailableQuantity(int pharmacy, int product) async { + var response = + await stockItemHttpClient.client.get('/stock/$pharmacy/$product'); + + if (response.statusCode == 200) { + var data = response.data; + return data['availableQuantity']['quantity']; + } else { + return 0; + } + } +} diff --git a/lib/api/repositories/product_repository.dart b/lib/api/repositories/product_repository.dart index d7d7e6f..dbd5d65 100644 --- a/lib/api/repositories/product_repository.dart +++ b/lib/api/repositories/product_repository.dart @@ -1,4 +1,5 @@ import 'package:gohealth/api/interfaces/product_interface.dart'; +import 'package:gohealth/api/models/pharmacy_to_product_model.dart'; import 'package:gohealth/api/models/product_models.dart'; import 'package:gohealth/api/services/http_client.dart'; @@ -60,4 +61,31 @@ class ProductRepository implements IProduct { return model; } + + Future> getQuantity( + int productId, int pharmacyId) async { + var response = await repositoryHttpClient.client + .get('/product/stock/$productId/$pharmacyId'); + + List model = []; + + for (var item in response.data['products']) { + model.add(PharmacyStockItem.fromJson(item)); + } + + return model; + } + + Future> getProducts(String searchText) async { + var response = + await repositoryHttpClient.client.post('/product/name/$searchText'); + + List model = []; + + for (var item in response.data['products']) { + model.add(ProductModels.fromJson(item)); + } + + return model; + } } diff --git a/lib/api/repositories/user_repository.dart b/lib/api/repositories/user_repository.dart index a9ed3aa..17b31d9 100644 --- a/lib/api/repositories/user_repository.dart +++ b/lib/api/repositories/user_repository.dart @@ -94,8 +94,23 @@ class UserRepository implements IUser { if (response.data['user'] == null) { return UserModels(); } - + UserModels model = UserModels.fromJson(response.data['user']); return model; } + + buy( + {required int id, + required int productId, + required int quantity, + required int pharid}) async { + var response = await userNetworkClient.client.post('/user/product/buy', data: { + 'id': id, + 'prodid': productId, + 'pharid': pharid, + 'quantity': quantity + }); + + return response.data; + } } diff --git a/lib/api/services/shared_local_storage_service.dart b/lib/api/services/shared_local_storage_service.dart index 78ab124..ea85729 100644 --- a/lib/api/services/shared_local_storage_service.dart +++ b/lib/api/services/shared_local_storage_service.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'package:gohealth/api/interfaces/local_storage_interface.dart'; +import 'package:gohealth/api/models/pharmacy_model.dart'; import 'package:gohealth/api/models/product_models.dart'; import 'package:gohealth/api/models/user_models.dart'; +import 'package:gohealth/api/repositories/pharmacy_to_product.dart'; import 'package:shared_preferences/shared_preferences.dart'; class SharedLocalStorageService implements ILocalStorage { @@ -21,14 +23,25 @@ class SharedLocalStorageService implements ILocalStorage { var user = UserModels( id: shared.getInt('id'), - createdAt: shared.getString('createdAt'), - updatedAt: shared.getString('updatedAt'), + createdAt: shared.getString('createdAt') != null + ? DateTime.parse(shared.getString('createdAt')!) + : null, + updatedAt: shared.getString('updatedAt') != null + ? DateTime.parse(shared.getString('updatedAt')!) + : null, email: shared.getString('email'), name: shared.getString('name'), - product: product, + products: product != null + ? (product['products'] as List) + .map((e) => ProductModels.fromJson(e)) + .toList() + : null, avatar: shared.getString('avatar'), bio: shared.getString('bio'), - role: shared.getString('role'), + role: shared.getString('role') != null + ? Role.values.firstWhere( + (e) => e.toString() == 'Role.' + shared.getString('role')!) + : null, ); return user; @@ -46,7 +59,7 @@ class SharedLocalStorageService implements ILocalStorage { shared.setString(key, user.toJson()[key].toString()); } } - + return user; } @@ -79,68 +92,126 @@ class SharedLocalStorageService implements ILocalStorage { }); } - void saveProduct(ProductModels productModels) { + Future> fetchAllCartItems() { var shared = SharedPreferences.getInstance(); - shared.then((value) { - var products = value.getString('products'); - List list = []; + return shared.then((value) { + var products = value.getString('cart'); + List list = []; if (products != null) { var decoded = jsonDecode(products) as List; - list = decoded.map((e) => ProductModels.fromJson(e)).toList(); + list = decoded.map((e) => CartItem.fromJson(e)).toList(); } - list.add(productModels); - value.setString('products', jsonEncode(list)); + return list; }); } - Future> getAllProducts() { + Future> getProductsForCart() { var shared = SharedPreferences.getInstance(); return shared.then((value) { - var products = value.getString('products'); + var products = value.getString('cart'); + List list = []; if (products != null) { var decoded = jsonDecode(products) as List; - return decoded.map((e) => ProductModels.fromJson(e)).toList(); - } else { - throw Exception('No products found'); + list = decoded.map((e) => CartItem.fromJson(e)).toList(); } + return list.map((e) => e.product).toList(); }); } - getSelectedItems() { + void removeProductTocart({required int id}) { var shared = SharedPreferences.getInstance(); - return shared.then((value) { - var products = value.getString('products'); + shared.then((value) { + var products = value.getString('cart'); + List list = []; if (products != null) { var decoded = jsonDecode(products) as List; - return decoded.map((e) => e.toString()).toList(); - } else { - throw Exception('No products found'); + list = decoded.map((e) => CartItem.fromJson(e)).toList(); } + list.removeWhere((element) => element.product.id == id); + value.setString('cart', jsonEncode(list.map((e) => e.toJson()).toList())); }); } - void removeProduct(int? id) { + void clearCart() { var shared = SharedPreferences.getInstance(); shared.then((value) { - var products = value.getString('products'); - List list = []; + value.remove('cart'); + }); + } + + addProductToCart( + {required ProductModels product, + required PharmacyModels pharmacy, + required int quantity}) { + final _repositoryStock = PharmacyUserRepository(); + + var shared = SharedPreferences.getInstance(); + shared.then((value) async { + var products = value.getString('cart'); + List list = []; if (products != null) { var decoded = jsonDecode(products) as List; - list = decoded.map((e) => ProductModels.fromJson(e)).toList(); + list = decoded.map((e) => CartItem.fromJson(e)).toList(); + } + + // Obter a quantidade disponível do produto na farmácia + int availableQuantity = (await _repositoryStock.getAvailableQuantity( + pharmacy.id!, + product.id!, + )); + + // Verificar se a quantidade solicitada é maior que a disponível + if (quantity > availableQuantity || availableQuantity <= 0) { + throw Exception('Quantidade indisponível'); } - list.removeWhere((element) => element.id == id); - value.setString('products', jsonEncode(list)); + + // Verificar se o produto já está no carrinho + + var index = list.indexWhere((element) => + element.product.id == product.id && + element.pharmacy.id == pharmacy.id); + + if (index >= 0) { + list[index].quantity += quantity; + } else { + list.add(new CartItem( + product: product, pharmacy: pharmacy, quantity: quantity)); + } + + value.setString('cart', jsonEncode(list.map((e) => e.toJson()).toList())); }); } +} - void clearCart() { - var shared = SharedPreferences.getInstance(); - shared.then((value) { - value.remove('products'); - }); +class CartItem { + final ProductModels product; + final PharmacyModels pharmacy; + int quantity; + + CartItem({ + required this.product, + required this.pharmacy, + required this.quantity, + }); + + Map toJson() { + return { + 'product': product.toJson(), + 'pharmacy': pharmacy.toJson(), + 'quantity': quantity, + }; } - getProductsReserveList(int? id) { - + factory CartItem.fromJson(Map json) { + return CartItem( + product: ProductModels.fromJson(json['product']), + pharmacy: PharmacyModels.fromJson(json['pharmacy']), + quantity: json['quantity'], + ); + } + + @override + String toString() { + return 'CartItem{product: $product, pharmacy: $pharmacy, quantity: $quantity}'; } } diff --git a/lib/api/services/socket_client_service.dart b/lib/api/services/socket_client_service.dart index b51a4ef..fb46726 100644 --- a/lib/api/services/socket_client_service.dart +++ b/lib/api/services/socket_client_service.dart @@ -31,7 +31,7 @@ Future initializeService() async { androidConfiguration: AndroidConfiguration( autoStart: true, onStart: onStart, - isForegroundMode: true, + isForegroundMode: false, autoStartOnBoot: true, ), ); diff --git a/lib/src/app/home/cart/cart_page.dart b/lib/src/app/home/cart/cart_page.dart index 3ca651d..d03e401 100644 --- a/lib/src/app/home/cart/cart_page.dart +++ b/lib/src/app/home/cart/cart_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:gohealth/api/models/product_models.dart'; +import 'package:gohealth/api/repositories/user_repository.dart'; import 'package:gohealth/api/services/shared_local_storage_service.dart'; import 'package:gohealth/src/app/home/home_page.dart'; import 'package:gohealth/src/app/sessions/products/product_page.dart'; @@ -7,13 +8,16 @@ import 'package:gohealth/src/components/header_bar.dart'; import 'package:gohealth/src/components/side_menu.dart'; class CartPage extends StatefulWidget { - const CartPage({ Key? key }) : super(key: key); + const CartPage({Key? key}) : super(key: key); @override _CartPageState createState() => _CartPageState(); } class _CartPageState extends State { + final _repositoryUser = UserRepository(); + + final _sharedLocalStorageService = SharedLocalStorageService(); @override Widget build(BuildContext context) { @@ -23,7 +27,7 @@ class _CartPageState extends State { bottomNavigationBar: Padding( padding: const EdgeInsets.all(8.0), child: FutureBuilder>( - future: SharedLocalStorageService().getAllProducts(), + future: SharedLocalStorageService().getProductsForCart(), builder: (context, snapshot) { if (!snapshot.hasData || snapshot.data!.isEmpty) { return const SizedBox.shrink(); @@ -36,7 +40,30 @@ class _CartPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ElevatedButton( - onPressed: () { + onPressed: () async { + final cartItens = + await _sharedLocalStorageService.fetchAllCartItems(); + + final user = + await _sharedLocalStorageService.getProfile(); + + if (user != null && user.id != null) { + for (var item in cartItens) { + await _repositoryUser.buy( + id: user.id!, + productId: item.product.id!, + quantity: item.quantity, + pharid: item.pharmacy.id!); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erro ao obter perfil do usuário'), + backgroundColor: Colors.red, + duration: Duration(seconds: 2), + ), + ); + } SharedLocalStorageService().clearCart(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( @@ -45,7 +72,7 @@ class _CartPageState extends State { duration: Duration(seconds: 2), ), ); - Navigator.push( + Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const Homepage())); @@ -74,11 +101,12 @@ class _CartPageState extends State { ), ), body: FutureBuilder>( - future: SharedLocalStorageService().getAllProducts(), + future: SharedLocalStorageService().getProductsForCart(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + print(snapshot.data); return const Center(child: Text('Nenhum produto no carrinho')); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); @@ -98,24 +126,28 @@ class _CartPageState extends State { ); }, child: Card( - margin: const EdgeInsets.all(10.0), - child: ListTile( - leading: Image.network(product.image ?? "https://via.placeholder.com/150", width: 50, height: 50, fit: BoxFit.cover), - title: Text(product.name!), + margin: const EdgeInsets.all(10.0), + child: ListTile( + leading: Image.network( + product.image ?? "https://via.placeholder.com/150", + width: 50, + height: 50, + fit: BoxFit.cover), + title: Text(product.name!), subtitle: Text( 'Preço: R\$${product.price!.toStringAsFixed(2)}'), - trailing: IconButton( + trailing: IconButton( icon: const Icon(Icons.delete), onPressed: () { setState(() { snapshot.data!.removeAt(index); SharedLocalStorageService() - .removeProduct(product.id); + .removeProductTocart(id: product.id!); }); }, + ), ), ), - ), ); }, ); @@ -124,4 +156,4 @@ class _CartPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/app/login/login_page.dart b/lib/src/app/login/login_page.dart index 442a514..c554eb1 100644 --- a/lib/src/app/login/login_page.dart +++ b/lib/src/app/login/login_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gohealth/api/layout/user_view_model.dart'; -import 'package:gohealth/api/models/user_models.dart'; import 'package:gohealth/src/app/login/login_controller.dart'; import 'package:gohealth/src/app/register/register_page.dart'; import 'package:gohealth/api/repositories/user_repository.dart'; @@ -179,7 +178,12 @@ class LoginPageState extends State { .login(_emailController.text, _passwordController.text) .then((value) => value) .catchError((error) { - return Future.value(UserModels()); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Erro ao fazer login'), + backgroundColor: Colors.redAccent, + ), + ); }); if (user.id != null) { diff --git a/lib/src/app/sessions/products/product_details.dart b/lib/src/app/sessions/products/product_details.dart index fbe58c6..a1fa0cd 100644 --- a/lib/src/app/sessions/products/product_details.dart +++ b/lib/src/app/sessions/products/product_details.dart @@ -11,7 +11,6 @@ class ProductDetailsPage extends StatefulWidget { final ProductModels productModels; } - class ProductDetails extends State { @override Widget build(BuildContext context) { @@ -30,6 +29,107 @@ class ProductDetails extends State { style: TextStyle(fontSize: 20, color: Colors.grey), ), SizedBox(height: 16), + Text( + 'Avaliações', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + SizedBox(height: 8), + widget.productModels.reviews != null && + widget.productModels.reviews!.isNotEmpty + ? Column( + children: [ + ...widget.productModels.reviews!.take(2).map((review) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + review.title, + style: TextStyle(fontSize: 16), + ), + ), + SizedBox(width: 8), + Icon(Icons.star, color: Colors.yellow), + Row( + children: + List.generate(review.rating.toInt(), (index) { + return Icon(Icons.star, color: Colors.yellow); + }), + ), + Expanded( + child: Text( + review.body, + style: TextStyle(fontSize: 16), + )) + ], + ), + ); + }).toList(), + if (widget.productModels.reviews!.length > 2) + TextButton( + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('Todas as Avaliações'), + content: SingleChildScrollView( + child: ListBody( + children: widget.productModels.reviews! + .map((review) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 4.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + review.title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold), + ), + SizedBox(height: 4), + Row( + children: List.generate( + review.rating.toInt(), + (index) { + return Icon(Icons.star, + color: Colors.yellow); + }), + ), + SizedBox(height: 4), + Text( + review.body, + style: TextStyle(fontSize: 16), + ), + Divider(), + ], + ), + ); + }).toList(), + ), + ), + actions: [ + TextButton( + child: Text('Fechar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + child: Text('Ver mais'), + ), + ], + ) + : Container() ], ), ); diff --git a/lib/src/app/sessions/products/product_page.dart b/lib/src/app/sessions/products/product_page.dart index 8f2d814..e7a598e 100644 --- a/lib/src/app/sessions/products/product_page.dart +++ b/lib/src/app/sessions/products/product_page.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:gohealth/api/models/pharmacy_model.dart'; import 'package:gohealth/api/models/product_models.dart'; +import 'package:gohealth/api/repositories/pharmacy_repository.dart'; import 'package:gohealth/api/services/shared_local_storage_service.dart'; import 'image_carousel.dart'; import 'product_details.dart'; @@ -15,6 +17,20 @@ class ProductPage extends StatefulWidget { } class ProductState extends State { + final _repository = PharmacyRepository(); + + List pharmacies = []; + + Future> _fetchPharmacies() async { + pharmacies.clear(); + for (var i in widget.productModels.pharmacyProduct!) { + PharmacyModels pharmacy = + await _repository.getPharmacyById(i.pharmacyId!); + pharmacies.add(pharmacy); + } + return pharmacies; + } + @override void initState() { super.initState(); @@ -28,7 +44,10 @@ class ProductState extends State { actions: [ IconButton( icon: Icon(Icons.more_vert), - onPressed: () {}, + onPressed: () { + // Ação de criar uma avaliação + + }, ), ], ), @@ -46,16 +65,98 @@ class ProductState extends State { padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: () { - // Salvar os dados do produto no carrinho atráves do storage - SharedLocalStorageService().saveProduct(widget.productModels); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Produto adicionado ao carrinho'), - backgroundColor: Colors.green, - duration: Duration(seconds: 2), - ), + showDialog( + context: context, + builder: (BuildContext context) { + return FutureBuilder>( + future: _fetchPharmacies(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return AlertDialog( + title: Text('Erro'), + content: Text( + 'Erro ao carregar farmácias: ${snapshot.error}'), + actions: [ + TextButton( + child: Text('Fechar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } else { + return AlertDialog( + title: Text('Escolha uma farmácia'), + content: SingleChildScrollView( + child: ListBody( + children: snapshot.data!.map((pharmacy) { + return ListTile( + leading: CircleAvatar( + backgroundImage: NetworkImage(pharmacy + .image!), // Supondo que a URL da foto esteja em pharmacy.photoUrl + backgroundColor: Colors.transparent, + ), + title: Text( + pharmacy.name!, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + subtitle: Text('Telefone: ${pharmacy.phone}'), + onTap: () async { + // Ação ao selecionar a farmácia + Navigator.of(context).pop(); + + try { + SharedLocalStorageService() + .addProductToCart( + product: widget.productModels, + pharmacy: pharmacy, + quantity: 1, + ) + .toString(); + + final product = widget.productModels.name; + + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + '$product adicionado ao carrinho'), + backgroundColor: Colors.green, + ), + ); + } catch (e) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Erro ao adicionar ao carrinho: $e'), + backgroundColor: Colors.red, + ), + ); + } + }); + }).toList(), + ), + ), + actions: [ + TextButton( + child: Text('Cancelar'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + } + }, + ); + }, ); - Navigator.pop(context); }, child: Text('Adicionar ao carrinho'), style: ElevatedButton.styleFrom( diff --git a/lib/src/app/sessions/products/products_list_page.dart b/lib/src/app/sessions/products/products_list_page.dart index c98be32..58a7d38 100644 --- a/lib/src/app/sessions/products/products_list_page.dart +++ b/lib/src/app/sessions/products/products_list_page.dart @@ -1,8 +1,14 @@ -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:gohealth/api/repositories/product_repository.dart'; +import 'package:gohealth/src/app/home/home_page.dart'; +import 'package:gohealth/src/app/sessions/products/product_page.dart'; +import 'package:gohealth/src/components/header_bar.dart'; +import 'package:gohealth/src/components/side_menu.dart'; class ProductsListPage extends StatefulWidget { - const ProductsListPage({ Key? key, required this.searchText}) : super(key: key); + const ProductsListPage({Key? key, required this.searchText}) + : super(key: key); @override _ProductsListPageState createState() => _ProductsListPageState(); @@ -11,11 +17,80 @@ class ProductsListPage extends StatefulWidget { } class _ProductsListPageState extends State { - final _repository = ProductRepository(); @override Widget build(BuildContext context) { - return Container(); + return Scaffold( + appBar: const HeaderBarState(), + drawer: const SideMenu(), + body: FutureBuilder( + future: _repository.getProducts(widget.searchText), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error, color: Colors.red, size: 64), + SizedBox(height: 16), + Text( + 'Erro: ${snapshot.error}', + style: TextStyle(fontSize: 18, color: Colors.red), + textAlign: TextAlign.center, + ), + ], + ), + ); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + SchedulerBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Nenhum produto encontrado'), + duration: Duration(seconds: 3), + backgroundColor: Colors.orange, + ), + ); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const Homepage(), + ), + ); + }); + return SizedBox.shrink(); + } else { + return ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + final product = snapshot.data![index]; + return GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ProductPage(productModels: snapshot.data![index]), + ), + ); + }, + child: Card( + child: ListTile( + leading: Image.network( + product.image ?? 'https://via.placeholder.com/150', + width: 50, + ), + title: Text(product.name!), + subtitle: Text('R\$ ${product.price.toString()}'), + ), + ), + ); + }, + ); + } + }, + )); } -} \ No newline at end of file +} diff --git a/lib/src/app/splash_page.dart b/lib/src/app/splash_page.dart index a682c75..0f53a26 100644 --- a/lib/src/app/splash_page.dart +++ b/lib/src/app/splash_page.dart @@ -29,12 +29,12 @@ class SplashPageState extends State { bool isLogged = await userRepository.repository .checkToken() - .timeout(const Duration(seconds: 5), onTimeout: () => false); + .timeout(const Duration(seconds: 0), onTimeout: () => false); bool isFirstSession = await userRepository.repository.doesUserHaveProduct(id); - Future.delayed(const Duration(seconds: 2), () { + Future.delayed(const Duration(seconds: 0), () { if (isLogged) { if (isFirstSession) { Navigator.push( diff --git a/lib/src/components/ProductReserve.dart b/lib/src/components/ProductReserve.dart index 5701cb1..4001337 100644 --- a/lib/src/components/ProductReserve.dart +++ b/lib/src/components/ProductReserve.dart @@ -80,7 +80,7 @@ class _ProductReserveState extends State { setState(() { snapshot.data!.removeAt(index); SharedLocalStorageService() - .removeProduct(product.id); + .removeProductTocart(id: product.id!); }); }, ), diff --git a/lib/src/components/banner.dart b/lib/src/components/banner.dart index f23d41c..bca0ebb 100644 --- a/lib/src/components/banner.dart +++ b/lib/src/components/banner.dart @@ -40,8 +40,9 @@ class _BannerComponentState extends State { } else if (snapshot.hasError) { return Text('Erro: ${snapshot.error}'); } else { - if (snapshot.data!.isEmpty) { - return Container(); + if (!snapshot.hasData || snapshot.data!.isEmpty) { + return const Center( + child: Text('Nenhum produto encontrado.')); } else { return ListView.builder( scrollDirection: Axis.horizontal, @@ -54,7 +55,6 @@ class _BannerComponentState extends State { Navigator.push( context, MaterialPageRoute( - // ! builder: (context) => ProductPage(productId: snapshot.data![index].id), builder: (context) => ProductPage( productModels: snapshot.data![index])), ); diff --git a/lib/src/components/header_bar.dart b/lib/src/components/header_bar.dart index e220a48..fde6cb1 100644 --- a/lib/src/components/header_bar.dart +++ b/lib/src/components/header_bar.dart @@ -33,7 +33,7 @@ class _HeaderBarState extends State { setState(() { name = user?.name ?? ''; profile = user; - productLength = user?.product?.length.toString() ?? ''; + productLength = user?.products?.length.toString() ?? ''; }); }); } @@ -82,6 +82,34 @@ class _HeaderBarState extends State { style: const TextStyle( color: Colors.white), // Define a cor do texto onSubmitted: (value) { + if (value.isEmpty) { + return; + } + + if (value.length < 3) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Digite pelo menos 3 caracteres'), + backgroundColor: Colors.red, + duration: Duration(seconds: 2), + ), + ); + return; + } + + if (value.contains( + RegExp(r'[!@#<>?":_`~;[\]\\|=+)(*&^%0-9-]'))) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Caracteres especiais não são permitidos'), + backgroundColor: Colors.red, + duration: Duration(seconds: 2), + ), + ); + return; + } + Navigator.push( context, MaterialPageRoute( @@ -124,7 +152,7 @@ class _HeaderBarState extends State { right: 0, child: Container( padding: EdgeInsets.all(0.7), - decoration: (profile?.product == true) + decoration: (profile?.products == true) ? BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(6), diff --git a/lib/src/components/pharmacy.dart b/lib/src/components/pharmacy.dart index 509d5bc..d295e5c 100644 --- a/lib/src/components/pharmacy.dart +++ b/lib/src/components/pharmacy.dart @@ -1,4 +1,3 @@ -import 'dart:math'; import 'package:flutter/material.dart'; import 'package:gohealth/api/layout/pharmacy_view_model.dart'; @@ -48,10 +47,9 @@ class PharmacyComponent extends State { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Column( - children: [ + children: [ CircleAvatar( - backgroundColor: Colors.primaries[ - Random().nextInt(Colors.primaries.length)], + backgroundColor: Colors.white, radius: 50, child: snapshot.data![index].image != null ? ClipOval( diff --git a/lib/src/components/side_menu.dart b/lib/src/components/side_menu.dart index e5f2fb2..7e121cc 100644 --- a/lib/src/components/side_menu.dart +++ b/lib/src/components/side_menu.dart @@ -100,7 +100,6 @@ class SideMenuState extends State { UserRepository prefs = UserViewModel(UserRepository()).repository; await prefs.logout(); - _repository.clearProfile(); Navigator.push( context, MaterialPageRoute(