From c2b70eca3d6fed290f0e6976720bc83418e48a5d Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Thu, 27 Jun 2024 13:52:51 +0200 Subject: [PATCH] fix: product page --- .../product_page_configuration.dart | 79 +++++-- .../product_page_localization.dart | 2 +- .../lib/src/services/category_service.dart | 30 +-- .../lib/src/ui/components/product_item.dart | 73 +++---- .../src/ui/components/weekly_discount.dart | 19 +- .../lib/src/ui/product_page.dart | 202 +++++++----------- .../lib/src/ui/product_page_screen.dart | 2 +- .../src/ui/widgets/horizontal_list_items.dart | 69 +++--- .../src/ui/widgets/product_item_popup.dart | 78 ++++--- .../lib/src/ui/widgets/spaced_wrap.dart | 140 +++--------- .../lib/src/models/product.dart | 6 +- 11 files changed, 316 insertions(+), 384 deletions(-) diff --git a/packages/flutter_product_page/lib/src/configuration/product_page_configuration.dart b/packages/flutter_product_page/lib/src/configuration/product_page_configuration.dart index 6cd7e81..1d469d4 100644 --- a/packages/flutter_product_page/lib/src/configuration/product_page_configuration.dart +++ b/packages/flutter_product_page/lib/src/configuration/product_page_configuration.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:flutter_product_page/src/services/shopping_cart_notifier.dart"; import "package:flutter_product_page/src/ui/widgets/product_item_popup.dart"; import "package:flutter_shopping/flutter_shopping.dart"; @@ -10,7 +11,7 @@ class ProductPageConfiguration { required this.getProducts, required this.onAddToCart, required this.onNavigateToShoppingCart, - this.navigateToShoppingCartBuilder, + this.navigateToShoppingCartBuilder = _defaultNavigateToShoppingCartBuilder, this.initialShopId, this.productBuilder, this.onShopSelectionChange, @@ -20,7 +21,7 @@ class ProductPageConfiguration { this.categoryStylingConfiguration = const ProductPageCategoryStylingConfiguration(), this.pagePadding = const EdgeInsets.all(4), - this.appBar, + this.appBar = _defaultAppBar, this.bottomNavigationBar, Function( BuildContext context, @@ -50,8 +51,7 @@ class ProductPageConfiguration { ); _onProductDetail = onProductDetail; - _onProductDetail ??= - (BuildContext context, Product product) async { + _onProductDetail ??= (BuildContext context, Product product) async { var theme = Theme.of(context); await showModalBottomSheet( @@ -88,8 +88,8 @@ class ProductPageConfiguration { }; _getDiscountDescription = getDiscountDescription; - _getDiscountDescription ??= - (Product product) => "${product.name} is on sale!"; + _getDiscountDescription ??= (Product product) => + "${product.name}, now for ${product.discountPrice} each"; } /// The shop that is initially selected. @@ -109,8 +109,7 @@ class ProductPageConfiguration { /// for each product in their seperated category. This builder should only /// build the widget for one specific product. This builder has a default /// in-case the developer does not override it. - Widget Function(BuildContext context, Product product)? - productBuilder; + Widget Function(BuildContext context, Product product)? productBuilder; late Widget Function(BuildContext context, Product product)? _productPopupBuilder; @@ -122,14 +121,13 @@ class ProductPageConfiguration { Widget Function(BuildContext context, Product product) get productPopupBuilder => _productPopupBuilder!; - late Function(BuildContext context, Product product)? - _onProductDetail; + late Function(BuildContext context, Product product)? _onProductDetail; /// This function handles the creation of the product detail popup. This /// function has a default in-case the developer does not override it. /// The default intraction is a popup, but this can be overriden. - Function(BuildContext context, Product product) - get onProductDetail => _onProductDetail!; + Function(BuildContext context, Product product) get onProductDetail => + _onProductDetail!; late Widget Function(BuildContext context)? _noContentBuilder; @@ -139,7 +137,11 @@ class ProductPageConfiguration { /// The builder for the shopping cart. This builder should return a widget /// that navigates to the shopping cart overview page. - Widget Function(BuildContext context)? navigateToShoppingCartBuilder; + Widget Function( + BuildContext context, + ProductPageConfiguration configuration, + ShoppingCartNotifier notifier, + ) navigateToShoppingCartBuilder; late Widget Function( BuildContext context, @@ -188,5 +190,54 @@ class ProductPageConfiguration { final Widget? bottomNavigationBar; /// Optional app bar that you can pass to the order detail screen. - final PreferredSizeWidget? appBar; + final AppBar Function(BuildContext context)? appBar; +} + +AppBar _defaultAppBar( + BuildContext context, +) { + var theme = Theme.of(context); + + return AppBar( + leading: IconButton(onPressed: () {}, icon: const Icon(Icons.person)), + actions: [ + IconButton(onPressed: () {}, icon: const Icon(Icons.filter_alt)), + ], + title: Text( + "Product page", + style: theme.textTheme.headlineLarge, + ), + ); +} + +Widget _defaultNavigateToShoppingCartBuilder( + BuildContext context, + ProductPageConfiguration configuration, + ShoppingCartNotifier notifier, +) { + var theme = Theme.of(context); + + return ListenableBuilder( + listenable: notifier, + builder: (context, widget) => FilledButton( + onPressed: configuration.getProductsInShoppingCart?.call() != 0 + ? configuration.onNavigateToShoppingCart + : null, + style: theme.filledButtonTheme.style?.copyWith( + backgroundColor: WidgetStateProperty.all( + theme.colorScheme.primary, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: Text( + configuration.localizations.navigateToShoppingCart, + style: theme.textTheme.displayLarge, + ), + ), + ), + ); } diff --git a/packages/flutter_product_page/lib/src/configuration/product_page_localization.dart b/packages/flutter_product_page/lib/src/configuration/product_page_localization.dart index 16d8722..f2bbe3f 100644 --- a/packages/flutter_product_page/lib/src/configuration/product_page_localization.dart +++ b/packages/flutter_product_page/lib/src/configuration/product_page_localization.dart @@ -3,7 +3,7 @@ class ProductPageLocalization { /// Default constructor const ProductPageLocalization({ this.navigateToShoppingCart = "View shopping cart", - this.discountTitle = "Discount", + this.discountTitle = "Weekly offer", this.failedToLoadImageExplenation = "Failed to load image", this.close = "Close", }); diff --git a/packages/flutter_product_page/lib/src/services/category_service.dart b/packages/flutter_product_page/lib/src/services/category_service.dart index 593831b..5d39843 100644 --- a/packages/flutter_product_page/lib/src/services/category_service.dart +++ b/packages/flutter_product_page/lib/src/services/category_service.dart @@ -19,12 +19,13 @@ Product onAddToCartWrapper( /// Generates a [CategoryList] from a list of [Product]s and a /// [ProductPageConfiguration]. -CategoryList getCategoryList( +Widget getCategoryList( BuildContext context, ProductPageConfiguration configuration, ShoppingCartNotifier shoppingCartNotifier, List products, ) { + var theme = Theme.of(context); var categorizedProducts = >{}; for (var product in products) { if (!categorizedProducts.containsKey(product.category)) { @@ -43,8 +44,7 @@ CategoryList getCategoryList( : ProductItem( product: product, onProductDetail: configuration.onProductDetail, - onAddToCart: (Product product) => - onAddToCartWrapper( + onAddToCart: (Product product) => onAddToCartWrapper( configuration, shoppingCartNotifier, product, @@ -59,15 +59,19 @@ CategoryList getCategoryList( ); categories.add(category); }); - - return CategoryList( - title: configuration.categoryStylingConfiguration.title, - titleStyle: configuration.categoryStylingConfiguration.titleStyle, - customTitle: configuration.categoryStylingConfiguration.customTitle, - headerCentered: configuration.categoryStylingConfiguration.headerCentered, - headerStyling: configuration.categoryStylingConfiguration.headerStyling, - isCategoryCollapsible: - configuration.categoryStylingConfiguration.isCategoryCollapsible, - content: categories, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + for (var category in categories) ...[ + Text( + category.name!, + style: theme.textTheme.titleMedium, + ), + Column( + children: category.content, + ), + const SizedBox(height: 16), + ], + ], ); } diff --git a/packages/flutter_product_page/lib/src/ui/components/product_item.dart b/packages/flutter_product_page/lib/src/ui/components/product_item.dart index beb3801..ffcea2c 100644 --- a/packages/flutter_product_page/lib/src/ui/components/product_item.dart +++ b/packages/flutter_product_page/lib/src/ui/components/product_item.dart @@ -18,8 +18,7 @@ class ProductItem extends StatelessWidget { final Product product; /// Function to call when the product detail is requested. - final Function(BuildContext context, Product selectedProduct) - onProductDetail; + final Function(BuildContext context, Product selectedProduct) onProductDetail; /// Function to call when the product is added to the cart. final Function(Product selectedProduct) onAddToCart; @@ -76,7 +75,10 @@ class ProductItem extends StatelessWidget { padding: const EdgeInsets.only(left: 4), child: IconButton( onPressed: () => onProductDetail(context, product), - icon: const Icon(Icons.info_outline), + icon: Icon( + Icons.info_outline, + color: theme.colorScheme.primary, + ), ), ); @@ -84,10 +86,7 @@ class ProductItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ _PriceLabel( - price: product.price, - discountPrice: (product.hasDiscount && product.discountPrice != null) - ? product.discountPrice - : null, + product: product, ), _AddToCardButton( product: product, @@ -113,42 +112,36 @@ class ProductItem extends StatelessWidget { class _PriceLabel extends StatelessWidget { const _PriceLabel({ - required this.price, - required this.discountPrice, + required this.product, }); - final double price; - final double? discountPrice; + final Product product; @override Widget build(BuildContext context) { var theme = Theme.of(context); - if (discountPrice == null) - return Text( - price.toStringAsFixed(2), - style: theme.textTheme.bodyMedium, - ); - else - return Row( - children: [ + return Row( + children: [ + if (product.hasDiscount) ...[ Text( - price.toStringAsFixed(2), + product.price.toStringAsFixed(2), style: theme.textTheme.bodySmall?.copyWith( - fontSize: 10, - color: theme.colorScheme.primary, decoration: TextDecoration.lineThrough, ), + textAlign: TextAlign.center, ), - Padding( - padding: const EdgeInsets.only(left: 4.0), - child: Text( - discountPrice!.toStringAsFixed(2), - style: theme.textTheme.bodyMedium, - ), - ), + const SizedBox(width: 4), ], - ); + Text( + product.hasDiscount + ? product.discountPrice!.toStringAsFixed(2) + : product.price.toStringAsFixed(2), + style: theme.textTheme.bodySmall, + textAlign: TextAlign.center, + ), + ], + ); } } @@ -166,28 +159,22 @@ class _AddToCardButton extends StatelessWidget { @override Widget build(BuildContext context) { var theme = Theme.of(context); - return SizedBox( + return Container( + decoration: BoxDecoration( + color: theme.colorScheme.primary, + borderRadius: BorderRadius.circular(4), + ), width: boxSize, height: boxSize, child: Center( child: IconButton( padding: EdgeInsets.zero, - icon: Icon( + icon: const Icon( Icons.add, - color: theme.primaryColor, + color: Colors.white, size: 20, ), onPressed: () => onAddToCart(product), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - theme.colorScheme.secondary, - ), - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - ), - ), - ), ), ), ); diff --git a/packages/flutter_product_page/lib/src/ui/components/weekly_discount.dart b/packages/flutter_product_page/lib/src/ui/components/weekly_discount.dart index edce5ad..e82a270 100644 --- a/packages/flutter_product_page/lib/src/ui/components/weekly_discount.dart +++ b/packages/flutter_product_page/lib/src/ui/components/weekly_discount.dart @@ -18,7 +18,7 @@ class WeeklyDiscount extends StatelessWidget { final Product product; /// The top padding of the widget. - static const double topPadding = 32.0; + static const double topPadding = 20; @override Widget build(BuildContext context) { @@ -28,9 +28,7 @@ class WeeklyDiscount extends StatelessWidget { padding: const EdgeInsets.all(20.0), child: Text( configuration.getDiscountDescription!(product), - style: theme.textTheme.titleMedium?.copyWith( - color: theme.colorScheme.primary, - ), + style: theme.textTheme.bodyMedium, textAlign: TextAlign.left, ), ); @@ -73,9 +71,9 @@ class WeeklyDiscount extends StatelessWidget { ); var topText = DecoratedBox( - decoration: BoxDecoration( - color: theme.primaryColor, - borderRadius: const BorderRadius.only( + decoration: const BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), @@ -88,10 +86,8 @@ class WeeklyDiscount extends StatelessWidget { horizontal: 16, ), child: Text( - configuration.localizations.discountTitle.toUpperCase(), - style: theme.textTheme.titleMedium?.copyWith( - color: theme.colorScheme.onPrimary, - ), + configuration.localizations.discountTitle, + style: theme.textTheme.headlineSmall, textAlign: TextAlign.left, ), ), @@ -100,7 +96,6 @@ class WeeklyDiscount extends StatelessWidget { var boxDecoration = BoxDecoration( border: Border.all( - color: theme.primaryColor, width: 1.0, ), borderRadius: BorderRadius.circular(4.0), diff --git a/packages/flutter_product_page/lib/src/ui/product_page.dart b/packages/flutter_product_page/lib/src/ui/product_page.dart index 5c99990..bbdfe8f 100644 --- a/packages/flutter_product_page/lib/src/ui/product_page.dart +++ b/packages/flutter_product_page/lib/src/ui/product_page.dart @@ -121,6 +121,7 @@ class _ProductPage extends StatelessWidget { Widget build(BuildContext context) { var pageContent = SingleChildScrollView( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ ShopSelector( configuration: configuration, @@ -142,63 +143,13 @@ class _ProductPage extends StatelessWidget { pageContent, Align( alignment: Alignment.bottomCenter, - child: configuration.navigateToShoppingCartBuilder != null - ? configuration.navigateToShoppingCartBuilder!(context) - : _NavigateToShoppingCartButton( - configuration: configuration, - shoppingCartNotifier: shoppingCartNotifier, - ), - ), - ], - ); - } -} - -class _NavigateToShoppingCartButton extends StatelessWidget { - const _NavigateToShoppingCartButton({ - required this.configuration, - required this.shoppingCartNotifier, - }); - - final ProductPageConfiguration configuration; - final ShoppingCartNotifier shoppingCartNotifier; - - @override - Widget build(BuildContext context) { - var theme = Theme.of(context); - - String getProductsInShoppingCartLabel() { - var fun = configuration.getProductsInShoppingCart; - - if (fun == null) { - return ""; - } - - return "(${fun()})"; - } - - return FilledButton( - onPressed: configuration.onNavigateToShoppingCart, - style: theme.filledButtonTheme.style?.copyWith( - backgroundColor: WidgetStateProperty.all( - theme.colorScheme.primary, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: ListenableBuilder( - listenable: shoppingCartNotifier, - builder: (BuildContext context, Widget? _) => Text( - """${configuration.localizations.navigateToShoppingCart.toUpperCase()} ${getProductsInShoppingCartLabel()}""", - style: theme.textTheme.titleMedium?.copyWith( - color: theme.colorScheme.onPrimary, - ), + child: configuration.navigateToShoppingCartBuilder( + context, + configuration, + shoppingCartNotifier, ), ), - ), + ], ); } } @@ -215,74 +166,87 @@ class _ShopContents extends StatelessWidget { final ShoppingCartNotifier shoppingCartNotifier; @override - Widget build(BuildContext context) => Padding( - padding: EdgeInsets.symmetric( - horizontal: configuration.pagePadding.horizontal, + Widget build(BuildContext context) { + var theme = Theme.of(context); + return Padding( + padding: EdgeInsets.symmetric( + horizontal: configuration.pagePadding.horizontal, + ), + child: FutureBuilder( + // ignore: discarded_futures + future: configuration.getProducts( + selectedShopService.selectedShop!, ), - child: FutureBuilder( - // ignore: discarded_futures - future: configuration.getProducts( - selectedShopService.selectedShop!, - ), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Align( - alignment: Alignment.center, - child: CircularProgressIndicator.adaptive(), - ); - } - - if (snapshot.hasError) { - return configuration.errorBuilder!( - context, - snapshot.error, - snapshot.stackTrace, - ); - } - - var productPageContent = snapshot.data; - - if (productPageContent == null || - productPageContent.products.isEmpty) { - return configuration.noContentBuilder!(context); - } - - var productList = Padding( - padding: const EdgeInsets.fromLTRB(16, 24, 16, 8), - child: Column( - children: [ - // Products - getCategoryList( - context, - configuration, - shoppingCartNotifier, - productPageContent.products, - ), - - // Bottom padding so the last product is not cut off - // by the to shopping cart button. - const SizedBox(height: 48), - ], - ), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Align( + alignment: Alignment.center, + child: CircularProgressIndicator.adaptive(), ); + } - return Column( + if (snapshot.hasError) { + return configuration.errorBuilder!( + context, + snapshot.error, + snapshot.stackTrace, + ); + } + + var productPageContent = snapshot.data; + + if (productPageContent == null || + productPageContent.products.isEmpty) { + return configuration.noContentBuilder!(context); + } + + var productList = Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), + child: Column( children: [ - // Discounted product - if (productPageContent.discountedProduct != null) ...[ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: WeeklyDiscount( - configuration: configuration, - product: productPageContent.discountedProduct!, - ), - ), - ], + // Products + getCategoryList( + context, + configuration, + shoppingCartNotifier, + productPageContent.products, + ), - productList, + // Bottom padding so the last product is not cut off + // by the to shopping cart button. + const SizedBox(height: 48), ], - ); - }, - ), - ); + ), + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Discounted product + if (productPageContent.discountedProduct != null) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: WeeklyDiscount( + configuration: configuration, + product: productPageContent.discountedProduct!, + ), + ), + ], + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: Text( + "What would you like to order?", + style: theme.textTheme.titleLarge, + textAlign: TextAlign.start, + ), + ), + + productList, + ], + ); + }, + ), + ); + } } diff --git a/packages/flutter_product_page/lib/src/ui/product_page_screen.dart b/packages/flutter_product_page/lib/src/ui/product_page_screen.dart index 959626b..ceaa158 100644 --- a/packages/flutter_product_page/lib/src/ui/product_page_screen.dart +++ b/packages/flutter_product_page/lib/src/ui/product_page_screen.dart @@ -24,13 +24,13 @@ class ProductPageScreen extends StatelessWidget { @override Widget build(BuildContext context) => Scaffold( + appBar: configuration.appBar!.call(context), body: SafeArea( child: ProductPage( configuration: configuration, initialBuildShopId: initialBuildShopId, ), ), - appBar: configuration.appBar, bottomNavigationBar: configuration.bottomNavigationBar, ); } diff --git a/packages/flutter_product_page/lib/src/ui/widgets/horizontal_list_items.dart b/packages/flutter_product_page/lib/src/ui/widgets/horizontal_list_items.dart index 8079723..515f17b 100644 --- a/packages/flutter_product_page/lib/src/ui/widgets/horizontal_list_items.dart +++ b/packages/flutter_product_page/lib/src/ui/widgets/horizontal_list_items.dart @@ -9,7 +9,7 @@ class HorizontalListItems extends StatelessWidget { required this.selectedItem, required this.onTap, this.paddingBetweenButtons = 2.0, - this.paddingOnButtons = 4, + this.paddingOnButtons = 6, super.key, }); @@ -32,41 +32,46 @@ class HorizontalListItems extends StatelessWidget { Widget build(BuildContext context) { var theme = Theme.of(context); - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: shops - .map( - (shop) => Padding( - padding: EdgeInsets.only(right: paddingBetweenButtons), - child: InkWell( - onTap: () => onTap(shop), - child: Container( - decoration: BoxDecoration( - color: shop.id == selectedItem - ? theme.colorScheme.primary - : theme.colorScheme.secondary, - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: theme.colorScheme.primary, - width: 1, + return Padding( + padding: const EdgeInsets.only( + top: 4, + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: shops + .map( + (shop) => Padding( + padding: EdgeInsets.only(right: paddingBetweenButtons), + child: InkWell( + onTap: () => onTap(shop), + child: Container( + decoration: BoxDecoration( + color: shop.id == selectedItem + ? theme.colorScheme.primary + : Colors.white, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: theme.colorScheme.primary, + width: 1, + ), + ), + padding: EdgeInsets.all(paddingOnButtons), + child: Text( + shop.name, + style: shop.id == selectedItem + ? theme.textTheme.bodyMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ) + : theme.textTheme.bodyMedium, ), - ), - padding: EdgeInsets.all(paddingOnButtons), - child: Text( - shop.name, - style: shop.id == selectedItem - ? theme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - ) - : theme.textTheme.bodyMedium, ), ), ), - ), - ) - .toList(), + ) + .toList(), + ), ), ); } diff --git a/packages/flutter_product_page/lib/src/ui/widgets/product_item_popup.dart b/packages/flutter_product_page/lib/src/ui/widgets/product_item_popup.dart index 2847543..099fe49 100644 --- a/packages/flutter_product_page/lib/src/ui/widgets/product_item_popup.dart +++ b/packages/flutter_product_page/lib/src/ui/widgets/product_item_popup.dart @@ -20,48 +20,44 @@ class ProductItemPopup extends StatelessWidget { Widget build(BuildContext context) { var theme = Theme.of(context); - var productDescription = Padding( - padding: const EdgeInsets.fromLTRB(44, 32, 44, 20), - child: Text( - product.name, - textAlign: TextAlign.center, - ), - ); - - var closeButton = Padding( - padding: const EdgeInsets.fromLTRB(80, 0, 80, 32), - child: SizedBox( - width: 254, - child: ElevatedButton( - style: theme.elevatedButtonTheme.style?.copyWith( - shape: WidgetStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - ), - ), - onPressed: () => Navigator.of(context).pop(), - child: Padding( - padding: const EdgeInsets.all(14), - child: Text( - configuration.localizations.close, - style: theme.textTheme.titleMedium?.copyWith( - color: theme.colorScheme.onSurface, - ), - ), - ), - ), - ), - ); - return SingleChildScrollView( - child: Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - productDescription, - closeButton, - ], + child: Padding( + padding: const EdgeInsets.all(32), + child: SizedBox( + width: double.infinity, + child: Column( + children: [ + Text( + product.description, + style: theme.textTheme.bodySmall, + textAlign: TextAlign.center, + ), + Padding( + padding: const EdgeInsets.only(top: 20, left: 40, right: 40), + child: SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () => Navigator.of(context).pop(), + style: theme.filledButtonTheme.style?.copyWith( + backgroundColor: WidgetStateProperty.all( + theme.colorScheme.primary, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: Text( + configuration.localizations.close, + style: theme.textTheme.displayLarge, + ), + ), + ), + ), + ), + ], + ), ), ), ); diff --git a/packages/flutter_product_page/lib/src/ui/widgets/spaced_wrap.dart b/packages/flutter_product_page/lib/src/ui/widgets/spaced_wrap.dart index 9bfa8b4..66cf0c8 100644 --- a/packages/flutter_product_page/lib/src/ui/widgets/spaced_wrap.dart +++ b/packages/flutter_product_page/lib/src/ui/widgets/spaced_wrap.dart @@ -33,118 +33,44 @@ class SpacedWrap extends StatelessWidget { /// Callback when an item is tapped. final Function(ProductPageShop shop) onTap; - Row _buildRow( - BuildContext context, - List currentRow, - double availableRowLength, - ) { + @override + Widget build(BuildContext context) { var theme = Theme.of(context); - - var row = []; - var extraButtonPadding = availableRowLength / currentRow.length / 2; - - for (var i = 0, len = currentRow.length; i < len; i++) { - var shop = shops[currentRow[i]]; - row.add( - Padding( - padding: EdgeInsets.only(top: paddingBetweenButtons), - child: InkWell( - onTap: () => onTap(shop), - child: Container( - decoration: BoxDecoration( - color: shop.id == selectedItem - ? theme.colorScheme.primary - : theme.colorScheme.secondary, - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: theme.colorScheme.primary, - width: 1, + return Wrap( + alignment: WrapAlignment.center, + spacing: 4, + children: [ + for (var shop in shops) ...[ + Padding( + padding: EdgeInsets.only(top: paddingBetweenButtons), + child: InkWell( + onTap: () => onTap(shop), + child: DecoratedBox( + decoration: BoxDecoration( + color: shop.id == selectedItem + ? Theme.of(context).colorScheme.primary + : Colors.white, + borderRadius: BorderRadius.circular(4), + border: Border.all( + color: Theme.of(context).colorScheme.primary, + width: 1, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + shop.name, + style: shop.id == selectedItem + ? theme.textTheme.titleMedium + ?.copyWith(color: Colors.white) + : theme.textTheme.bodyMedium, + ), ), - ), - padding: EdgeInsets.symmetric( - horizontal: paddingOnButtons + extraButtonPadding, - vertical: paddingOnButtons, - ), - child: Text( - shop.name, - style: shop.id == selectedItem - ? theme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - ) - : theme.textTheme.bodyMedium, ), ), ), - ), - ); - if (shops.last != shop) { - row.add(const Spacer()); - } - } - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: row, + ], + ], ); } - - List _buildButtonRows(BuildContext context) { - var theme = Theme.of(context); - var rows = []; - var currentRow = []; - var availableRowLength = width; - - for (var i = 0; i < shops.length; i++) { - var shop = shops[i]; - - var textPainter = TextPainter( - text: TextSpan( - text: shop.name, - style: shop.id == selectedItem - ? theme.textTheme.bodyMedium?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - ) - : theme.textTheme.bodyMedium, - ), - maxLines: 1, - textDirection: TextDirection.ltr, - )..layout(minWidth: 0, maxWidth: double.infinity); - - var buttonWidth = textPainter.width + paddingOnButtons * 2; - - if (availableRowLength - buttonWidth < 0) { - rows.add( - _buildRow( - context, - currentRow, - availableRowLength, - ), - ); - currentRow = []; - availableRowLength = width; - } - - currentRow.add(i); - - availableRowLength -= buttonWidth + paddingBetweenButtons; - } - if (currentRow.isNotEmpty) { - rows.add( - _buildRow( - context, - currentRow, - availableRowLength, - ), - ); - } - return rows; - } - - @override - Widget build(BuildContext context) => Column( - children: _buildButtonRows( - context, - ), - ); } diff --git a/packages/flutter_shopping/lib/src/models/product.dart b/packages/flutter_shopping/lib/src/models/product.dart index 04efabb..680b5f5 100644 --- a/packages/flutter_shopping/lib/src/models/product.dart +++ b/packages/flutter_shopping/lib/src/models/product.dart @@ -8,7 +8,8 @@ class Product { required this.imageUrl, required this.category, required this.price, - required this.hasDiscount, + required this.description, + this.hasDiscount = false, this.discountPrice, this.quantity = 1, }); @@ -36,4 +37,7 @@ class Product { /// Quantity for the product. int quantity; + + /// The description of the product. + final String description; }