diff --git a/packages/flutter_product_page/lib/src/category_selection_screen.dart b/packages/flutter_product_page/lib/src/category_selection_screen.dart new file mode 100644 index 0000000..0f3649f --- /dev/null +++ b/packages/flutter_product_page/lib/src/category_selection_screen.dart @@ -0,0 +1,67 @@ +import "package:flutter/material.dart"; +import "package:flutter_product_page/flutter_product_page.dart"; + +/// Category selection screen. +class CategorySelectionScreen extends StatelessWidget { + /// Constructor for the category selection screen. + const CategorySelectionScreen({ + required this.configuration, + super.key, + }); + + /// Configuration for the product page. + final ProductPageConfiguration configuration; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + return Scaffold( + appBar: AppBar( + leading: const SizedBox.shrink(), + title: Text( + "filter", + style: theme.textTheme.headlineLarge, + ), + actions: [ + IconButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + icon: const Icon(Icons.close), + ), + ], + ), + body: ListenableBuilder( + listenable: configuration.shoppingService.productService, + builder: (context, _) => Column( + children: [ + ...configuration.shoppingService.productService.getCategories().map( + (category) { + var isChecked = configuration + .shoppingService.productService.selectedCategories + .contains(category); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: CheckboxListTile( + activeColor: theme.colorScheme.primary, + controlAffinity: ListTileControlAffinity.leading, + value: isChecked, + onChanged: (value) { + configuration.shoppingService.productService + .selectCategory(category); + }, + shape: const UnderlineInputBorder(), + title: Text( + category, + style: theme.textTheme.bodyMedium, + ), + ), + ); + }, + ), + ], + ), + ), + ); + } +} 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 616bea4..b9ef07a 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 @@ -29,6 +29,7 @@ class ProductPageConfiguration { this.shopselectorBuilder, this.discountBuilder, this.categoryListBuilder, + this.selectedCategoryBuilder, }) { onProductDetail ??= _onProductDetail; discountDescription ??= _defaultDiscountDescription; @@ -143,6 +144,10 @@ class ProductPageConfiguration { ProductPageConfiguration configuration, List products, )? categoryListBuilder; + + /// Builder for the list of selected categories + final Widget Function(ProductPageConfiguration configuration)? + selectedCategoryBuilder; } Future _onProductDetail( diff --git a/packages/flutter_product_page/lib/src/product_page_screen.dart b/packages/flutter_product_page/lib/src/product_page_screen.dart index 9489d7a..dbee8dc 100644 --- a/packages/flutter_product_page/lib/src/product_page_screen.dart +++ b/packages/flutter_product_page/lib/src/product_page_screen.dart @@ -142,6 +142,12 @@ class _ProductPage extends StatelessWidget { shops: shops, onTap: configuration.shoppingService.shopService.selectShop, ), + configuration.selectedCategoryBuilder?.call( + configuration, + ) ?? + SelectedCategories( + configuration: configuration, + ), _ShopContents( configuration: configuration, ), @@ -203,31 +209,16 @@ class _ShopContents extends StatelessWidget { } } - var productPageContent = snapshot.data; + List productPageContent; - if (productPageContent == null || productPageContent.isEmpty) { + productPageContent = + configuration.shoppingService.productService.products; + + if (productPageContent.isEmpty) { return configuration.noContentBuilder?.call(context) ?? const DefaultNoContent(); } - var productList = Padding( - padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), - child: Column( - children: [ - // Products - - getCategoryList( - context, - configuration, - productPageContent, - ), - - // Bottom padding so the last product is not cut off - // by the to shopping cart button. - const SizedBox(height: 48), - ], - ), - ); var discountedproducts = productPageContent .where((product) => product.hasDiscount) .toList(); @@ -264,7 +255,29 @@ class _ShopContents extends StatelessWidget { configuration, productPageContent, ) ?? - productList, + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 8), + child: Column( + children: [ + // Products + + ListenableBuilder( + listenable: + configuration.shoppingService.productService, + builder: (context, _) => getCategoryList( + context, + configuration, + configuration + .shoppingService.productService.products, + ), + ), + + // Bottom padding so the last product is not cut off + // by the to shopping cart button. + const SizedBox(height: 48), + ], + ), + ), ], ); }, @@ -272,3 +285,55 @@ class _ShopContents extends StatelessWidget { ); } } + +/// Selected categories. +class SelectedCategories extends StatelessWidget { + /// Constructor for the selected categories. + const SelectedCategories({ + required this.configuration, + super.key, + }); + + /// Configuration for the product page. + final ProductPageConfiguration configuration; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + return ListenableBuilder( + listenable: configuration.shoppingService.productService, + builder: (context, _) => Padding( + padding: const EdgeInsets.only(left: 4), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (var category in configuration + .shoppingService.productService.selectedCategories) ...[ + Padding( + padding: const EdgeInsets.only(right: 8), + child: Chip( + backgroundColor: theme.colorScheme.primary, + deleteIcon: const Icon( + Icons.close, + color: Colors.white, + ), + onDeleted: () { + configuration.shoppingService.productService + .selectCategory(category); + }, + label: Text( + category, + style: theme.textTheme.bodyMedium + ?.copyWith(color: Colors.white), + ), + ), + ), + ], + ], + ), + ), + ), + ); + } +} diff --git a/packages/flutter_product_page/lib/src/widgets/defaults/default_appbar.dart b/packages/flutter_product_page/lib/src/widgets/defaults/default_appbar.dart index 9a1cf2b..e81cc2d 100644 --- a/packages/flutter_product_page/lib/src/widgets/defaults/default_appbar.dart +++ b/packages/flutter_product_page/lib/src/widgets/defaults/default_appbar.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_product_page/flutter_product_page.dart"; +import "package:flutter_product_page/src/category_selection_screen.dart"; /// Default appbar for the product page. class DefaultAppbar extends StatelessWidget implements PreferredSizeWidget { @@ -19,7 +20,18 @@ class DefaultAppbar extends StatelessWidget implements PreferredSizeWidget { return AppBar( leading: IconButton(onPressed: () {}, icon: const Icon(Icons.person)), actions: [ - IconButton(onPressed: () {}, icon: const Icon(Icons.filter_alt)), + IconButton( + onPressed: () async { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CategorySelectionScreen( + configuration: configuration, + ), + ), + ); + }, + icon: const Icon(Icons.filter_alt), + ), ], title: Text( configuration.translations.appBarTitle, diff --git a/packages/flutter_shopping/lib/src/configuration/shopping_configuration.dart b/packages/flutter_shopping/lib/src/configuration/shopping_configuration.dart index c4b0b05..0b2f55a 100644 --- a/packages/flutter_shopping/lib/src/configuration/shopping_configuration.dart +++ b/packages/flutter_shopping/lib/src/configuration/shopping_configuration.dart @@ -29,6 +29,7 @@ class ShoppingConfiguration { this.categoryListBuilder, this.shopselectorBuilder, this.discountBuilder, + this.selectedCategoryBuilder, /// ShoppingCart configurations this.onConfirmOrder, @@ -57,6 +58,10 @@ class ShoppingConfiguration { /// The service that will be used for the userstory final ShoppingService shoppingService; + /// Builder for the list of selected categories + final Widget Function(ProductPageConfiguration configuration)? + selectedCategoryBuilder; + /// Function that will be called when the products are requested final Future> Function(String shopId)? onGetProducts; diff --git a/packages/flutter_shopping/lib/src/flutter_shopping_navigator_userstory.dart b/packages/flutter_shopping/lib/src/flutter_shopping_navigator_userstory.dart index e16c05a..f462d4a 100644 --- a/packages/flutter_shopping/lib/src/flutter_shopping_navigator_userstory.dart +++ b/packages/flutter_shopping/lib/src/flutter_shopping_navigator_userstory.dart @@ -55,6 +55,7 @@ class ShoppingProductPage extends StatelessWidget { shopselectorBuilder: shoppingConfiguration.shopselectorBuilder, discountBuilder: shoppingConfiguration.discountBuilder, categoryListBuilder: shoppingConfiguration.categoryListBuilder, + selectedCategoryBuilder: shoppingConfiguration.selectedCategoryBuilder, shops: () async { if (shoppingConfiguration.onGetShops != null) { return shoppingConfiguration.onGetShops!(); diff --git a/packages/flutter_shopping_interface/lib/src/service/product_service.dart b/packages/flutter_shopping_interface/lib/src/service/product_service.dart index fbb7aec..82abee1 100644 --- a/packages/flutter_shopping_interface/lib/src/service/product_service.dart +++ b/packages/flutter_shopping_interface/lib/src/service/product_service.dart @@ -14,4 +14,10 @@ abstract class ProductService with ChangeNotifier { /// Get current Products List get products; + + /// Get current Products + List get selectedCategories; + + /// Select a category + void selectCategory(String category); } diff --git a/packages/flutter_shopping_local/lib/service/local_product_service.dart b/packages/flutter_shopping_local/lib/service/local_product_service.dart index c79d7f7..69c3f4f 100644 --- a/packages/flutter_shopping_local/lib/service/local_product_service.dart +++ b/packages/flutter_shopping_local/lib/service/local_product_service.dart @@ -4,10 +4,12 @@ import "package:flutter_shopping_interface/flutter_shopping_interface.dart"; /// Local product service class LocalProductService with ChangeNotifier implements ProductService { List _products = []; + List _allProducts = []; + final List _selectedCategories = []; @override List getCategories() => - _products.map((e) => e.category).toSet().toList(); + _allProducts.map((e) => e.category).toSet().toList(); @override Future getProduct(String id) => @@ -60,9 +62,32 @@ class LocalProductService with ChangeNotifier implements ProductService { description: "This is a delicious Brown fish", ), ]; + _allProducts = List.from(_products); return Future.value(_products); } @override List get products => _products; + + @override + void selectCategory(String category) { + if (_selectedCategories.contains(category)) { + _selectedCategories.remove(category); + } else { + _selectedCategories.add(category); + } + if (_selectedCategories.isEmpty) { + _products = List.from(_allProducts); + } + _products = _allProducts.where((element) { + if (_selectedCategories.isEmpty) { + return true; + } + return _selectedCategories.contains(element.category); + }).toList(); + notifyListeners(); + } + + @override + List get selectedCategories => _selectedCategories; }