feat: add filter screen and widgets

This commit is contained in:
mike doornenbal 2024-07-08 14:44:18 +02:00
parent b736072497
commit db1299f22b
8 changed files with 209 additions and 23 deletions

View file

@ -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,
),
),
);
},
),
],
),
),
);
}
}

View file

@ -29,6 +29,7 @@ class ProductPageConfiguration {
this.shopselectorBuilder, this.shopselectorBuilder,
this.discountBuilder, this.discountBuilder,
this.categoryListBuilder, this.categoryListBuilder,
this.selectedCategoryBuilder,
}) { }) {
onProductDetail ??= _onProductDetail; onProductDetail ??= _onProductDetail;
discountDescription ??= _defaultDiscountDescription; discountDescription ??= _defaultDiscountDescription;
@ -143,6 +144,10 @@ class ProductPageConfiguration {
ProductPageConfiguration configuration, ProductPageConfiguration configuration,
List<Product> products, List<Product> products,
)? categoryListBuilder; )? categoryListBuilder;
/// Builder for the list of selected categories
final Widget Function(ProductPageConfiguration configuration)?
selectedCategoryBuilder;
} }
Future<void> _onProductDetail( Future<void> _onProductDetail(

View file

@ -142,6 +142,12 @@ class _ProductPage extends StatelessWidget {
shops: shops, shops: shops,
onTap: configuration.shoppingService.shopService.selectShop, onTap: configuration.shoppingService.shopService.selectShop,
), ),
configuration.selectedCategoryBuilder?.call(
configuration,
) ??
SelectedCategories(
configuration: configuration,
),
_ShopContents( _ShopContents(
configuration: configuration, configuration: configuration,
), ),
@ -203,31 +209,16 @@ class _ShopContents extends StatelessWidget {
} }
} }
var productPageContent = snapshot.data; List<Product> productPageContent;
if (productPageContent == null || productPageContent.isEmpty) { productPageContent =
configuration.shoppingService.productService.products;
if (productPageContent.isEmpty) {
return configuration.noContentBuilder?.call(context) ?? return configuration.noContentBuilder?.call(context) ??
const DefaultNoContent(); 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 var discountedproducts = productPageContent
.where((product) => product.hasDiscount) .where((product) => product.hasDiscount)
.toList(); .toList();
@ -264,7 +255,29 @@ class _ShopContents extends StatelessWidget {
configuration, configuration,
productPageContent, 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),
),
),
),
],
],
),
),
),
);
}
}

View file

@ -1,5 +1,6 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_product_page/flutter_product_page.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. /// Default appbar for the product page.
class DefaultAppbar extends StatelessWidget implements PreferredSizeWidget { class DefaultAppbar extends StatelessWidget implements PreferredSizeWidget {
@ -19,7 +20,18 @@ class DefaultAppbar extends StatelessWidget implements PreferredSizeWidget {
return AppBar( return AppBar(
leading: IconButton(onPressed: () {}, icon: const Icon(Icons.person)), leading: IconButton(onPressed: () {}, icon: const Icon(Icons.person)),
actions: [ 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( title: Text(
configuration.translations.appBarTitle, configuration.translations.appBarTitle,

View file

@ -29,6 +29,7 @@ class ShoppingConfiguration {
this.categoryListBuilder, this.categoryListBuilder,
this.shopselectorBuilder, this.shopselectorBuilder,
this.discountBuilder, this.discountBuilder,
this.selectedCategoryBuilder,
/// ShoppingCart configurations /// ShoppingCart configurations
this.onConfirmOrder, this.onConfirmOrder,
@ -57,6 +58,10 @@ class ShoppingConfiguration {
/// The service that will be used for the userstory /// The service that will be used for the userstory
final ShoppingService shoppingService; 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 /// Function that will be called when the products are requested
final Future<List<Product>> Function(String shopId)? onGetProducts; final Future<List<Product>> Function(String shopId)? onGetProducts;

View file

@ -55,6 +55,7 @@ class ShoppingProductPage extends StatelessWidget {
shopselectorBuilder: shoppingConfiguration.shopselectorBuilder, shopselectorBuilder: shoppingConfiguration.shopselectorBuilder,
discountBuilder: shoppingConfiguration.discountBuilder, discountBuilder: shoppingConfiguration.discountBuilder,
categoryListBuilder: shoppingConfiguration.categoryListBuilder, categoryListBuilder: shoppingConfiguration.categoryListBuilder,
selectedCategoryBuilder: shoppingConfiguration.selectedCategoryBuilder,
shops: () async { shops: () async {
if (shoppingConfiguration.onGetShops != null) { if (shoppingConfiguration.onGetShops != null) {
return shoppingConfiguration.onGetShops!(); return shoppingConfiguration.onGetShops!();

View file

@ -14,4 +14,10 @@ abstract class ProductService with ChangeNotifier {
/// Get current Products /// Get current Products
List<Product> get products; List<Product> get products;
/// Get current Products
List<String> get selectedCategories;
/// Select a category
void selectCategory(String category);
} }

View file

@ -4,10 +4,12 @@ import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
/// Local product service /// Local product service
class LocalProductService with ChangeNotifier implements ProductService { class LocalProductService with ChangeNotifier implements ProductService {
List<Product> _products = []; List<Product> _products = [];
List<Product> _allProducts = [];
final List<String> _selectedCategories = [];
@override @override
List<String> getCategories() => List<String> getCategories() =>
_products.map((e) => e.category).toSet().toList(); _allProducts.map((e) => e.category).toSet().toList();
@override @override
Future<Product> getProduct(String id) => Future<Product> getProduct(String id) =>
@ -60,9 +62,32 @@ class LocalProductService with ChangeNotifier implements ProductService {
description: "This is a delicious Brown fish", description: "This is a delicious Brown fish",
), ),
]; ];
_allProducts = List.from(_products);
return Future.value(_products); return Future.value(_products);
} }
@override @override
List<Product> get products => _products; List<Product> 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<String> get selectedCategories => _selectedCategories;
} }