diff --git a/assets/icons/empty-star.svg b/assets/icons/empty-star.svg new file mode 100644 index 0000000..35785c4 --- /dev/null +++ b/assets/icons/empty-star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/half-star.svg b/assets/icons/half-star.svg new file mode 100644 index 0000000..a22bcee --- /dev/null +++ b/assets/icons/half-star.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/icons/heart.svg b/assets/icons/heart.svg index ddfb53e..4915c98 100644 --- a/assets/icons/heart.svg +++ b/assets/icons/heart.svg @@ -1,3 +1,3 @@ - - + + diff --git a/assets/icons/star.svg b/assets/icons/star.svg new file mode 100644 index 0000000..77a992f --- /dev/null +++ b/assets/icons/star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/models/product.dart b/lib/models/product.dart index df75e1e..f024e27 100644 --- a/lib/models/product.dart +++ b/lib/models/product.dart @@ -1,3 +1,5 @@ +import 'dart:ffi'; + class Product { Product({ required this.id, @@ -5,8 +7,10 @@ class Product { required this.url, required this.imageUrls, required this.rating, - required this.price -}); + required this.price, + required this.description, + required this.wasOpened, + }); String id; String name; @@ -14,6 +18,8 @@ class Product { List imageUrls; double rating; double price; + String description; + bool wasOpened; Product.fromJson(Map json) : id = json['id'] as String, @@ -21,5 +27,7 @@ class Product { url = json['url'] as String, imageUrls = json['imageUrls'] as List, rating = json['rating'] as double, - price = json['name'] as double; + price = json['name'] as double, + description = json['description'] as String, + wasOpened = json['wasOpened'] as bool; } \ No newline at end of file diff --git a/lib/network/product_service.dart b/lib/network/product_service.dart new file mode 100644 index 0000000..54d8c93 --- /dev/null +++ b/lib/network/product_service.dart @@ -0,0 +1,31 @@ +import 'package:graphql/client.dart'; +import 'package:shopping_assistant_mobile_client/models/product.dart'; +import 'package:shopping_assistant_mobile_client/network/api_client.dart'; + +class ProductService { + final ApiClient client = ApiClient(); + + Future addProductToPersonalWishlist(Product product, String wishlisId) async { + final options = MutationOptions( + document: gql(''' + mutation AddProductToPersonalWishlist(\$wishlistId: String!, \$dto: ProductCreateDtoInput!) { + addProductToPersonalWishlist(wishlistId: \$wishlistId, dto: \$dto) { + + } + } + '''), + variables: {'wishlistId': wishlisId, + 'dto': { + 'wasOpened': product.wasOpened, + 'url': product.url, + 'rating': product.rating, + 'price': product.price, + 'name': product.name, + 'imagesUrls': product.imageUrls, + 'description': product.description, + }}, + ); + + final result = await client.mutate(options); + } +} \ No newline at end of file diff --git a/lib/screens/cards.dart b/lib/screens/cards.dart index 542a20c..8264211 100644 --- a/lib/screens/cards.dart +++ b/lib/screens/cards.dart @@ -1,18 +1,86 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:shopping_assistant_mobile_client/models/product.dart'; +import 'package:shopping_assistant_mobile_client/network/product_service.dart'; + class Cards extends StatefulWidget { + final String wishlistId; final String wishlistName; - List products = []; + List inputProducts = []; + List products = []; - Cards({required this.wishlistName, required this.products}); + Cards({required this.wishlistName, required this.inputProducts, required this.wishlistId}); @override _CardsState createState() => _CardsState(); } class _CardsState extends State { + int currentProduct = 0; + ProductService productService = ProductService(); + List cart = []; + + @override + void initState(){ + for(String productName in this.widget.inputProducts){ + this.widget.products.add( + Product( + id: '', + name: productName, + url: 'link', + imageUrls: [], + rating: 0.0, + price: 0.0, + description: '', + wasOpened: false, + ) + ); + } + } + + Widget buildRatingStars(double rating) { + int whole = rating.floor(); + double fractal = rating - whole; + + List stars = []; + + for (int i = 0; i < 5; i++) { + if (i < whole) { + // Whole star + stars.add(SvgPicture.asset( + 'assets/icons/star.svg', + width: 19, + height: 19, + )); + } else if (fractal != 0.0) { + // Half star + stars.add(SvgPicture.asset( + 'assets/icons/half-star.svg', + width: 19, + height: 19, + )); + fractal -= fractal; + } else { + // Empty star + stars.add(SvgPicture.asset( + 'assets/icons/empty-star.svg', + width: 19, + height: 19, + )); + } + } + + return Row( + children: stars, + ); + } + @override Widget build(BuildContext context) { + bool hasCards = widget.products.isNotEmpty; + bool isLastProduct = currentProduct == widget.products.length; + return Scaffold( appBar: AppBar( title: Text(widget.wishlistName), @@ -21,33 +89,177 @@ class _CardsState extends State { body: Center( child: Stack( children: [ - // Back Card with increased rotation effect - Positioned( - left: 10, // Adjust the left position as needed - child: Transform.rotate( - angle: -5 * 3.1415926535 / 180, // Rotation angle - child: Container( - width: 400, // Increased width - height: 600, // Increased height - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - border: Border.all( - color: Color(0xFF009FFF), + if (currentProduct < widget.products.length - 1) + Positioned( + child: Transform.rotate( + angle: -5 * 3.1415926535 / 180, + child: Container( + width: 300, + height: 600, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + border: Border.all( + color: Color(0xFF009FFF), + ), + boxShadow: [ + BoxShadow( + color: Color(0xFF0165FF), + blurRadius: 8.0, + spreadRadius: 0.0, + offset: Offset(0, 0), + ), + ], ), ), ), ), - ), - // Main Card - Container( - width: 300, // Set the width of the main card - height: 500, // Set the height of the main card - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Colors.white, - border: Border.all( - color: Color(0xFF009FFF), + Positioned( + child: Container( + width: 300, + height: 600, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + border: Border.all( + color: Color(0xFF009FFF), + ), + boxShadow: [ + BoxShadow( + color: Color(0xFF0165FF), + blurRadius: 8.0, + spreadRadius: 0.0, + offset: Offset(0, 0), + ), + ], + ), + child: Column( + children: [ + Container( + height: 300, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/images/product_image_placeholder.jpg'), // Replace with your image path + fit: BoxFit.cover, + ), + ), + ), + SizedBox(height: 10), + Text( + currentProduct < widget.products.length + ? widget.products[currentProduct].name + : "The cards ended.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w600, + ), + ), + if (currentProduct == widget.products.length) + Text( + "Swipe right to show more or left to exit.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 17, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 10), + if (!isLastProduct) + Column( + children: [ + Text( + currentProduct < widget.products.length + ? widget.products[currentProduct].description + : "Product description not available.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buildRatingStars(currentProduct < widget.products.length + ? widget.products[currentProduct].rating + : 0.0), + SizedBox(width: 20), + Text( + '\$${currentProduct < widget.products.length ? widget.products[currentProduct].price : "-"}', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ], + ), + SizedBox(height: 50), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + icon: currentProduct < widget.products.length + ? SvgPicture.asset( + 'assets/icons/x.svg', + width: 30, + height: 30, + ) + : SvgPicture.asset( + 'assets/icons/exit-cards.svg', + width: 30, + height: 30, + ), + onPressed: () { + if (currentProduct < widget.products.length) { + showNextProduct(); + } else { + Navigator.of(context).pop(); + } + }, + ), + IconButton( + icon: SvgPicture.asset( + 'assets/icons/back.svg', + width: 30, + height: 30, + ), + onPressed: () { + showPreviousProduct(); + }, + ), + IconButton( + icon: currentProduct < widget.products.length + ? SvgPicture.asset( + 'assets/icons/heart.svg', + width: 30, + height: 30, + color: Colors.blue, + ) + : SvgPicture.asset( + 'assets/icons/add-products.svg', + width: 30, + height: 30, + ), + onPressed: () async { + if (currentProduct < widget.products.length) { + if (!cart.contains(widget.products[currentProduct])) { + await productService.addProductToPersonalWishlist( + widget.products[currentProduct], + widget.wishlistId, + ); + cart.add(widget.products[currentProduct]); + } + showNextProduct(); + } + }, + ), + ], + ), + ], ), ), ), @@ -56,5 +268,20 @@ class _CardsState extends State { ), ); } -} + void showNextProduct() { + setState(() { + if(currentProduct < widget.products.length) { + ++currentProduct; + } + }); + } + + void showPreviousProduct() { + setState(() { + if(currentProduct > 0 ) { + --currentProduct; + } + }); + } +} diff --git a/lib/screens/chat.dart b/lib/screens/chat.dart index b8f5f9a..6fae431 100644 --- a/lib/screens/chat.dart +++ b/lib/screens/chat.dart @@ -206,10 +206,11 @@ class ChatScreenState extends State { setState(() { if(_searchService.checkerForProduct()){ + messages.removeLast(); Navigator.push( context, MaterialPageRoute( - builder: (context) => Cards(wishlistName: this.widget.wishlistName, products: this._searchService.products,), + builder: (context) => Cards(wishlistName: this.widget.wishlistName, inputProducts: this._searchService.products, wishlistId: this.widget.wishlistId,), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 86567a6..19bca57 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -244,10 +244,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" http_parser: dependency: transitive description: @@ -396,10 +396,10 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" platform: dependency: transitive description: @@ -468,10 +468,10 @@ packages: dependency: transitive description: name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" shared_preferences_windows: dependency: transitive description: @@ -569,10 +569,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" url_launcher_macos: dependency: transitive description: @@ -593,18 +593,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "7286aec002c8feecc338cc33269e96b73955ab227456e9fb2a91f7fab8a358e9" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" uuid: dependency: "direct main" description: @@ -681,10 +681,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.5.0" yaml: dependency: transitive description: @@ -694,5 +694,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index cb6ac6d..42a23d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -74,6 +74,14 @@ flutter: - assets/icons/wishlists.svg - assets/icons/start-new-search.svg - assets/icons/settings.svg + - assets/icons/heart.svg + - assets/icons/x.svg + - assets/icons/exit-cards.svg + - assets/icons/back.svg + - assets/icons/add-products.svg + - assets/icons/star.svg + - assets/icons/half-star.svg + - assets/icons/empty-star.svg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware