From 157c1a6e966f764c202f747f30a3bfc6aac83d67 Mon Sep 17 00:00:00 2001 From: Anthony Date: Sat, 29 Jan 2022 12:52:05 +0000 Subject: [PATCH] v5.7.0 - updates --- LabelStoreMax/CHANGELOG.md | 10 + LabelStoreMax/README.md | 2 +- LabelStoreMax/lang/de.json | 11 +- LabelStoreMax/lang/en.json | 11 +- LabelStoreMax/lang/es.json | 11 +- LabelStoreMax/lang/fr.json | 11 +- LabelStoreMax/lang/hi.json | 11 +- LabelStoreMax/lang/it.json | 11 +- LabelStoreMax/lang/nl.json | 11 +- LabelStoreMax/lang/pt.json | 11 +- LabelStoreMax/lang/tr.json | 11 +- LabelStoreMax/lang/zh.json | 11 +- .../controllers/leave_review_controller.dart | 21 + .../product_detail_controller.dart | 99 ++++- .../product_loader_controller.dart | 2 + .../product_reviews_controller.dart | 22 + .../product_reviews_loader_controller.dart | 34 ++ .../enums/wishlist_action_enums.dart | 15 + LabelStoreMax/lib/bootstrap/helpers.dart | 16 +- .../lib/resources/pages/account_detail.dart | 3 +- .../resources/pages/account_order_detail.dart | 28 +- .../resources/pages/customer_countries.dart | 2 +- .../resources/pages/leave_review_page.dart | 193 +++++++++ .../lib/resources/pages/product_detail.dart | 408 +++--------------- .../resources/pages/product_reviews_page.dart | 213 +++++++++ .../resources/widgets/app_loader_widget.dart | 3 +- .../lib/resources/widgets/buttons.dart | 3 +- .../checkout_coupon_amount_widget.dart | 2 +- .../checkout_shipping_type_widget.dart | 12 +- .../resources/widgets/compo_home_widget.dart | 21 +- .../widgets/future_build_widget.dart | 43 ++ .../resources/widgets/home_drawer_widget.dart | 2 + .../resources/widgets/notic_home_widget.dart | 2 +- .../resources/widgets/notic_theme_widget.dart | 37 +- .../widgets/product_detail_body_widget.dart | 65 +++ .../product_detail_description_widget.dart | 90 ++++ .../product_detail_footer_actions_widget.dart | 128 ++++++ .../widgets/product_detail_header_widget.dart | 73 ++++ .../product_detail_image_swiper_widget.dart | 44 ++ ...roduct_detail_related_products_widget.dart | 80 ++++ .../product_detail_review_tile_widget.dart | 90 ++++ .../product_detail_reviews_widget.dart | 152 +++++++ .../widgets/product_detail_upsell_widget.dart | 170 ++++++++ .../product_review_item_container_widget.dart | 65 +++ .../lib/resources/widgets/woosignal_ui.dart | 8 +- LabelStoreMax/lib/routes/router.dart | 8 + LabelStoreMax/pubspec.lock | 230 +++++++++- LabelStoreMax/pubspec.yaml | 15 +- README.md | 2 +- 49 files changed, 2072 insertions(+), 451 deletions(-) create mode 100644 LabelStoreMax/lib/app/controllers/leave_review_controller.dart create mode 100644 LabelStoreMax/lib/app/controllers/product_reviews_controller.dart create mode 100644 LabelStoreMax/lib/app/controllers/product_reviews_loader_controller.dart create mode 100644 LabelStoreMax/lib/bootstrap/enums/wishlist_action_enums.dart create mode 100644 LabelStoreMax/lib/resources/pages/leave_review_page.dart create mode 100644 LabelStoreMax/lib/resources/pages/product_reviews_page.dart create mode 100644 LabelStoreMax/lib/resources/widgets/future_build_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_body_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_description_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_footer_actions_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_header_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_image_swiper_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_related_products_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_review_tile_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_reviews_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_detail_upsell_widget.dart create mode 100644 LabelStoreMax/lib/resources/widgets/product_review_item_container_widget.dart diff --git a/LabelStoreMax/CHANGELOG.md b/LabelStoreMax/CHANGELOG.md index 7c65a56..bf38aa7 100644 --- a/LabelStoreMax/CHANGELOG.md +++ b/LabelStoreMax/CHANGELOG.md @@ -1,3 +1,13 @@ +## [5.7.0] - 2022-01-29 + +* Refactor product detail page +* View HTML in description on the product detail page +* Allow upsell products to be viewed on the Product detail page (if enabled) +* Allow related products to be viewed on the Product detail page (if enabled) +* Allow product reviews to be view on the product page (if enabled) +* Flutter format in /resources +* Pubspec.yaml dependency updates + ## [5.6.2] - 2022-01-07 * Fix null return in CheckoutShippingTypeWidget diff --git a/LabelStoreMax/README.md b/LabelStoreMax/README.md index 0faa092..3ec30ca 100644 --- a/LabelStoreMax/README.md +++ b/LabelStoreMax/README.md @@ -4,7 +4,7 @@ # WooCommerce App: Label StoreMax -### Label StoreMax - v5.6.2 +### Label StoreMax - v5.7.0 [Official WooSignal WooCommerce App](https://woosignal.com) diff --git a/LabelStoreMax/lang/de.json b/LabelStoreMax/lang/de.json index f1cd593..9557a13 100644 --- a/LabelStoreMax/lang/de.json +++ b/LabelStoreMax/lang/de.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Dieses Produkt wurde von Ihrer Wunschliste entfernt", "This product has been added to your wishlist": "Dieses Produkt wurde Ihrer Wunschliste hinzugefügt", "Spend a minimum of minimumAmount to redeem": "Verbringen Sie mindestens {{minimumAmount}} einlösen", - "Spend less than maximumAmount to redeem": "Geben Sie weniger aus als {{maximumAmount}} einlösen" + "Spend less than maximumAmount to redeem": "Geben Sie weniger aus als {{maximumAmount}} einlösen", + "Related products": "Verwandte Produkte", + "Reviews": "Bewertungen", + "There are no reviews yet.": "Es gibt noch keine Bewertungen.", + "More": "Mehr", + "You may also like": "Sie können auch mögen", + "Leave a review": "Hinterlassen Sie eine Bewertung", + "How would you rate": "Wie beurteilen Sie", + "Submit": "einreichen", + "Your review has been submitted": "Ihre Bewertung wurde übermittelt" } \ No newline at end of file diff --git a/LabelStoreMax/lang/en.json b/LabelStoreMax/lang/en.json index 1fc7819..6055b21 100644 --- a/LabelStoreMax/lang/en.json +++ b/LabelStoreMax/lang/en.json @@ -210,5 +210,14 @@ "Wishlist": "Wishlist", "No items found": "No items found", "This product has been removed from your wishlist": "This product has been removed from your wishlist", - "This product has been added to your wishlist": "This product has been added to your wishlist" + "This product has been added to your wishlist": "This product has been added to your wishlist", + "Related products": "Related products", + "Reviews": "Reviews", + "There are no reviews yet.": "There are no reviews yet.", + "More": "More", + "You may also like": "You may also like", + "Leave a review": "Leave a review", + "How would you rate": "How would you rate", + "Submit": "Submit", + "Your review has been submitted": "Your review has been submitted" } \ No newline at end of file diff --git a/LabelStoreMax/lang/es.json b/LabelStoreMax/lang/es.json index 9749517..62f3913 100644 --- a/LabelStoreMax/lang/es.json +++ b/LabelStoreMax/lang/es.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Este producto ha sido eliminado de tu lista de deseos.", "This product has been added to your wishlist": "Este producto ha sido añadido a su lista de deseos", "Spend a minimum of minimumAmount to redeem": "Gasta un mínimo de {{minimumAmount}} para redimir", - "Spend less than maximumAmount to redeem": "Gasta menos de {{maximumAmount}} para redimir" + "Spend less than maximumAmount to redeem": "Gasta menos de {{maximumAmount}} para redimir", + "Related products": "Productos relacionados", + "Reviews": "Reseñas", + "There are no reviews yet.": "Aún no hay reseñas.", + "More": "Más", + "You may also like": "También te puede interesar", + "Leave a review": "Dejar un comentario", + "How would you rate": "Cómo calificarías", + "Submit": "Entregar", + "Your review has been submitted": "Tu reseña ha sido enviada" } \ No newline at end of file diff --git a/LabelStoreMax/lang/fr.json b/LabelStoreMax/lang/fr.json index 509155c..c4751c4 100644 --- a/LabelStoreMax/lang/fr.json +++ b/LabelStoreMax/lang/fr.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Ce produit a été supprimé de votre liste de souhaits", "This product has been added to your wishlist": "Ce produit a été ajouté à votre liste de souhaits", "Spend a minimum of minimumAmount to redeem": "Dépensez un minimum de {{minimumAmount}} de racheter", - "Spend less than maximumAmount to redeem": "Dépensez moins de {{maximumAmount}} de racheter" + "Spend less than maximumAmount to redeem": "Dépensez moins de {{maximumAmount}} de racheter", + "Related products": "Produits connexes", + "Reviews": "Commentaires", + "There are no reviews yet.": "Il n'y a pas encore de critiques.", + "More": "Suite", + "You may also like": "Tu pourrais aussi aimer", + "Leave a review": "Laisser un commentaire", + "How would you rate": "Comment évalueriez-vous", + "Submit": "Soumettre", + "Your review has been submitted": "Votre avis a été soumis" } \ No newline at end of file diff --git a/LabelStoreMax/lang/hi.json b/LabelStoreMax/lang/hi.json index 51048b7..1cab3fb 100644 --- a/LabelStoreMax/lang/hi.json +++ b/LabelStoreMax/lang/hi.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "yah utpaad aapakee ichchha soochee se hata diya gaya hai", "This product has been added to your wishlist": "is utpaad ko aapakee vish - list mein jod diya gaya hai", "Spend a minimum of minimumAmount to redeem": "kam se kam kharch karen {{minimumAmount}} ke evaj mein lena", - "Spend less than maximumAmount to redeem": "se kam kharch karen {{maximumAmount}} ke evaj mein lena" + "Spend less than maximumAmount to redeem": "se kam kharch karen {{maximumAmount}} ke evaj mein lena", + "Related products": "sambandhit utpaad", + "Reviews": "sameeksha", + "There are no reviews yet.": "abhee tak koee sameeksha nahin.", + "More": "adhik", + "You may also like": "aapako yah bhee pasand aa sakata hain", + "Leave a review": "sameeksha likhen", + "How would you rate": "aap ise kya ret karate hain", + "Submit": "prastut karana", + "Your review has been submitted": "aapakee sameeksha jama ho chukee hai" } \ No newline at end of file diff --git a/LabelStoreMax/lang/it.json b/LabelStoreMax/lang/it.json index 8e3ad5b..29c118f 100644 --- a/LabelStoreMax/lang/it.json +++ b/LabelStoreMax/lang/it.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Questo prodotto è stato rimosso dalla tua lista dei desideri", "This product has been added to your wishlist": "Questo prodotto è stato aggiunto alla tua lista dei desideri", "Spend a minimum of minimumAmount to redeem": "Spendi un minimo di {{minimumAmount}} riscattare", - "Spend less than maximumAmount to redeem": "Spendi meno di {{maximumAmount}} riscattare" + "Spend less than maximumAmount to redeem": "Spendi meno di {{maximumAmount}} riscattare", + "Related products": "Prodotti correlati", + "Reviews": "Recensioni", + "There are no reviews yet.": "Non ci sono ancora recensioni.", + "More": "Di più", + "You may also like": "Potrebbe piacerti anche", + "Leave a review": "Lascia una recensione", + "How would you rate": "Come valuteresti", + "Submit": "Invia", + "Your review has been submitted": "La tua recensione è stata inviata" } \ No newline at end of file diff --git a/LabelStoreMax/lang/nl.json b/LabelStoreMax/lang/nl.json index 3d16e25..b250233 100644 --- a/LabelStoreMax/lang/nl.json +++ b/LabelStoreMax/lang/nl.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Dit product is van je verlanglijst verwijderd", "This product has been added to your wishlist": "Dit product is toegevoegd aan je verlanglijst", "Spend a minimum of minimumAmount to redeem": "Besteed minimaal € {{minimumAmount}} verlossen", - "Spend less than maximumAmount to redeem": "Minder uitgeven dan {{maximumAmount}} verlossen" + "Spend less than maximumAmount to redeem": "Minder uitgeven dan {{maximumAmount}} verlossen", + "Related products": "Gerelateerde producten", + "Reviews": "Beoordelingen", + "There are no reviews yet.": "Er zijn nog geen beoordelingen.", + "More": "Meer", + "You may also like": "Dit vind je misschien ook leuk", + "Leave a review": "Laat je mening achter", + "How would you rate": "Hoe zou jij beoordelen", + "Submit": "Indienen", + "Your review has been submitted": "Uw beoordeling is verzonden" } \ No newline at end of file diff --git a/LabelStoreMax/lang/pt.json b/LabelStoreMax/lang/pt.json index 0357246..5dac310 100644 --- a/LabelStoreMax/lang/pt.json +++ b/LabelStoreMax/lang/pt.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Este produto foi removido da sua lista de desejos", "This product has been added to your wishlist": "Este produto foi adicionado à sua lista de desejos", "Spend a minimum of minimumAmount to redeem": "Gaste um mínimo de {{minimumAmount}} redimir", - "Spend less than maximumAmount to redeem": "Gaste menos que {{maximumAmount}} redimir" + "Spend less than maximumAmount to redeem": "Gaste menos que {{maximumAmount}} redimir", + "Related products": "Produtos relacionados", + "Reviews": "Avaliações", + "There are no reviews yet.": "Não há comentários ainda.", + "More": "Mais", + "You may also like": "você pode gostar", + "Leave a review": "Deixe um comentário", + "How would you rate": "Como você avaliaria", + "Submit": "Enviar", + "Your review has been submitted": "Sua avaliação foi enviada" } \ No newline at end of file diff --git a/LabelStoreMax/lang/tr.json b/LabelStoreMax/lang/tr.json index 11f42c6..c68e67e 100644 --- a/LabelStoreMax/lang/tr.json +++ b/LabelStoreMax/lang/tr.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "Bu ürün istek listenizden kaldırıldı", "This product has been added to your wishlist": "Bu ürün istek listenize eklendi", "Spend a minimum of minimumAmount to redeem": "Minimum harcamak {{minimumAmount}} rehinden kurtarmak", - "Spend less than maximumAmount to redeem": "Şundan daha az harcayın: {{maximumAmount}} rehinden kurtarmak" + "Spend less than maximumAmount to redeem": "Şundan daha az harcayın: {{maximumAmount}} rehinden kurtarmak", + "Related products": "İlgili ürünler", + "Reviews": "incelemeler", + "There are no reviews yet.": "Henüz yorum yok.", + "More": "Daha", + "You may also like": "Şunlar da hoşunuza gidebilir", + "Leave a review": "İnceleme bırak", + "How would you rate": "Nasıl değerlendirirsiniz", + "Submit": "Göndermek", + "Your review has been submitted": "İncelemeniz gönderildi" } \ No newline at end of file diff --git a/LabelStoreMax/lang/zh.json b/LabelStoreMax/lang/zh.json index c7e3c62..b682c5f 100644 --- a/LabelStoreMax/lang/zh.json +++ b/LabelStoreMax/lang/zh.json @@ -210,5 +210,14 @@ "This product has been removed from your wishlist": "该产品已从您的愿望清单中删除", "This product has been added to your wishlist": "本产品已经被加入你的心愿单", "Spend a minimum of minimumAmount to redeem": "至少花费 {{minimumAmount}} 赎回", - "Spend less than maximumAmount to redeem": "花费少于 {{maximumAmount}} 赎回" + "Spend less than maximumAmount to redeem": "花费少于 {{maximumAmount}} 赎回", + "Related products": "相关产品", + "Reviews": "评论", + "There are no reviews yet.": "还没有评论。", + "More": "更多的", + "You may also like": "你可能也会喜欢", + "Leave a review": "发表评论", + "How would you rate": "你如何评价", + "Submit": "提交", + "Your review has been submitted": "您的评论已提交" } \ No newline at end of file diff --git a/LabelStoreMax/lib/app/controllers/leave_review_controller.dart b/LabelStoreMax/lib/app/controllers/leave_review_controller.dart new file mode 100644 index 0000000..2524b13 --- /dev/null +++ b/LabelStoreMax/lib/app/controllers/leave_review_controller.dart @@ -0,0 +1,21 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'controller.dart'; +import 'package:flutter/widgets.dart'; + +class LeaveReviewController extends Controller { + + @override + construct(BuildContext context) { + super.construct(context); + + } +} \ No newline at end of file diff --git a/LabelStoreMax/lib/app/controllers/product_detail_controller.dart b/LabelStoreMax/lib/app/controllers/product_detail_controller.dart index 716988d..c9ee6ad 100644 --- a/LabelStoreMax/lib/app/controllers/product_detail_controller.dart +++ b/LabelStoreMax/lib/app/controllers/product_detail_controller.dart @@ -8,27 +8,110 @@ // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +import 'package:flutter/material.dart'; +import 'package:flutter_app/app/models/cart.dart'; +import 'package:flutter_app/app/models/cart_line_item.dart'; +import 'package:flutter_app/bootstrap/enums/wishlist_action_enums.dart'; import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:nylo_framework/nylo_framework.dart'; import 'package:woosignal/models/response/products.dart'; +import 'package:woosignal/models/response/product_variation.dart' +as ws_product_variation; import 'controller.dart'; -import 'package:flutter/widgets.dart'; class ProductDetailController extends Controller { + int quantity = 1; + Product product; + @override construct(BuildContext context) { super.construct(context); + product = data() as Product; } - viewExternalProduct(Product product) { + viewExternalProduct() { if (product.externalUrl != null && product.externalUrl.isNotEmpty) { openBrowserTab(url: product.externalUrl); } } - viewProductImages(int i, Product product) => - Navigator.pushNamed(context, "/product-images", arguments: { - "index": i, - "images": product.images.map((f) => f.src).toList() - }); -} + itemAddToCart({@required CartLineItem cartLineItem, @required Function onSuccess}) async { + await Cart.getInstance.addToCart(cartLineItem: cartLineItem); + showStatusAlert(context, + title: trans("Success"), + subtitle: trans("Added to cart"), + duration: 1, + icon: Icons.add_shopping_cart, + ); + onSuccess(); + } + + addQuantityTapped({@required Function onSuccess}) { + if (product.manageStock != null && product.manageStock == true) { + if (quantity >= product.stockQuantity) { + showToastNotification(context, + title: trans("Maximum quantity reached"), + description: + "${trans("Sorry, only")} ${product.stockQuantity} ${trans("left")}", + style: ToastNotificationStyleType.INFO); + return; + } + } + if (quantity != 0) { + quantity++; + onSuccess(); + } + } + + removeQuantityTapped({@required Function onSuccess}) { + if ((quantity - 1) >= 1) { + quantity--; + onSuccess(); + } + } + + toggleWishList({@required Function onSuccess, @required WishlistAction wishlistAction}) async { + String subtitleMsg; + if (wishlistAction == WishlistAction.remove) { + await removeWishlistProduct(product: product); + subtitleMsg = trans("This product has been removed from your wishlist"); + } else { + await saveWishlistProduct(product: product); + subtitleMsg = trans("This product has been added to your wishlist"); + } + showStatusAlert(context, + title: trans("Success"), + subtitle: subtitleMsg, + icon: Icons.favorite, + duration: 1, + ); + + onSuccess(); + } + + ws_product_variation.ProductVariation findProductVariation( + {@required Map tmpAttributeObj, + @required List productVariations}) { + ws_product_variation.ProductVariation tmpProductVariation; + + Map tmpSelectedObj = {}; + for (var attributeObj in tmpAttributeObj.values) { + tmpSelectedObj[attributeObj["name"]] = attributeObj["value"]; + } + + for (var productVariation in productVariations) { + Map tmpVariations = {}; + + for (var attr in productVariation.attributes) { + tmpVariations[attr.name] = attr.option; + } + + if (tmpVariations.toString() == tmpSelectedObj.toString()) { + tmpProductVariation = productVariation; + } + } + + return tmpProductVariation; + } +} \ No newline at end of file diff --git a/LabelStoreMax/lib/app/controllers/product_loader_controller.dart b/LabelStoreMax/lib/app/controllers/product_loader_controller.dart index aa43e75..f7cc293 100644 --- a/LabelStoreMax/lib/app/controllers/product_loader_controller.dart +++ b/LabelStoreMax/lib/app/controllers/product_loader_controller.dart @@ -18,6 +18,7 @@ class ProductLoaderController extends WooSignalApiLoaderController { Future loadProducts({ @required bool Function(bool hasProducts) hasResults, @required void Function() didFinish, + List productIds = const [] }) async { await load( hasResults: hasResults, @@ -25,6 +26,7 @@ class ProductLoaderController extends WooSignalApiLoaderController { apiQuery: (api) => api.getProducts( perPage: 50, page: page, + include: productIds, status: "publish", stockStatus: "instock", )); diff --git a/LabelStoreMax/lib/app/controllers/product_reviews_controller.dart b/LabelStoreMax/lib/app/controllers/product_reviews_controller.dart new file mode 100644 index 0000000..b4f255e --- /dev/null +++ b/LabelStoreMax/lib/app/controllers/product_reviews_controller.dart @@ -0,0 +1,22 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'controller.dart'; +import 'package:flutter/widgets.dart'; + +class ProductReviewsController extends Controller { + + @override + construct(BuildContext context) { + super.construct(context); + + } + +} diff --git a/LabelStoreMax/lib/app/controllers/product_reviews_loader_controller.dart b/LabelStoreMax/lib/app/controllers/product_reviews_loader_controller.dart new file mode 100644 index 0000000..68e1098 --- /dev/null +++ b/LabelStoreMax/lib/app/controllers/product_reviews_loader_controller.dart @@ -0,0 +1,34 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_app/app/controllers/woosignal_api_loader_controller.dart'; +import 'package:woosignal/models/response/product_review.dart'; +import 'package:woosignal/models/response/products.dart'; + +class ProductReviewsLoaderController extends WooSignalApiLoaderController { + ProductReviewsLoaderController(); + + Future loadProductReviews({ + @required Product product, + @required bool Function(bool hasProducts) hasResults, + @required void Function() didFinish, + }) async { + await load( + hasResults: hasResults, + didFinish: didFinish, + apiQuery: (api) => api.getProductReviews( + product: [product.id], + perPage: 50, + page: page, + status: "approved", + )); + } +} diff --git a/LabelStoreMax/lib/bootstrap/enums/wishlist_action_enums.dart b/LabelStoreMax/lib/bootstrap/enums/wishlist_action_enums.dart new file mode 100644 index 0000000..2a54d73 --- /dev/null +++ b/LabelStoreMax/lib/bootstrap/enums/wishlist_action_enums.dart @@ -0,0 +1,15 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + +enum WishlistAction { + add, + remove +} \ No newline at end of file diff --git a/LabelStoreMax/lib/bootstrap/helpers.dart b/LabelStoreMax/lib/bootstrap/helpers.dart index 9b8537a..f0fc71e 100644 --- a/LabelStoreMax/lib/bootstrap/helpers.dart +++ b/LabelStoreMax/lib/bootstrap/helpers.dart @@ -99,7 +99,7 @@ List getPaymentTypes() { } dynamic envVal(String envVal, {dynamic defaultValue}) => - (getEnv(envVal) == null ? defaultValue : getEnv(envVal)); + (getEnv(envVal) ?? defaultValue); PaymentType addPayment( {@required int id, @@ -372,7 +372,7 @@ navigatorPush(BuildContext context, if (forgetAll) { Navigator.of(context).pushNamedAndRemoveUntil( routeName, (Route route) => false, - arguments: arguments ?? null); + arguments: arguments); } if (forgetLast != null) { int count = 0; @@ -380,7 +380,7 @@ navigatorPush(BuildContext context, return count++ == forgetLast; }); } - Navigator.of(context).pushNamed(routeName, arguments: arguments ?? null); + Navigator.of(context).pushNamed(routeName, arguments: arguments); } PlatformDialogAction dialogAction(BuildContext context, @@ -554,6 +554,16 @@ Future> getWishlistProducts() async { return favouriteProducts; } +hasAddedWishlistProduct(int productId) async { + List favouriteProducts = await getWishlistProducts(); + List productIds = + favouriteProducts.map((e) => e['id']).cast().toList(); + if (productIds.isEmpty) { + return false; + } + return productIds.contains(productId); +} + saveWishlistProduct({@required Product product}) async { List products = await getWishlistProducts(); if (products.any((wishListProduct) => wishListProduct['id'] == product.id) == diff --git a/LabelStoreMax/lib/resources/pages/account_detail.dart b/LabelStoreMax/lib/resources/pages/account_detail.dart index d488caa..170c7e8 100644 --- a/LabelStoreMax/lib/resources/pages/account_detail.dart +++ b/LabelStoreMax/lib/resources/pages/account_detail.dart @@ -17,7 +17,6 @@ import 'package:flutter_app/bootstrap/shared_pref/sp_auth.dart'; import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; import 'package:flutter_app/resources/widgets/safearea_widget.dart'; import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; -import 'package:hexcolor/hexcolor.dart'; import 'package:nylo_framework/nylo_framework.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:woosignal/models/response/order.dart'; @@ -343,7 +342,7 @@ class _AccountDetailPageState extends State decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: HexColor("#fcfcfc"), + color: Color(0xFFFCFCFC), width: 1, ), ), diff --git a/LabelStoreMax/lib/resources/pages/account_order_detail.dart b/LabelStoreMax/lib/resources/pages/account_order_detail.dart index 0306c9a..d1052ec 100644 --- a/LabelStoreMax/lib/resources/pages/account_order_detail.dart +++ b/LabelStoreMax/lib/resources/pages/account_order_detail.dart @@ -14,7 +14,6 @@ import 'package:flutter_app/bootstrap/helpers.dart'; import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; import 'package:flutter_app/resources/widgets/safearea_widget.dart'; import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; -import 'package:hexcolor/hexcolor.dart'; import 'package:nylo_support/helpers/helper.dart'; import 'package:nylo_support/widgets/ny_state.dart'; import 'package:nylo_support/widgets/ny_stateful_widget.dart'; @@ -63,12 +62,15 @@ class _AccountOrderDetailPageState extends NyState { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( - "${trans("Date Ordered").capitalize()}: " + - dateFormatted( - date: _order.dateCreated, - formatType: formatForDateTime(FormatType.date), - ), + Padding( + padding: EdgeInsets.only(top: 8), + child: Text( + "${trans("Date Ordered").capitalize()}: " + + dateFormatted( + date: _order.dateCreated, + formatType: formatForDateTime(FormatType.date), + ), + ), ), Container( margin: EdgeInsets.only(top: 10, bottom: 10), @@ -116,6 +118,7 @@ class _AccountOrderDetailPageState extends NyState { Expanded( child: ListView.builder( itemBuilder: (cxt, i) { + LineItems lineItem = _order.lineItems[i]; return Card( child: ListTile( contentPadding: EdgeInsets.only( @@ -124,7 +127,7 @@ class _AccountOrderDetailPageState extends NyState { decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: HexColor("#fcfcfc"), width: 1), + color: Color(0xFFFCFCFC), width: 1), ), ), child: Row( @@ -134,14 +137,13 @@ class _AccountOrderDetailPageState extends NyState { children: [ Flexible( child: Text( - _order.lineItems[i].name, + lineItem.name, maxLines: 1, overflow: TextOverflow.ellipsis, ), ), Text( - formatStringCurrency( - total: _order.lineItems[i].price) + formatStringCurrency(total: lineItem.price) .capitalize(), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -163,7 +165,7 @@ class _AccountOrderDetailPageState extends NyState { children: [ Text( formatStringCurrency( - total: _order.lineItems[i].total, + total: lineItem.total, ), style: Theme.of(context) .textTheme @@ -174,7 +176,7 @@ class _AccountOrderDetailPageState extends NyState { textAlign: TextAlign.left, ), Text( - "x${_order.lineItems[i].quantity.toString()}", + "x${lineItem.quantity.toString()}", style: Theme.of(context) .textTheme .bodyText1, diff --git a/LabelStoreMax/lib/resources/pages/customer_countries.dart b/LabelStoreMax/lib/resources/pages/customer_countries.dart index 09cabe1..b22efa1 100644 --- a/LabelStoreMax/lib/resources/pages/customer_countries.dart +++ b/LabelStoreMax/lib/resources/pages/customer_countries.dart @@ -131,7 +131,7 @@ class _CustomerCountriesPageState extends State { } _handleCountryTapped(DefaultShipping defaultShipping) { - if (defaultShipping.states.length > 0) { + if (defaultShipping.states.isNotEmpty) { _handleStates(defaultShipping); return; } diff --git a/LabelStoreMax/lib/resources/pages/leave_review_page.dart b/LabelStoreMax/lib/resources/pages/leave_review_page.dart new file mode 100644 index 0000000..5bc7cf6 --- /dev/null +++ b/LabelStoreMax/lib/resources/pages/leave_review_page.dart @@ -0,0 +1,193 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:flutter_app/bootstrap/shared_pref/sp_auth.dart'; +import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; +import 'package:flutter_app/resources/widgets/buttons.dart'; +import 'package:flutter_app/resources/widgets/safearea_widget.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:woosignal/models/response/order.dart'; +import 'package:woosignal/models/response/product_review.dart'; +import 'package:wp_json_api/models/responses/wc_customer_info_response.dart' + as wc_customer_info; +import 'package:wp_json_api/wp_json_api.dart'; +import '../../app/controllers/leave_review_controller.dart'; + +class LeaveReviewPage extends NyStatefulWidget { + final LeaveReviewController controller = LeaveReviewController(); + + LeaveReviewPage({Key key}) : super(key: key); + + @override + _LeaveReviewPageState createState() => _LeaveReviewPageState(); +} + +class _LeaveReviewPageState extends NyState { + LineItems _lineItem; + Order _order; + + TextEditingController _textEditingController; + int _rating; + bool _isLoading = false; + + @override + widgetDidLoad() async { + _lineItem = widget.controller.data()['line_item'] as LineItems; + _order = widget.controller.data()['order'] as Order; + _textEditingController = TextEditingController(); + _rating = 5; + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(trans("Leave a review")), + centerTitle: true, + ), + body: SafeAreaWidget( + child: _isLoading + ? AppLoaderWidget() + : Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 16), + ), + Text( + trans("How would you rate"), + style: Theme.of(context).textTheme.bodyText1, + ), + Text(_lineItem.name), + Flexible( + child: Container( + child: TextField( + controller: _textEditingController, + style: Theme.of(context).textTheme.subtitle1, + keyboardType: TextInputType.text, + autocorrect: false, + autofocus: true, + obscureText: false, + textCapitalization: TextCapitalization.sentences, + ), + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 16), + ), + RatingBar.builder( + initialRating: _rating.toDouble(), + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: false, + itemCount: 5, + itemPadding: EdgeInsets.symmetric(horizontal: 4.0), + itemBuilder: (context, _) => + Icon(Icons.star, color: Colors.amber), + onRatingUpdate: (rating) { + _rating = rating.toInt(); + }, + ), + Padding( + padding: EdgeInsets.only(bottom: 16), + ), + PrimaryButton(title: trans("Submit"), action: _leaveReview), + ], + )), + ); + } + + _leaveReview() async { + if (_isLoading == true) { + return; + } + setState(() { + _isLoading = true; + }); + String review = _textEditingController.text; + wc_customer_info.Data wcCustomerInfo = await _fetchWpUserData(); + if (wcCustomerInfo == null) { + showToastNotification( + context, + title: trans("Oops!"), + description: trans("Something went wrong"), + style: ToastNotificationStyleType.DANGER, + ); + setState(() { + _isLoading = false; + }); + return; + } + + try { + validator(rules: {"review": "min:5"}, data: {"review": review}); + + ProductReview productReview = + await appWooSignal((api) => api.createProductReview( + productId: _lineItem.productId, + verified: true, + review: review, + status: "approved", + reviewer: [_order.billing.firstName, _order.billing.lastName] + .join(" "), + rating: _rating, + reviewerEmail: _order.billing.email, + )); + + if (productReview == null) { + showToastNotification(context, + title: trans("Oops"), + description: trans("Something went wrong"), + style: ToastNotificationStyleType.INFO); + return; + } + showToastNotification(context, + title: trans("Success"), + description: trans("Your review has been submitted"), + style: ToastNotificationStyleType.SUCCESS); + pop(result: _lineItem); + } on ValidationException catch (e) { + NyLogger.error(e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } + + Future _fetchWpUserData() async { + String userToken = await readAuthToken(); + + wc_customer_info.WCCustomerInfoResponse wcCustomerInfoResponse; + try { + wcCustomerInfoResponse = await WPJsonAPI.instance + .api((request) => request.wcCustomerInfo(userToken)); + + if (wcCustomerInfoResponse == null) { + return null; + } + if (wcCustomerInfoResponse.status != 200) { + return null; + } + return wcCustomerInfoResponse.data; + } on Exception catch (_) { + return null; + } + } +} diff --git a/LabelStoreMax/lib/resources/pages/product_detail.dart b/LabelStoreMax/lib/resources/pages/product_detail.dart index 4ba00b7..1de6b38 100644 --- a/LabelStoreMax/lib/resources/pages/product_detail.dart +++ b/LabelStoreMax/lib/resources/pages/product_detail.dart @@ -10,20 +10,21 @@ import 'package:flutter/material.dart'; import 'package:flutter_app/app/controllers/product_detail_controller.dart'; -import 'package:flutter_app/app/models/cart.dart'; import 'package:flutter_app/app/models/cart_line_item.dart'; import 'package:flutter_app/bootstrap/app_helper.dart'; +import 'package:flutter_app/bootstrap/enums/wishlist_action_enums.dart'; import 'package:flutter_app/bootstrap/helpers.dart'; import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; import 'package:flutter_app/resources/widgets/buttons.dart'; -import 'package:flutter_app/resources/widgets/cached_image_widget.dart'; import 'package:flutter_app/resources/widgets/cart_icon_widget.dart'; +import 'package:flutter_app/resources/widgets/future_build_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_body_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_footer_actions_widget.dart'; import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; import 'package:nylo_framework/nylo_framework.dart'; import 'package:woosignal/models/response/product_variation.dart' as ws_product_variation; import 'package:woosignal/models/response/products.dart' as ws_product; -import 'package:flutter_swiper/flutter_swiper.dart'; import 'package:woosignal/models/response/woosignal_app.dart'; class ProductDetailPage extends NyStatefulWidget { @@ -36,10 +37,9 @@ class ProductDetailPage extends NyStatefulWidget { } class _ProductDetailState extends NyState { - bool _isLoading = false; + bool _isLoading = true; ws_product.Product _product; - bool isInFavourites = false; - int _quantityIndicator = 1; + List _productVariations = []; final Map _tmpAttributeObj = {}; final WooSignalApp _wooSignalApp = AppHelper.instance.appConfig; @@ -47,11 +47,13 @@ class _ProductDetailState extends NyState { @override widgetDidLoad() async { _product = widget.controller.data(); - if (_product.type == "variable") { - _isLoading = true; await _fetchProductVariations(); + return; } + setState(() { + _isLoading = false; + }); } _fetchProductVariations() async { @@ -80,29 +82,6 @@ class _ProductDetailState extends NyState { }); } - ws_product_variation.ProductVariation findProductVariation() { - ws_product_variation.ProductVariation tmpProductVariation; - - Map tmpSelectedObj = {}; - for (var attributeObj in _tmpAttributeObj.values) { - tmpSelectedObj[attributeObj["name"]] = attributeObj["value"]; - } - - for (var productVariation in _productVariations) { - Map tmpVariations = {}; - - for (var attr in productVariation.attributes) { - tmpVariations[attr.name] = attr.option; - } - - if (tmpVariations.toString() == tmpSelectedObj.toString()) { - tmpProductVariation = productVariation; - } - } - - return tmpProductVariation; - } - _modalBottomSheetOptionsForAttribute(int attributeIndex) { wsModalBottom( context, @@ -137,19 +116,11 @@ class _ProductDetailState extends NyState { ); } - _itemAddToCart({CartLineItem cartLineItem}) async { - await Cart.getInstance.addToCart(cartLineItem: cartLineItem); - showStatusAlert(context, - title: trans("Success"), - subtitle: trans("Added to cart"), - duration: 1, - icon: Icons.add_shopping_cart); - setState(() {}); - } - _modalBottomSheetAttributes() { - ws_product_variation.ProductVariation productVariation = - findProductVariation(); + ws_product_variation.ProductVariation productVariation = widget.controller + .findProductVariation( + tmpAttributeObj: _tmpAttributeObj, + productVariations: _productVariations); wsModalBottom( context, title: trans("Options"), @@ -203,7 +174,7 @@ class _ProductDetailState extends NyState { ), PrimaryButton( title: trans("Add to cart"), - action: () { + action: () async { if (_product.attributes.length != _tmpAttributeObj.values.length) { showToastNotification(context, @@ -235,12 +206,16 @@ class _ProductDetailState extends NyState { }); CartLineItem cartLineItem = CartLineItem.fromProductVariation( - quantityAmount: _quantityIndicator, - options: options, - product: _product, - productVariation: productVariation); + quantityAmount: widget.controller.quantity, + options: options, + product: _product, + productVariation: productVariation, + ); - _itemAddToCart(cartLineItem: cartLineItem); + await widget.controller.itemAddToCart( + cartLineItem: cartLineItem, + onSuccess: () => setState(() => {})); + setState(() {}); Navigator.of(context).pop(); }), ], @@ -250,30 +225,28 @@ class _ProductDetailState extends NyState { ); } - _modalBottomSheetMenu() { - wsModalBottom( - context, - title: trans("Description"), - bodyWidget: SingleChildScrollView( - child: Text( - parseHtmlString(_product.description), - ), - ), - ); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( actions: [ if (_wooSignalApp.wishlistEnabled) - IconButton( - onPressed: _toggleWishList, - icon: isInFavourites - ? Icon(Icons.favorite, color: Colors.red) - : Icon(Icons.favorite_border, color: Colors.black54), - ), + FutureBuildWidget( + asyncFuture: hasAddedWishlistProduct(_product.id), + onValue: (isInFavourites) { + return isInFavourites + ? IconButton( + onPressed: () => widget.controller.toggleWishList( + onSuccess: () => setState(() {}), + wishlistAction: WishlistAction.remove), + icon: Icon(Icons.favorite, color: Colors.red)) + : IconButton( + onPressed: () => widget.controller.toggleWishList( + onSuccess: () => setState(() {}), + wishlistAction: WishlistAction.add), + icon: Icon(Icons.favorite_border, + color: Colors.black54)); + }), CartIconWidget(), ], title: StoreLogo( @@ -289,248 +262,30 @@ class _ProductDetailState extends NyState { crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: ListView( - children: [ - SizedBox( - height: MediaQuery.of(context).size.height * 0.40, - child: SizedBox( - child: Swiper( - itemBuilder: (BuildContext context, int index) => - CachedImageWidget( - image: _product.images.isNotEmpty - ? _product.images[index].src - : getEnv("PRODUCT_PLACEHOLDER_IMAGE"), - ), - itemCount: _product.images.isEmpty - ? 1 - : _product.images.length, - viewportFraction: 0.85, - scale: 0.9, - onTap: (int i) => widget.controller - .viewProductImages(i, _product), - ), - ), - ), - Container( - height: 100, - padding: EdgeInsets.symmetric( - vertical: 10, - horizontal: 16, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - _product.name, - style: Theme.of(context) - .textTheme - .bodyText1 - .copyWith(fontSize: 20), - textAlign: TextAlign.left, - overflow: TextOverflow.ellipsis, - maxLines: 2, - ), - flex: 4, - ), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - formatStringCurrency( - total: _product.price), - style: Theme.of(context) - .textTheme - .headline4 - .copyWith( - fontSize: 20, - ), - textAlign: TextAlign.right, - ), - (_product.onSale == true && - _product.type != "variable" - ? Text( - formatStringCurrency( - total: _product.regularPrice), - style: TextStyle( - color: Colors.grey, - decoration: - TextDecoration.lineThrough, - ), - ) - : null) - ].where((t) => t != null).toList(), - ), - flex: 2, - ) - ], - ), - ), - Container( - decoration: BoxDecoration( - color: ThemeColor.get(context).background, - borderRadius: BorderRadius.circular(4), - ), - padding: - EdgeInsets.symmetric(vertical: 4, horizontal: 16), - height: 180, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - trans("Description"), - style: Theme.of(context) - .textTheme - .caption - .copyWith(fontSize: 18), - textAlign: TextAlign.left, - ), - MaterialButton( - child: Text( - trans("Full description"), - style: Theme.of(context) - .textTheme - .bodyText2 - .copyWith(fontSize: 14), - textAlign: TextAlign.right, - overflow: TextOverflow.ellipsis, - ), - height: 50, - minWidth: 60, - onPressed: _modalBottomSheetMenu, - ), - ], - ), - Flexible( - child: Text( - (_product.shortDescription != null && - _product.shortDescription != "" - ? parseHtmlString( - _product.shortDescription) - : parseHtmlString(_product.description)), - maxLines: 5, - overflow: TextOverflow.ellipsis, - ), - flex: 3, - ), - ], - ), - ), - ], + child: ProductDetailBodyWidget( + wooSignalApp: _wooSignalApp, + product: _product, ), ), - Container( - padding: EdgeInsets.symmetric(horizontal: 16), - decoration: BoxDecoration( - color: ThemeColor.get(context).background, - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 15.0, - spreadRadius: -17, - offset: Offset( - 0, - -10, - ), - ) - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - (_product.type != "external" - ? Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - trans("Quantity"), - style: Theme.of(context) - .textTheme - .bodyText1 - .copyWith(color: Colors.grey), - ), - Row( - children: [ - IconButton( - icon: Icon( - Icons.remove_circle_outline, - size: 28, - ), - onPressed: _removeQuantityTapped, - ), - Text( - _quantityIndicator.toString(), - style: Theme.of(context) - .textTheme - .bodyText1, - ), - IconButton( - icon: Icon( - Icons.add_circle_outline, - size: 28, - ), - onPressed: _addQuantityTapped, - ), - ], - ) - ], - ) - : null), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Align( - child: Text( - formatStringCurrency( - total: (parseWcPrice(_product.price) * - _quantityIndicator) - .toString()), - style: Theme.of(context).textTheme.headline4, - textAlign: TextAlign.center, - ), - alignment: Alignment.centerLeft, - )), - _product.type == "external" - ? Flexible( - child: PrimaryButton( - title: trans("Buy Product"), - action: () => widget.controller - .viewExternalProduct(_product), - ), - ) - : Flexible( - child: PrimaryButton( - title: trans("Add to cart"), - action: () => _addItemToCart(), - ), - ), - ], - ), - ].where((e) => e != null).toList(), - ), - height: 140, - ), + // + ProductDetailFooterActionsWidget( + onAddToCart: _addItemToCart, + onViewExternalProduct: + widget.controller.viewExternalProduct, + onAddQuantity: () => widget.controller + .addQuantityTapped(onSuccess: () => setState(() {})), + onRemoveQuantity: () => widget.controller + .removeQuantityTapped(onSuccess: () => setState(() {})), + product: _product, + quantity: widget.controller.quantity, + ) ], ), ), ); } - _addItemToCart() { + _addItemToCart() async { if (_product.type != "simple") { _modalBottomSheetAttributes(); return; @@ -543,53 +298,10 @@ class _ProductDetailState extends NyState { icon: Icons.local_shipping); return; } - _itemAddToCart( + + await widget.controller.itemAddToCart( cartLineItem: CartLineItem.fromProduct( - quantityAmount: _quantityIndicator, product: _product)); - } - - _addQuantityTapped() { - if (_product.manageStock != null && _product.manageStock == true) { - if (_quantityIndicator >= _product.stockQuantity) { - showToastNotification(context, - title: trans("Maximum quantity reached"), - description: - "${trans("Sorry, only")} ${_product.stockQuantity} ${trans("left")}", - style: ToastNotificationStyleType.INFO); - return; - } - } - if (_quantityIndicator != 0) { - setState(() { - _quantityIndicator++; - }); - } - } - - _removeQuantityTapped() { - if ((_quantityIndicator - 1) >= 1) { - setState(() { - _quantityIndicator--; - }); - } - } - - _toggleWishList() async { - String subtitleMsg; - if (isInFavourites) { - await removeWishlistProduct(product: _product); - subtitleMsg = trans("This product has been removed from your wishlist"); - } else { - await saveWishlistProduct(product: _product); - subtitleMsg = trans("This product has been added to your wishlist"); - } - showStatusAlert(context, - title: trans("Success"), - subtitle: subtitleMsg, - icon: Icons.favorite, - duration: 1); - - isInFavourites = !isInFavourites; - setState(() {}); + quantityAmount: widget.controller.quantity, product: _product), + onSuccess: () => setState(() {})); } } diff --git a/LabelStoreMax/lib/resources/pages/product_reviews_page.dart b/LabelStoreMax/lib/resources/pages/product_reviews_page.dart new file mode 100644 index 0000000..98116ce --- /dev/null +++ b/LabelStoreMax/lib/resources/pages/product_reviews_page.dart @@ -0,0 +1,213 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_app/app/controllers/product_reviews_loader_controller.dart'; +import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; +import 'package:flutter_app/resources/widgets/no_results_for_products_widget.dart'; +import 'package:flutter_app/resources/widgets/product_review_item_container_widget.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:woosignal/models/response/product_review.dart'; +import 'package:woosignal/models/response/products.dart'; +import '../../app/controllers/product_reviews_controller.dart'; + +class ProductReviewsPage extends NyStatefulWidget { + final ProductReviewsController controller = ProductReviewsController(); + + ProductReviewsPage({Key key}) : super(key: key); + + @override + _ProductReviewsPageState createState() => _ProductReviewsPageState(); +} + +class _ProductReviewsPageState extends NyState { + final RefreshController _refreshController = + RefreshController(initialRefresh: false); + Product _product; + bool _shouldStopRequests = false, _isLoading = true; + final ProductReviewsLoaderController _productReviewsLoaderController = + ProductReviewsLoaderController(); + + @override + widgetDidLoad() async { + _product = widget.data() as Product; + await fetchProductReviews(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + List productReviews = + _productReviewsLoaderController.getResults(); + return Scaffold( + appBar: AppBar( + title: Text(trans('Reviews')), + centerTitle: true, + ), + body: _isLoading + ? AppLoaderWidget() + : SafeArea( + child: Column( + // shrinkWrap: true, + children: [ + Container( + height: mediaQuery.size.height / 5, + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), + margin: EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + border: + Border(bottom: BorderSide(color: Colors.black12))), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + child: Text( + _product.name, + style: Theme.of(context).textTheme.headline6, + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 8), + child: Text( + _product.ratingCount.toString() + " Reviews", + style: Theme.of(context).textTheme.bodyText2, + ), + ), + Row( + children: [ + Container( + margin: EdgeInsets.only(right: 8), + child: Text( + _product.averageRating + " Stars", + style: Theme.of(context).textTheme.bodyText2, + ), + ), + RatingBarIndicator( + rating: double.parse(_product.averageRating), + itemBuilder: (context, index) => Icon( + Icons.star, + color: Colors.amber, + ), + itemCount: 5, + itemSize: 20.0, + direction: Axis.horizontal, + ), + ], + ), + ], + ), + ), + Expanded( + child: SmartRefresher( + enablePullDown: true, + enablePullUp: true, + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode) { + Widget body; + if (mode == LoadStatus.idle) { + body = Text(trans("pull up load")); + } else if (mode == LoadStatus.loading) { + body = CupertinoActivityIndicator(); + } else if (mode == LoadStatus.failed) { + body = Text(trans("Load Failed! Click retry!")); + } else if (mode == LoadStatus.canLoading) { + body = Text(trans("release to load more")); + } else { + return SizedBox.shrink(); + } + return Container( + height: 55.0, + child: Center(child: body), + ); + }, + ), + controller: _refreshController, + onRefresh: _onRefresh, + onLoading: _onLoading, + child: (productReviews.length != null && + productReviews.isNotEmpty + ? StaggeredGridView.countBuilder( + crossAxisCount: 2, + scrollDirection: Axis.vertical, + itemCount: productReviews.length, + itemBuilder: (BuildContext context, int index) { + ProductReview productReview = productReviews[index]; + return Container( + padding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + margin: EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + border: Border( + bottom: + BorderSide(color: Colors.black12))), + child: ProductReviewItemContainerWidget( + productReview: productReview), + ); + }, + staggeredTileBuilder: (int index) { + return StaggeredTile.fit(2); + }, + mainAxisSpacing: 4.0, + crossAxisSpacing: 4.0, + ) + : NoResultsForProductsWidget()), + )) + ], + )), + ); + } + + _onRefresh() async { + _productReviewsLoaderController.clear(); + _shouldStopRequests = false; + + await fetchProductReviews(); + _refreshController.refreshCompleted(); + } + + _onLoading() async { + await fetchProductReviews(); + + if (mounted) { + setState(() {}); + if (_shouldStopRequests) { + _refreshController.loadNoData(); + } else { + _refreshController.loadComplete(); + } + } + } + + Future fetchProductReviews() async { + await _productReviewsLoaderController.loadProductReviews( + product: _product, + hasResults: (result) { + if (result == false) { + setState(() { + _shouldStopRequests = true; + }); + return false; + } + return true; + }, + didFinish: () => setState(() { + _isLoading = false; + })); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/app_loader_widget.dart b/LabelStoreMax/lib/resources/widgets/app_loader_widget.dart index 51dcae6..9778279 100644 --- a/LabelStoreMax/lib/resources/widgets/app_loader_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/app_loader_widget.dart @@ -10,7 +10,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:hexcolor/hexcolor.dart'; class AppLoaderWidget extends StatelessWidget { const AppLoaderWidget({Key key}) : super(key: key); @@ -19,6 +18,6 @@ class AppLoaderWidget extends StatelessWidget { Widget build(BuildContext context) { bool isDark = (Theme.of(context).brightness == Brightness.dark); return SpinKitDoubleBounce( - color: HexColor(!isDark ? "#424242" : "#c7c7c7")); + color: Color(!isDark ? 0xFF424242 : 0xFFC7C7C7)); } } diff --git a/LabelStoreMax/lib/resources/widgets/buttons.dart b/LabelStoreMax/lib/resources/widgets/buttons.dart index 724be8b..a8b3a76 100644 --- a/LabelStoreMax/lib/resources/widgets/buttons.dart +++ b/LabelStoreMax/lib/resources/widgets/buttons.dart @@ -11,7 +11,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app/bootstrap/helpers.dart'; -import 'package:hexcolor/hexcolor.dart'; class PrimaryButton extends StatelessWidget { const PrimaryButton({ @@ -54,7 +53,7 @@ class SecondaryButton extends StatelessWidget { textStyle: Theme.of(context).textTheme.bodyText1.copyWith( color: Colors.black87, ), - bgColor: HexColor("#f6f6f9"), + bgColor: Color(0xFFF6F6F9), ); } diff --git a/LabelStoreMax/lib/resources/widgets/checkout_coupon_amount_widget.dart b/LabelStoreMax/lib/resources/widgets/checkout_coupon_amount_widget.dart index 8e9a195..3098ed5 100644 --- a/LabelStoreMax/lib/resources/widgets/checkout_coupon_amount_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/checkout_coupon_amount_widget.dart @@ -25,7 +25,7 @@ class CheckoutCouponAmountWidget extends StatelessWidget { return Text(""); } else { if (checkoutSession.coupon == null) { - return Container(); + return SizedBox.shrink(); } return Padding( child: CheckoutMetaLine( diff --git a/LabelStoreMax/lib/resources/widgets/checkout_shipping_type_widget.dart b/LabelStoreMax/lib/resources/widgets/checkout_shipping_type_widget.dart index 427a94f..b094f97 100644 --- a/LabelStoreMax/lib/resources/widgets/checkout_shipping_type_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/checkout_shipping_type_widget.dart @@ -23,7 +23,7 @@ class CheckoutShippingTypeWidget extends StatelessWidget { Widget build(BuildContext context) { bool hasDisableShipping = wooSignalApp.disableShipping == 1; if (hasDisableShipping == true) { - return Container(); + return SizedBox.shrink(); } bool hasSelectedShippingType = checkoutSession.shippingType != null; return CheckoutRowLine( @@ -42,10 +42,12 @@ class CheckoutShippingTypeWidget extends StatelessWidget { CustomerAddress shippingAddress = checkoutSession.billingDetails.shippingAddress; if (shippingAddress == null || shippingAddress.customerCountry == null) { - showToastNotification(context, - title: trans("Oops"), - description: trans("Add your shipping details first"), - icon: Icons.local_shipping); + showToastNotification( + context, + title: trans("Oops"), + description: trans("Add your shipping details first"), + icon: Icons.local_shipping, + ); return; } Navigator.pushNamed(context, "/checkout-shipping-type") diff --git a/LabelStoreMax/lib/resources/widgets/compo_home_widget.dart b/LabelStoreMax/lib/resources/widgets/compo_home_widget.dart index a6d59e1..f3db3d7 100644 --- a/LabelStoreMax/lib/resources/widgets/compo_home_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/compo_home_widget.dart @@ -1,3 +1,4 @@ +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app/bootstrap/helpers.dart'; import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; @@ -29,16 +30,18 @@ class _CompoHomeWidgetState extends State { _loadHome() async { categories = await appWooSignal((api) => - api.getProductCategories(parent: 0, perPage: 20, hideEmpty: true)); + api.getProductCategories(parent: 0, perPage: 50, hideEmpty: true)); categories.sort((category1, category2) => category1.menuOrder.compareTo(category2.menuOrder)); for (var category in categories) { - List products = await appWooSignal((api) => api.getProducts( - perPage: 10, - category: category.id.toString(), - status: "publish", - stockStatus: "instock")); + List products = await appWooSignal( + (api) => api.getProducts( + perPage: 10, + category: category.id.toString(), + status: "publish", + stockStatus: "instock"), + ); if (products.isNotEmpty) { categoryAndProducts.addAll({category: products}); setState(() {}); @@ -106,7 +109,8 @@ class _CompoHomeWidgetState extends State { width: MediaQuery.of(context).size.width, fit: BoxFit.cover, ), - onTap: () => _showCategory(catProds.key)) + onTap: () => _showCategory(catProds.key), + ) : null, ConstrainedBox( constraints: BoxConstraints( @@ -123,7 +127,7 @@ class _CompoHomeWidgetState extends State { MainAxisAlignment.spaceBetween, children: [ Expanded( - child: Text( + child: AutoSizeText( catProds.key.name, style: Theme.of(context) .textTheme @@ -131,6 +135,7 @@ class _CompoHomeWidgetState extends State { .copyWith( fontWeight: FontWeight.bold, fontSize: 22), + maxLines: 1, ), ), Flexible( diff --git a/LabelStoreMax/lib/resources/widgets/future_build_widget.dart b/LabelStoreMax/lib/resources/widgets/future_build_widget.dart new file mode 100644 index 0000000..becfd75 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/future_build_widget.dart @@ -0,0 +1,43 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; + +class FutureBuildWidget extends StatelessWidget { + const FutureBuildWidget( + {Key key, + @required this.asyncFuture, + @required this.onValue, + this.onLoading}) + : super(key: key); + + final Widget Function(T value) onValue; + final Widget onLoading; + final Future asyncFuture; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: asyncFuture, + builder: (BuildContext context, AsyncSnapshot snapshot) { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + return onLoading ?? Container(); + default: + if (snapshot.hasError) { + return SizedBox.shrink(); + } else { + return onValue(snapshot.data); + } + } + }, + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/home_drawer_widget.dart b/LabelStoreMax/lib/resources/widgets/home_drawer_widget.dart index 24062e9..9454f19 100644 --- a/LabelStoreMax/lib/resources/widgets/home_drawer_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/home_drawer_widget.dart @@ -57,6 +57,8 @@ class _HomeDrawerWidgetState extends State { ), if (["compo"].contains(_themeType) == false) Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ Padding( child: Text( diff --git a/LabelStoreMax/lib/resources/widgets/notic_home_widget.dart b/LabelStoreMax/lib/resources/widgets/notic_home_widget.dart index dd23681..900ab85 100644 --- a/LabelStoreMax/lib/resources/widgets/notic_home_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/notic_home_widget.dart @@ -161,7 +161,7 @@ class _NoticHomeWidgetState extends State { } else if (mode == LoadStatus.canLoading) { body = Text(trans("release to load more")); } else { - return Container(); + return SizedBox.shrink(); } return Container( height: 55.0, diff --git a/LabelStoreMax/lib/resources/widgets/notic_theme_widget.dart b/LabelStoreMax/lib/resources/widgets/notic_theme_widget.dart index be313a6..d257fbe 100644 --- a/LabelStoreMax/lib/resources/widgets/notic_theme_widget.dart +++ b/LabelStoreMax/lib/resources/widgets/notic_theme_widget.dart @@ -60,21 +60,22 @@ class _NoticThemeWidgetState extends State { body: activeWidget, resizeToAvoidBottomInset: false, bottomNavigationBar: allNavWidgets == null - ? AppLoaderWidget() : BottomNavigationBar( - onTap: (currentIndex) => - _changeMainWidget(currentIndex, allNavWidgets), - currentIndex: _currentIndex, - unselectedItemColor: Colors.black54, - fixedColor: Colors.black87, - selectedLabelStyle: TextStyle(color: Colors.black), - unselectedLabelStyle: TextStyle( - color: Colors.black87, - ), - showSelectedLabels: false, - showUnselectedLabels: false, - items: - allNavWidgets.map((e) => e.bottomNavigationBarItem).toList(), - ), + ? AppLoaderWidget() + : BottomNavigationBar( + onTap: (currentIndex) => + _changeMainWidget(currentIndex, allNavWidgets), + currentIndex: _currentIndex, + unselectedItemColor: Colors.black54, + fixedColor: Colors.black87, + selectedLabelStyle: TextStyle(color: Colors.black), + unselectedLabelStyle: TextStyle( + color: Colors.black87, + ), + showSelectedLabels: false, + showUnselectedLabels: false, + items: + allNavWidgets.map((e) => e.bottomNavigationBarItem).toList(), + ), ); } @@ -122,12 +123,12 @@ class _NoticThemeWidgetState extends State { items.add(BottomNavItem( id: 5, bottomNavigationBarItem: - BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Account'), + BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Account'), tabWidget: (await authCheck()) ? AccountDetailPage(showLeadingBackButton: false) : AccountLandingPage( - showBackButton: false, - ), + showBackButton: false, + ), )); } return items; diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_body_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_body_widget.dart new file mode 100644 index 0000000..acad9b9 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_body_widget.dart @@ -0,0 +1,65 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/resources/widgets/product_detail_description_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_header_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_image_swiper_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_related_products_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_reviews_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_upsell_widget.dart'; +import 'package:woosignal/models/response/products.dart'; +import 'package:woosignal/models/response/woosignal_app.dart'; + +class ProductDetailBodyWidget extends StatelessWidget { + const ProductDetailBodyWidget( + {Key key, @required this.product, @required this.wooSignalApp}) + : super(key: key); + + final Product product; + final WooSignalApp wooSignalApp; + + @override + Widget build(BuildContext context) { + return ListView( + shrinkWrap: true, + children: [ + ProductDetailImageSwiperWidget( + product: product, + onTapImage: (i) => _viewProductImages(context, i)), + // + + ProductDetailHeaderWidget(product: product), + // + + ProductDetailDescriptionWidget(product: product), + // + + ProductDetailReviewsWidget( + product: product, wooSignalApp: wooSignalApp), + // + + ProductDetailUpsellWidget( + productIds: product.upsellIds, wooSignalApp: wooSignalApp), + // + + ProductDetailRelatedProductsWidget( + product: product, wooSignalApp: wooSignalApp) + // + ], + ); + } + + _viewProductImages(BuildContext context, int i) => + Navigator.pushNamed(context, "/product-images", arguments: { + "index": i, + "images": product.images.map((f) => f.src).toList() + }); +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_description_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_description_widget.dart new file mode 100644 index 0000000..8ba52b6 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_description_widget.dart @@ -0,0 +1,90 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'package:woosignal/models/response/products.dart'; + +class ProductDetailDescriptionWidget extends StatelessWidget { + const ProductDetailDescriptionWidget({Key key, @required this.product}) + : super(key: key); + + final Product product; + + @override + Widget build(BuildContext context) { + if (product.shortDescription.isEmpty && product.description.isEmpty) { + return SizedBox.shrink(); + } + + return ListView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: [ + Container( + height: 50, + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + trans("Description"), + style: + Theme.of(context).textTheme.caption.copyWith(fontSize: 18), + textAlign: TextAlign.left, + ), + if (product.shortDescription.isNotEmpty && + product.description.isNotEmpty) + MaterialButton( + child: Text( + trans("Full description"), + style: Theme.of(context) + .textTheme + .bodyText2 + .copyWith(fontSize: 14), + textAlign: TextAlign.right, + overflow: TextOverflow.ellipsis, + ), + height: 50, + minWidth: 60, + onPressed: () => _modalBottomSheetMenu(context), + ), + ], + ), + ), + Container( + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16), + child: HtmlWidget( + product.shortDescription.isNotEmpty + ? product.shortDescription + : product.description, + renderMode: RenderMode.column, onTapUrl: (String url) async { + await launch(url); + return true; + }, textStyle: Theme.of(context).textTheme.bodyText2), + ), + ], + ); + } + + _modalBottomSheetMenu(BuildContext context) { + wsModalBottom( + context, + title: trans("Description"), + bodyWidget: SingleChildScrollView( + child: HtmlWidget(product.description), + ), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_footer_actions_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_footer_actions_widget.dart new file mode 100644 index 0000000..c5964d2 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_footer_actions_widget.dart @@ -0,0 +1,128 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:flutter_app/resources/widgets/buttons.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:woosignal/models/response/products.dart'; + +class ProductDetailFooterActionsWidget extends StatelessWidget { + const ProductDetailFooterActionsWidget( + {Key key, + @required this.product, + @required this.quantity, + @required this.onAddToCart, + @required this.onViewExternalProduct, + @required this.onAddQuantity, + @required this.onRemoveQuantity}) + : super(key: key); + final Product product; + final Function onViewExternalProduct; + final Function onAddToCart; + final Function onAddQuantity; + final Function onRemoveQuantity; + final int quantity; + + @override + Widget build(BuildContext context) { + return Container( + height: 140, + padding: EdgeInsets.symmetric(horizontal: 16), + decoration: BoxDecoration( + color: ThemeColor.get(context).background, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 15.0, + spreadRadius: -17, + offset: Offset( + 0, + -10, + ), + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + (product.type != "external" + ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + trans("Quantity"), + style: Theme.of(context) + .textTheme + .bodyText1 + .copyWith(color: Colors.grey), + ), + Row( + children: [ + IconButton( + icon: Icon( + Icons.remove_circle_outline, + size: 28, + ), + onPressed: onRemoveQuantity, + ), + Text( + quantity.toString(), + style: Theme.of(context).textTheme.bodyText1, + ), + IconButton( + icon: Icon( + Icons.add_circle_outline, + size: 28, + ), + onPressed: onAddQuantity, + ), + ], + ) + ], + ) + : null), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Align( + child: Text( + formatStringCurrency( + total: + (parseWcPrice(product.price) * quantity).toString()), + style: Theme.of(context).textTheme.headline4, + textAlign: TextAlign.center, + ), + alignment: Alignment.centerLeft, + )), + product.type == "external" + ? Flexible( + child: PrimaryButton( + title: trans("Buy Product"), + action: () => onViewExternalProduct, + ), + ) + : Flexible( + child: PrimaryButton( + title: trans("Add to cart"), + action: onAddToCart, + ), + ), + ], + ), + ].where((e) => e != null).toList(), + ), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_header_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_header_widget.dart new file mode 100644 index 0000000..65458b8 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_header_widget.dart @@ -0,0 +1,73 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:woosignal/models/response/products.dart'; + +class ProductDetailHeaderWidget extends StatelessWidget { + const ProductDetailHeaderWidget({Key key, @required this.product}) + : super(key: key); + + final Product product; + + @override + Widget build(BuildContext context) { + return Container( + height: 100, + padding: EdgeInsets.symmetric( + vertical: 10, + horizontal: 16, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + product.name, + style: + Theme.of(context).textTheme.bodyText1.copyWith(fontSize: 20), + textAlign: TextAlign.left, + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + flex: 4, + ), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + formatStringCurrency(total: product.price), + style: Theme.of(context).textTheme.headline4.copyWith( + fontSize: 20, + ), + textAlign: TextAlign.right, + ), + (product.onSale == true && product.type != "variable" + ? Text( + formatStringCurrency(total: product.regularPrice), + style: TextStyle( + color: Colors.grey, + decoration: TextDecoration.lineThrough, + ), + ) + : null) + ].where((t) => t != null).toList(), + ), + flex: 2, + ) + ], + ), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_image_swiper_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_image_swiper_widget.dart new file mode 100644 index 0000000..ab74ccb --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_image_swiper_widget.dart @@ -0,0 +1,44 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/resources/widgets/cached_image_widget.dart'; +import 'package:flutter_swiper/flutter_swiper.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:woosignal/models/response/products.dart'; + +class ProductDetailImageSwiperWidget extends StatelessWidget { + const ProductDetailImageSwiperWidget( + {Key key, @required this.product, @required this.onTapImage}) + : super(key: key); + + final Product product; + final void Function(int i) onTapImage; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: MediaQuery.of(context).size.height * 0.40, + child: SizedBox( + child: Swiper( + itemBuilder: (BuildContext context, int index) => CachedImageWidget( + image: product.images.isNotEmpty + ? product.images[index].src + : getEnv("PRODUCT_PLACEHOLDER_IMAGE"), + ), + itemCount: product.images.isEmpty ? 1 : product.images.length, + viewportFraction: 0.85, + scale: 0.9, + onTap: onTapImage, + ), + ), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_related_products_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_related_products_widget.dart new file mode 100644 index 0000000..a940c46 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_related_products_widget.dart @@ -0,0 +1,80 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; +import 'package:flutter_app/resources/widgets/future_build_widget.dart'; +import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:woosignal/models/response/products.dart'; +import 'package:woosignal/models/response/woosignal_app.dart'; + +class ProductDetailRelatedProductsWidget extends StatelessWidget { + const ProductDetailRelatedProductsWidget( + {Key key, @required this.product, @required this.wooSignalApp}) + : super(key: key); + + final Product product; + final WooSignalApp wooSignalApp; + + @override + Widget build(BuildContext context) { + if (wooSignalApp.showRelatedProducts == false) { + return SizedBox.shrink(); + } + return ListView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: [ + Container( + height: 50, + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + trans("Related products"), + style: + Theme.of(context).textTheme.caption.copyWith(fontSize: 18), + textAlign: TextAlign.left, + ), + ], + ), + ), + Container( + height: 200, + child: FutureBuildWidget>( + asyncFuture: fetchRelated(), + onValue: (relatedProducts) { + return ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: relatedProducts + .map((e) => Container( + width: MediaQuery.of(context).size.width / 2.2, + child: ProductItemContainer(product: e))) + .toList(), + ); + }, + onLoading: AppLoaderWidget(), + ), + ), + ], + ); + } + + Future> fetchRelated() async { + return await appWooSignal( + (api) => api.getProducts(perPage: 100, include: product.relatedIds), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_review_tile_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_review_tile_widget.dart new file mode 100644 index 0000000..56535fb --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_review_tile_widget.dart @@ -0,0 +1,90 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:woosignal/models/response/product_review.dart'; + +class ProductDetailReviewTileWidget extends StatefulWidget { + ProductDetailReviewTileWidget({Key key, @required this.productReview}); + final ProductReview productReview; + + @override + _ProductDetailReviewTileWidgetState createState() => + _ProductDetailReviewTileWidgetState(); +} + +class _ProductDetailReviewTileWidgetState + extends State { + int _maxLines = 3; + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only(top: 5, left: 16, right: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + RatingBarIndicator( + rating: widget.productReview.rating.toDouble(), + itemBuilder: (context, index) => Icon( + Icons.star, + color: Colors.amber, + ), + itemCount: 5, + itemSize: 20.0, + direction: Axis.horizontal, + ), + Text(widget.productReview.reviewer), + Text( + formatDateTime("MMM d, yyyy").format( + parseDateTime(widget.productReview.dateCreated), + ), + ), + ], + ), + ListTile( + title: Container( + margin: EdgeInsets.only(top: 10), + child: Text(parseHtmlString(widget.productReview.review), + maxLines: _maxLines, + overflow: _maxLines != null + ? TextOverflow.ellipsis + : TextOverflow.visible), + ), + contentPadding: EdgeInsets.all(0), + minVerticalPadding: 0), + if (_maxLines != null && widget.productReview.review.length > 115) + InkWell( + child: Text(trans("More"), + style: Theme.of(context) + .textTheme + .bodyText2 + .copyWith(fontWeight: FontWeight.bold)), + onTap: () => setState(() { + _maxLines = null; + }), + ) + ], + ), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide(color: Colors.black12, width: 1), + )), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_reviews_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_reviews_widget.dart new file mode 100644 index 0000000..2ef330a --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_reviews_widget.dart @@ -0,0 +1,152 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:flutter_app/resources/widgets/future_build_widget.dart'; +import 'package:flutter_app/resources/widgets/product_detail_review_tile_widget.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:woosignal/models/response/product_review.dart'; +import 'package:woosignal/models/response/products.dart'; +import 'package:woosignal/models/response/woosignal_app.dart'; + +class ProductDetailReviewsWidget extends StatefulWidget { + ProductDetailReviewsWidget( + {@required this.product, @required this.wooSignalApp}); + final Product product; + final WooSignalApp wooSignalApp; + + @override + _ProductDetailReviewsWidgetState createState() => + _ProductDetailReviewsWidgetState(); +} + +class _ProductDetailReviewsWidgetState + extends State { + bool _ratingExpanded = false; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.product.reviewsAllowed == false || + widget.wooSignalApp.showProductReviews == false) { + return SizedBox.shrink(); + } + + return Row( + children: [ + Expanded( + child: ExpansionTile( + textColor: ThemeColor.get(context).primaryAccent, + iconColor: ThemeColor.get(context).primaryAccent, + tilePadding: EdgeInsets.symmetric(horizontal: 16), + childrenPadding: EdgeInsets.all(0), + title: Text("${trans("Reviews")} (${widget.product.ratingCount})"), + onExpansionChanged: (value) { + setState(() { + _ratingExpanded = value; + }); + }, + trailing: Container( + width: MediaQuery.of(context).size.width / 1.8, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + RatingBarIndicator( + rating: double.parse(widget.product.averageRating), + itemBuilder: (context, index) => Icon( + Icons.star, + color: Colors.amber, + ), + itemCount: 5, + itemSize: 25.0, + direction: Axis.horizontal, + ), + Icon( + _ratingExpanded + ? Icons.keyboard_arrow_down_rounded + : Icons.keyboard_arrow_up_rounded, + size: 30) + ], + ), + ), + initiallyExpanded: false, + children: [ + _ratingExpanded == true + ? FutureBuildWidget>( + asyncFuture: fetchReviews(), + onValue: (reviews) { + int reviewsCount = reviews.length; + List childrenWidgets = []; + List children = reviews + .map((review) => ProductDetailReviewTileWidget( + productReview: review)) + .toList(); + childrenWidgets.addAll(children); + + if (reviewsCount >= 5) { + childrenWidgets.add( + Container( + child: ListTile( + contentPadding: + EdgeInsets.symmetric(horizontal: 16), + title: Text( + trans('See More Reviews'), + ), + onTap: () => Navigator.pushNamed( + context, "/product-reviews", + arguments: widget.product), + ), + ), + ); + } + return ListView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(0), + children: reviews.isEmpty + ? [ + Container( + child: ListTile( + title: Text( + trans('There are no reviews yet.'), + ), + ), + ) + ] + : childrenWidgets, + ); + }, + onLoading: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: CupertinoActivityIndicator(), + ), + ) + : null, + ].where((element) => element != null).toList(), + )), + ], + ); + } + + Future> fetchReviews() async { + return await appWooSignal( + (api) => api.getProductReviews( + perPage: 5, product: [widget.product.id], status: "approved"), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_detail_upsell_widget.dart b/LabelStoreMax/lib/resources/widgets/product_detail_upsell_widget.dart new file mode 100644 index 0000000..0ed7872 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_detail_upsell_widget.dart @@ -0,0 +1,170 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_app/app/controllers/product_loader_controller.dart'; +import 'package:flutter_app/resources/widgets/app_loader_widget.dart'; +import 'package:flutter_app/resources/widgets/no_results_for_products_widget.dart'; +import 'package:flutter_app/resources/widgets/woosignal_ui.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:nylo_framework/nylo_framework.dart'; +import 'package:pull_to_refresh/pull_to_refresh.dart'; +import 'package:woosignal/models/response/products.dart'; +import 'package:woosignal/models/response/woosignal_app.dart'; + +class ProductDetailUpsellWidget extends StatefulWidget { + ProductDetailUpsellWidget( + {@required this.productIds, @required this.wooSignalApp}); + final List productIds; + final WooSignalApp wooSignalApp; + + @override + _ProductDetailUpsellWidgetState createState() => + _ProductDetailUpsellWidgetState(); +} + +class _ProductDetailUpsellWidgetState extends State { + final RefreshController _refreshControllerUpsell = + RefreshController(initialRefresh: false); + + final ProductLoaderController _productLoaderController = + ProductLoaderController(); + + bool _shouldStopRequests = false, _isLoading = true; + + @override + void initState() { + super.initState(); + fetchProducts(); + } + + @override + Widget build(BuildContext context) { + List products = _productLoaderController.getResults(); + if (widget.productIds.isEmpty || + products.isEmpty || + widget.wooSignalApp.showUpsellProducts == false) { + return SizedBox.shrink(); + } + return ListView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: [ + Container( + height: 50, + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + trans("${trans('You may also like')}…"), + style: + Theme.of(context).textTheme.caption.copyWith(fontSize: 18), + textAlign: TextAlign.left, + ), + ], + ), + ), + _isLoading == true + ? AppLoaderWidget() + : Container( + height: 200, + child: SmartRefresher( + enablePullDown: true, + enablePullUp: true, + footer: CustomFooter( + builder: (BuildContext context, LoadStatus mode) { + Widget body; + if (mode == LoadStatus.idle) { + body = Text(trans("pull up load")); + } else if (mode == LoadStatus.loading) { + body = CupertinoActivityIndicator(); + } else if (mode == LoadStatus.failed) { + body = Text(trans("Load Failed! Click retry!")); + } else if (mode == LoadStatus.canLoading) { + body = Text(trans("release to load more")); + } else { + return SizedBox.shrink(); + } + return Container( + height: 55.0, + child: Center(child: body), + ); + }, + ), + controller: _refreshControllerUpsell, + onRefresh: _onRefresh, + onLoading: _onLoading, + child: (products.length != null && products.isNotEmpty + ? StaggeredGridView.countBuilder( + crossAxisCount: 2, + scrollDirection: Axis.horizontal, + itemCount: products.length, + itemBuilder: (BuildContext context, int index) { + return Container( + height: 250, + child: ProductItemContainer( + product: products[index], + ), + ); + }, + staggeredTileBuilder: (int index) { + return StaggeredTile.fit(2); + }, + mainAxisSpacing: 4.0, + crossAxisSpacing: 4.0, + ) + : NoResultsForProductsWidget()), + ), + ), + ], + ); + } + + _onRefresh() async { + _productLoaderController.clear(); + _shouldStopRequests = false; + + await fetchProducts(); + _refreshControllerUpsell.refreshCompleted(); + } + + _onLoading() async { + await fetchProducts(); + + if (mounted) { + setState(() {}); + if (_shouldStopRequests) { + _refreshControllerUpsell.loadNoData(); + } else { + _refreshControllerUpsell.loadComplete(); + } + } + } + + Future fetchProducts() async { + await _productLoaderController.loadProducts( + hasResults: (result) { + if (result == false) { + setState(() { + _shouldStopRequests = true; + }); + return false; + } + return true; + }, + didFinish: () => setState(() { + _isLoading = false; + }), + productIds: widget.productIds); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/product_review_item_container_widget.dart b/LabelStoreMax/lib/resources/widgets/product_review_item_container_widget.dart new file mode 100644 index 0000000..75397e7 --- /dev/null +++ b/LabelStoreMax/lib/resources/widgets/product_review_item_container_widget.dart @@ -0,0 +1,65 @@ +// Label StoreMax +// +// Created by Anthony Gordon. +// 2022, WooSignal Ltd. All rights reserved. +// + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +import 'package:flutter/material.dart'; +import 'package:flutter_app/bootstrap/helpers.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:woosignal/models/response/product_review.dart'; + +class ProductReviewItemContainerWidget extends StatelessWidget { + const ProductReviewItemContainerWidget( + {Key key, @required this.productReview}) + : super(key: key); + + final ProductReview productReview; + + @override + Widget build(BuildContext context) { + return Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + RatingBarIndicator( + rating: productReview.rating.toDouble(), + itemBuilder: (context, index) => Icon( + Icons.star, + color: Colors.amber, + ), + itemCount: 5, + itemSize: 20.0, + direction: Axis.horizontal, + ), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Text(parseHtmlString(productReview.review)), + ), + Row( + children: [ + Text(productReview.reviewer), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8), + child: Icon( + Icons.circle_rounded, + size: 3, + ), + ), + Text( + formatDateTime("MMM d, yyyy").format( + parseDateTime(productReview.dateCreated), + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/LabelStoreMax/lib/resources/widgets/woosignal_ui.dart b/LabelStoreMax/lib/resources/widgets/woosignal_ui.dart index 0ac09c0..6578dd6 100644 --- a/LabelStoreMax/lib/resources/widgets/woosignal_ui.dart +++ b/LabelStoreMax/lib/resources/widgets/woosignal_ui.dart @@ -22,7 +22,6 @@ import 'package:flutter_app/resources/widgets/no_results_for_products_widget.dar import 'package:flutter_app/resources/widgets/top_nav_widget.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_swiper/flutter_swiper.dart'; -import 'package:hexcolor/hexcolor.dart'; import 'package:nylo_support/helpers/helper.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:woosignal/models/response/products.dart'; @@ -303,7 +302,7 @@ class CheckoutMetaLine extends StatelessWidget { List wsBoxShadow({double blurRadius}) => [ BoxShadow( - color: HexColor("#e8e8e8"), + color: Color(0xFFE8E8E8), blurRadius: blurRadius ?? 15.0, spreadRadius: 0, offset: Offset( @@ -452,7 +451,10 @@ class ProductItemContainer extends StatelessWidget { ].where((e) => e != null).toList(), ), ), - onTap: () => onTap(product), + onTap: () => onTap != null + ? onTap(product) + : Navigator.pushNamed(context, "/product-detail", + arguments: product), ), ); } diff --git a/LabelStoreMax/lib/routes/router.dart b/LabelStoreMax/lib/routes/router.dart index afb474c..864bbaf 100644 --- a/LabelStoreMax/lib/routes/router.dart +++ b/LabelStoreMax/lib/routes/router.dart @@ -17,9 +17,11 @@ import 'package:flutter_app/resources/pages/coupon_page.dart'; import 'package:flutter_app/resources/pages/customer_countries.dart'; import 'package:flutter_app/resources/pages/home.dart'; import 'package:flutter_app/resources/pages/home_search.dart'; +import 'package:flutter_app/resources/pages/leave_review_page.dart'; import 'package:flutter_app/resources/pages/no_connection_page.dart'; import 'package:flutter_app/resources/pages/product_detail.dart'; import 'package:flutter_app/resources/pages/product_image_viewer_page.dart'; +import 'package:flutter_app/resources/pages/product_reviews_page.dart'; import 'package:flutter_app/resources/pages/wishlist_page_widget.dart'; import 'package:flutter_app/resources/widgets/checkout_paypal.dart'; import 'package:nylo_support/router/router.dart'; @@ -47,6 +49,12 @@ appRouter() => nyRoutes((router) { router.route("/product-detail", (context) => ProductDetailPage(), transition: PageTransitionType.rightToLeftWithFade); + router.route("/product-reviews", (context) => ProductReviewsPage(), + transition: PageTransitionType.rightToLeftWithFade); + + router.route("/product-leave-review", (context) => LeaveReviewPage(), + transition: PageTransitionType.rightToLeftWithFade); + router.route("/product-images", (context) => ProductImageViewerPage(), transition: PageTransitionType.fade); diff --git a/LabelStoreMax/pubspec.lock b/LabelStoreMax/pubspec.lock index ff9c09c..b2a72f3 100644 --- a/LabelStoreMax/pubspec.lock +++ b/LabelStoreMax/pubspec.lock @@ -21,7 +21,7 @@ packages: name: animate_do url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" archive: dependency: transitive description: @@ -43,6 +43,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.8.2" + audio_session: + dependency: transitive + description: + name: audio_session + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.6+1" auto_size_text: dependency: "direct main" description: @@ -99,6 +106,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + chewie: + dependency: transitive + description: + name: chewie + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.2" cli_util: dependency: transitive description: @@ -249,6 +263,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.3" + flutter_rating_bar: + dependency: "direct main" + description: + name: flutter_rating_bar + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" flutter_secure_storage: dependency: transitive description: @@ -311,7 +332,7 @@ packages: name: flutter_stripe url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" flutter_styled_toast: dependency: "direct main" description: @@ -319,6 +340,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" + flutter_svg: + dependency: transitive + description: + name: flutter_svg + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" flutter_swiper: dependency: "direct main" description: @@ -343,6 +371,20 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_widget_from_html: + dependency: "direct main" + description: + name: flutter_widget_from_html + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4" + flutter_widget_from_html_core: + dependency: transitive + description: + name: flutter_widget_from_html_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.4" freezed_annotation: dependency: transitive description: @@ -350,6 +392,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + fwfh_cached_network_image: + dependency: transitive + description: + name: fwfh_cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0+2" + fwfh_chewie: + dependency: transitive + description: + name: fwfh_chewie + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0+1" + fwfh_just_audio: + dependency: transitive + description: + name: fwfh_just_audio + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2+2" + fwfh_selectable_text: + dependency: transitive + description: + name: fwfh_selectable_text + url: "https://pub.dartlang.org" + source: hosted + version: "0.8.3+1" + fwfh_svg: + dependency: transitive + description: + name: fwfh_svg + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.2" + fwfh_text_style: + dependency: transitive + description: + name: fwfh_text_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.2" + fwfh_url_launcher: + dependency: transitive + description: + name: fwfh_url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+3" + fwfh_webview: + dependency: transitive + description: + name: fwfh_webview + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.2+1" glob: dependency: transitive description: @@ -364,13 +462,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.0" - hexcolor: - dependency: "direct main" - description: - name: hexcolor - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.5" html: dependency: "direct main" description: @@ -420,6 +511,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.4.0" + just_audio: + dependency: transitive + description: + name: just_audio + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.18" + just_audio_platform_interface: + dependency: transitive + description: + name: just_audio_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + just_audio_web: + dependency: transitive + description: + name: just_audio_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.2" lints: dependency: "direct dev" description: @@ -462,6 +574,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.3" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" nylo_framework: dependency: "direct main" description: @@ -503,7 +622,7 @@ packages: name: page_transition url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" path: dependency: transitive description: @@ -511,6 +630,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" + path_drawing: + dependency: transitive + description: + name: path_drawing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + path_parsing: + dependency: transitive + description: + name: path_parsing + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" path_provider: dependency: transitive description: @@ -588,6 +721,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.3" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" pub_semver: dependency: transitive description: @@ -622,7 +762,7 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.12" shared_preferences_android: dependency: transitive description: @@ -732,21 +872,21 @@ packages: name: stripe_android url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" stripe_ios: dependency: transitive description: name: stripe_ios url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" stripe_platform_interface: dependency: transitive description: name: stripe_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" synchronized: dependency: transitive description: @@ -795,7 +935,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "6.0.17" + version: "6.0.18" url_launcher_android: dependency: transitive description: @@ -866,6 +1006,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + video_player: + dependency: transitive + description: + name: video_player + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.13" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + video_player_web: + dependency: transitive + description: + name: video_player_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + wakelock: + dependency: transitive + description: + name: wakelock + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.6" + wakelock_macos: + dependency: transitive + description: + name: wakelock_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + wakelock_platform_interface: + dependency: transitive + description: + name: wakelock_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + wakelock_web: + dependency: transitive + description: + name: wakelock_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.0" + wakelock_windows: + dependency: transitive + description: + name: wakelock_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" watcher: dependency: transitive description: @@ -914,7 +1110,7 @@ packages: name: woosignal url: "https://pub.dartlang.org" source: hosted - version: "3.0.3" + version: "3.0.5" wp_json_api: dependency: "direct main" description: @@ -945,4 +1141,4 @@ packages: version: "3.1.0" sdks: dart: ">=2.15.0 <3.0.0" - flutter: ">=2.5.0" + flutter: ">=2.8.0" diff --git a/LabelStoreMax/pubspec.yaml b/LabelStoreMax/pubspec.yaml index 653b124..5f379b1 100644 --- a/LabelStoreMax/pubspec.yaml +++ b/LabelStoreMax/pubspec.yaml @@ -1,7 +1,7 @@ # Official WooSignal App Template for WooCommerce # Label StoreMax -# Version: 5.6.2 +# Version: 5.7.0 # Author: Anthony Gordon # Homepage: https://woosignal.com # Documentation: https://woosignal.com/docs/app/label-storemax @@ -28,10 +28,10 @@ dependencies: google_fonts: ^2.2.0 analyzer: ^1.5.0 intl: ^0.17.0 - page_transition: ^2.0.4 + page_transition: ^2.0.5 nylo_framework: ^2.1.4 - woosignal: ^3.0.3 - flutter_stripe: ^2.1.0 + woosignal: ^3.0.5 + flutter_stripe: ^2.1.1 wp_json_api: ^3.1.3 cached_network_image: ^3.2.0 package_info: ^2.0.2 @@ -41,17 +41,18 @@ dependencies: webview_flutter: ^2.3.1 pull_to_refresh: 2.0.0 flutter_swiper: ^1.1.6 - url_launcher: ^6.0.17 + url_launcher: ^6.0.18 flutter_styled_toast: ^2.0.0 - animate_do: ^2.0.0 + animate_do: ^2.1.0 bubble_tab_indicator: ^0.1.5 status_alert: ^0.1.3 math_expressions: ^2.3.0 validated: ^2.0.0 - hexcolor: ^2.0.5 flutter_spinkit: ^5.1.0 auto_size_text: ^3.0.0 html: ^0.15.0 + flutter_widget_from_html: ^0.8.4 + flutter_rating_bar: ^4.0.0 flutter_staggered_grid_view: ^0.4.1 # firebase_messaging: ^11.2.3 # firebase_core: ^1.10.5 diff --git a/README.md b/README.md index a722d12..be03dd8 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # WooCommerce App: Label StoreMax -### Label StoreMax - v5.6.2 +### Label StoreMax - v5.7.0 [Official WooSignal WooCommerce App](https://woosignal.com)