mirror of
https://github.com/Iconica-Development/flutter_shopping.git
synced 2025-05-19 08:53:46 +02:00
fix: product page
This commit is contained in:
parent
48989421b4
commit
c2b70eca3d
11 changed files with 316 additions and 384 deletions
|
@ -1,4 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
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_product_page/src/ui/widgets/product_item_popup.dart";
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
import "package:flutter_shopping/flutter_shopping.dart";
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ class ProductPageConfiguration {
|
||||||
required this.getProducts,
|
required this.getProducts,
|
||||||
required this.onAddToCart,
|
required this.onAddToCart,
|
||||||
required this.onNavigateToShoppingCart,
|
required this.onNavigateToShoppingCart,
|
||||||
this.navigateToShoppingCartBuilder,
|
this.navigateToShoppingCartBuilder = _defaultNavigateToShoppingCartBuilder,
|
||||||
this.initialShopId,
|
this.initialShopId,
|
||||||
this.productBuilder,
|
this.productBuilder,
|
||||||
this.onShopSelectionChange,
|
this.onShopSelectionChange,
|
||||||
|
@ -20,7 +21,7 @@ class ProductPageConfiguration {
|
||||||
this.categoryStylingConfiguration =
|
this.categoryStylingConfiguration =
|
||||||
const ProductPageCategoryStylingConfiguration(),
|
const ProductPageCategoryStylingConfiguration(),
|
||||||
this.pagePadding = const EdgeInsets.all(4),
|
this.pagePadding = const EdgeInsets.all(4),
|
||||||
this.appBar,
|
this.appBar = _defaultAppBar,
|
||||||
this.bottomNavigationBar,
|
this.bottomNavigationBar,
|
||||||
Function(
|
Function(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
@ -50,8 +51,7 @@ class ProductPageConfiguration {
|
||||||
);
|
);
|
||||||
|
|
||||||
_onProductDetail = onProductDetail;
|
_onProductDetail = onProductDetail;
|
||||||
_onProductDetail ??=
|
_onProductDetail ??= (BuildContext context, Product product) async {
|
||||||
(BuildContext context, Product product) async {
|
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
await showModalBottomSheet(
|
await showModalBottomSheet(
|
||||||
|
@ -88,8 +88,8 @@ class ProductPageConfiguration {
|
||||||
};
|
};
|
||||||
|
|
||||||
_getDiscountDescription = getDiscountDescription;
|
_getDiscountDescription = getDiscountDescription;
|
||||||
_getDiscountDescription ??=
|
_getDiscountDescription ??= (Product product) =>
|
||||||
(Product product) => "${product.name} is on sale!";
|
"${product.name}, now for ${product.discountPrice} each";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The shop that is initially selected.
|
/// The shop that is initially selected.
|
||||||
|
@ -109,8 +109,7 @@ class ProductPageConfiguration {
|
||||||
/// for each product in their seperated category. This builder should only
|
/// for each product in their seperated category. This builder should only
|
||||||
/// build the widget for one specific product. This builder has a default
|
/// build the widget for one specific product. This builder has a default
|
||||||
/// in-case the developer does not override it.
|
/// in-case the developer does not override it.
|
||||||
Widget Function(BuildContext context, Product product)?
|
Widget Function(BuildContext context, Product product)? productBuilder;
|
||||||
productBuilder;
|
|
||||||
|
|
||||||
late Widget Function(BuildContext context, Product product)?
|
late Widget Function(BuildContext context, Product product)?
|
||||||
_productPopupBuilder;
|
_productPopupBuilder;
|
||||||
|
@ -122,14 +121,13 @@ class ProductPageConfiguration {
|
||||||
Widget Function(BuildContext context, Product product)
|
Widget Function(BuildContext context, Product product)
|
||||||
get productPopupBuilder => _productPopupBuilder!;
|
get productPopupBuilder => _productPopupBuilder!;
|
||||||
|
|
||||||
late Function(BuildContext context, Product product)?
|
late Function(BuildContext context, Product product)? _onProductDetail;
|
||||||
_onProductDetail;
|
|
||||||
|
|
||||||
/// This function handles the creation of the product detail popup. This
|
/// This function handles the creation of the product detail popup. This
|
||||||
/// function has a default in-case the developer does not override it.
|
/// function has a default in-case the developer does not override it.
|
||||||
/// The default intraction is a popup, but this can be overriden.
|
/// The default intraction is a popup, but this can be overriden.
|
||||||
Function(BuildContext context, Product product)
|
Function(BuildContext context, Product product) get onProductDetail =>
|
||||||
get onProductDetail => _onProductDetail!;
|
_onProductDetail!;
|
||||||
|
|
||||||
late Widget Function(BuildContext context)? _noContentBuilder;
|
late Widget Function(BuildContext context)? _noContentBuilder;
|
||||||
|
|
||||||
|
@ -139,7 +137,11 @@ class ProductPageConfiguration {
|
||||||
|
|
||||||
/// The builder for the shopping cart. This builder should return a widget
|
/// The builder for the shopping cart. This builder should return a widget
|
||||||
/// that navigates to the shopping cart overview page.
|
/// 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(
|
late Widget Function(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
@ -188,5 +190,54 @@ class ProductPageConfiguration {
|
||||||
final Widget? bottomNavigationBar;
|
final Widget? bottomNavigationBar;
|
||||||
|
|
||||||
/// Optional app bar that you can pass to the order detail screen.
|
/// 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ class ProductPageLocalization {
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
const ProductPageLocalization({
|
const ProductPageLocalization({
|
||||||
this.navigateToShoppingCart = "View shopping cart",
|
this.navigateToShoppingCart = "View shopping cart",
|
||||||
this.discountTitle = "Discount",
|
this.discountTitle = "Weekly offer",
|
||||||
this.failedToLoadImageExplenation = "Failed to load image",
|
this.failedToLoadImageExplenation = "Failed to load image",
|
||||||
this.close = "Close",
|
this.close = "Close",
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,12 +19,13 @@ Product onAddToCartWrapper(
|
||||||
|
|
||||||
/// Generates a [CategoryList] from a list of [Product]s and a
|
/// Generates a [CategoryList] from a list of [Product]s and a
|
||||||
/// [ProductPageConfiguration].
|
/// [ProductPageConfiguration].
|
||||||
CategoryList getCategoryList(
|
Widget getCategoryList(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ProductPageConfiguration configuration,
|
ProductPageConfiguration configuration,
|
||||||
ShoppingCartNotifier shoppingCartNotifier,
|
ShoppingCartNotifier shoppingCartNotifier,
|
||||||
List<Product> products,
|
List<Product> products,
|
||||||
) {
|
) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
var categorizedProducts = <String, List<Product>>{};
|
var categorizedProducts = <String, List<Product>>{};
|
||||||
for (var product in products) {
|
for (var product in products) {
|
||||||
if (!categorizedProducts.containsKey(product.category)) {
|
if (!categorizedProducts.containsKey(product.category)) {
|
||||||
|
@ -43,8 +44,7 @@ CategoryList getCategoryList(
|
||||||
: ProductItem(
|
: ProductItem(
|
||||||
product: product,
|
product: product,
|
||||||
onProductDetail: configuration.onProductDetail,
|
onProductDetail: configuration.onProductDetail,
|
||||||
onAddToCart: (Product product) =>
|
onAddToCart: (Product product) => onAddToCartWrapper(
|
||||||
onAddToCartWrapper(
|
|
||||||
configuration,
|
configuration,
|
||||||
shoppingCartNotifier,
|
shoppingCartNotifier,
|
||||||
product,
|
product,
|
||||||
|
@ -59,15 +59,19 @@ CategoryList getCategoryList(
|
||||||
);
|
);
|
||||||
categories.add(category);
|
categories.add(category);
|
||||||
});
|
});
|
||||||
|
return Column(
|
||||||
return CategoryList(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
title: configuration.categoryStylingConfiguration.title,
|
children: [
|
||||||
titleStyle: configuration.categoryStylingConfiguration.titleStyle,
|
for (var category in categories) ...[
|
||||||
customTitle: configuration.categoryStylingConfiguration.customTitle,
|
Text(
|
||||||
headerCentered: configuration.categoryStylingConfiguration.headerCentered,
|
category.name!,
|
||||||
headerStyling: configuration.categoryStylingConfiguration.headerStyling,
|
style: theme.textTheme.titleMedium,
|
||||||
isCategoryCollapsible:
|
),
|
||||||
configuration.categoryStylingConfiguration.isCategoryCollapsible,
|
Column(
|
||||||
content: categories,
|
children: category.content,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,7 @@ class ProductItem extends StatelessWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
|
|
||||||
/// Function to call when the product detail is requested.
|
/// Function to call when the product detail is requested.
|
||||||
final Function(BuildContext context, Product selectedProduct)
|
final Function(BuildContext context, Product selectedProduct) onProductDetail;
|
||||||
onProductDetail;
|
|
||||||
|
|
||||||
/// Function to call when the product is added to the cart.
|
/// Function to call when the product is added to the cart.
|
||||||
final Function(Product selectedProduct) onAddToCart;
|
final Function(Product selectedProduct) onAddToCart;
|
||||||
|
@ -76,7 +75,10 @@ class ProductItem extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(left: 4),
|
padding: const EdgeInsets.only(left: 4),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () => onProductDetail(context, product),
|
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,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
_PriceLabel(
|
_PriceLabel(
|
||||||
price: product.price,
|
product: product,
|
||||||
discountPrice: (product.hasDiscount && product.discountPrice != null)
|
|
||||||
? product.discountPrice
|
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
_AddToCardButton(
|
_AddToCardButton(
|
||||||
product: product,
|
product: product,
|
||||||
|
@ -113,39 +112,33 @@ class ProductItem extends StatelessWidget {
|
||||||
|
|
||||||
class _PriceLabel extends StatelessWidget {
|
class _PriceLabel extends StatelessWidget {
|
||||||
const _PriceLabel({
|
const _PriceLabel({
|
||||||
required this.price,
|
required this.product,
|
||||||
required this.discountPrice,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final double price;
|
final Product product;
|
||||||
final double? discountPrice;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
if (discountPrice == null)
|
|
||||||
return Text(
|
|
||||||
price.toStringAsFixed(2),
|
|
||||||
style: theme.textTheme.bodyMedium,
|
|
||||||
);
|
|
||||||
else
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
if (product.hasDiscount) ...[
|
||||||
Text(
|
Text(
|
||||||
price.toStringAsFixed(2),
|
product.price.toStringAsFixed(2),
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
fontSize: 10,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
decoration: TextDecoration.lineThrough,
|
decoration: TextDecoration.lineThrough,
|
||||||
),
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
Padding(
|
const SizedBox(width: 4),
|
||||||
padding: const EdgeInsets.only(left: 4.0),
|
],
|
||||||
child: Text(
|
Text(
|
||||||
discountPrice!.toStringAsFixed(2),
|
product.hasDiscount
|
||||||
style: theme.textTheme.bodyMedium,
|
? product.discountPrice!.toStringAsFixed(2)
|
||||||
),
|
: product.price.toStringAsFixed(2),
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -166,28 +159,22 @@ class _AddToCardButton extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return SizedBox(
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
width: boxSize,
|
width: boxSize,
|
||||||
height: boxSize,
|
height: boxSize,
|
||||||
child: Center(
|
child: Center(
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
icon: Icon(
|
icon: const Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
color: theme.primaryColor,
|
color: Colors.white,
|
||||||
size: 20,
|
size: 20,
|
||||||
),
|
),
|
||||||
onPressed: () => onAddToCart(product),
|
onPressed: () => onAddToCart(product),
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStateProperty.all(
|
|
||||||
theme.colorScheme.secondary,
|
|
||||||
),
|
|
||||||
shape: WidgetStateProperty.all(
|
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,7 +18,7 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
|
|
||||||
/// The top padding of the widget.
|
/// The top padding of the widget.
|
||||||
static const double topPadding = 32.0;
|
static const double topPadding = 20;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -28,9 +28,7 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.getDiscountDescription!(product),
|
configuration.getDiscountDescription!(product),
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.bodyMedium,
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -73,9 +71,9 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
var topText = DecoratedBox(
|
var topText = DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
color: theme.primaryColor,
|
color: Colors.black,
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
topLeft: Radius.circular(4),
|
topLeft: Radius.circular(4),
|
||||||
topRight: Radius.circular(4),
|
topRight: Radius.circular(4),
|
||||||
),
|
),
|
||||||
|
@ -88,10 +86,8 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.localizations.discountTitle.toUpperCase(),
|
configuration.localizations.discountTitle,
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.headlineSmall,
|
||||||
color: theme.colorScheme.onPrimary,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -100,7 +96,6 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
|
|
||||||
var boxDecoration = BoxDecoration(
|
var boxDecoration = BoxDecoration(
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.primaryColor,
|
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(4.0),
|
borderRadius: BorderRadius.circular(4.0),
|
||||||
|
|
|
@ -121,6 +121,7 @@ class _ProductPage extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var pageContent = SingleChildScrollView(
|
var pageContent = SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ShopSelector(
|
ShopSelector(
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
|
@ -142,11 +143,10 @@ class _ProductPage extends StatelessWidget {
|
||||||
pageContent,
|
pageContent,
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: configuration.navigateToShoppingCartBuilder != null
|
child: configuration.navigateToShoppingCartBuilder(
|
||||||
? configuration.navigateToShoppingCartBuilder!(context)
|
context,
|
||||||
: _NavigateToShoppingCartButton(
|
configuration,
|
||||||
configuration: configuration,
|
shoppingCartNotifier,
|
||||||
shoppingCartNotifier: shoppingCartNotifier,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -154,55 +154,6 @@ class _ProductPage extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShopContents extends StatelessWidget {
|
class _ShopContents extends StatelessWidget {
|
||||||
const _ShopContents({
|
const _ShopContents({
|
||||||
required this.configuration,
|
required this.configuration,
|
||||||
|
@ -215,7 +166,9 @@ class _ShopContents extends StatelessWidget {
|
||||||
final ShoppingCartNotifier shoppingCartNotifier;
|
final ShoppingCartNotifier shoppingCartNotifier;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Padding(
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
return Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: configuration.pagePadding.horizontal,
|
horizontal: configuration.pagePadding.horizontal,
|
||||||
),
|
),
|
||||||
|
@ -248,7 +201,7 @@ class _ShopContents extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
var productList = Padding(
|
var productList = Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 24, 16, 8),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Products
|
// Products
|
||||||
|
@ -267,6 +220,7 @@ class _ShopContents extends StatelessWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Discounted product
|
// Discounted product
|
||||||
if (productPageContent.discountedProduct != null) ...[
|
if (productPageContent.discountedProduct != null) ...[
|
||||||
|
@ -278,6 +232,15 @@ class _ShopContents extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
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,
|
productList,
|
||||||
],
|
],
|
||||||
|
@ -285,4 +248,5 @@ class _ShopContents extends StatelessWidget {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,13 @@ class ProductPageScreen extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) => Scaffold(
|
||||||
|
appBar: configuration.appBar!.call(context),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: ProductPage(
|
child: ProductPage(
|
||||||
configuration: configuration,
|
configuration: configuration,
|
||||||
initialBuildShopId: initialBuildShopId,
|
initialBuildShopId: initialBuildShopId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
appBar: configuration.appBar,
|
|
||||||
bottomNavigationBar: configuration.bottomNavigationBar,
|
bottomNavigationBar: configuration.bottomNavigationBar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ class HorizontalListItems extends StatelessWidget {
|
||||||
required this.selectedItem,
|
required this.selectedItem,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.paddingBetweenButtons = 2.0,
|
this.paddingBetweenButtons = 2.0,
|
||||||
this.paddingOnButtons = 4,
|
this.paddingOnButtons = 6,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -32,7 +32,11 @@ class HorizontalListItems extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 4,
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: shops
|
children: shops
|
||||||
|
@ -45,7 +49,7 @@ class HorizontalListItems extends StatelessWidget {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: shop.id == selectedItem
|
color: shop.id == selectedItem
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
: theme.colorScheme.secondary,
|
: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
|
@ -68,6 +72,7 @@ class HorizontalListItems extends StatelessWidget {
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,50 +20,46 @@ class ProductItemPopup extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
var productDescription = Padding(
|
return SingleChildScrollView(
|
||||||
padding: const EdgeInsets.fromLTRB(44, 32, 44, 20),
|
child: Padding(
|
||||||
child: Text(
|
padding: const EdgeInsets.all(32),
|
||||||
product.name,
|
child: SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
product.description,
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
);
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 20, left: 40, right: 40),
|
||||||
var closeButton = Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(80, 0, 80, 32),
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 254,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: FilledButton(
|
||||||
style: theme.elevatedButtonTheme.style?.copyWith(
|
|
||||||
shape: WidgetStateProperty.all(
|
|
||||||
RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
style: theme.filledButtonTheme.style?.copyWith(
|
||||||
|
backgroundColor: WidgetStateProperty.all(
|
||||||
|
theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(14),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.localizations.close,
|
configuration.localizations.close,
|
||||||
style: theme.textTheme.titleMedium?.copyWith(
|
style: theme.textTheme.displayLarge,
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
|
|
||||||
return SingleChildScrollView(
|
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
productDescription,
|
|
||||||
closeButton,
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,118 +33,44 @@ class SpacedWrap extends StatelessWidget {
|
||||||
/// Callback when an item is tapped.
|
/// Callback when an item is tapped.
|
||||||
final Function(ProductPageShop shop) onTap;
|
final Function(ProductPageShop shop) onTap;
|
||||||
|
|
||||||
Row _buildRow(
|
@override
|
||||||
BuildContext context,
|
Widget build(BuildContext context) {
|
||||||
List<int> currentRow,
|
|
||||||
double availableRowLength,
|
|
||||||
) {
|
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
return Wrap(
|
||||||
var row = <Widget>[];
|
alignment: WrapAlignment.center,
|
||||||
var extraButtonPadding = availableRowLength / currentRow.length / 2;
|
spacing: 4,
|
||||||
|
children: [
|
||||||
for (var i = 0, len = currentRow.length; i < len; i++) {
|
for (var shop in shops) ...[
|
||||||
var shop = shops[currentRow[i]];
|
|
||||||
row.add(
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: paddingBetweenButtons),
|
padding: EdgeInsets.only(top: paddingBetweenButtons),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => onTap(shop),
|
onTap: () => onTap(shop),
|
||||||
child: Container(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: shop.id == selectedItem
|
color: shop.id == selectedItem
|
||||||
? theme.colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: theme.colorScheme.secondary,
|
: Colors.white,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: theme.colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
width: 1,
|
width: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
padding: EdgeInsets.symmetric(
|
child: Padding(
|
||||||
horizontal: paddingOnButtons + extraButtonPadding,
|
padding: const EdgeInsets.all(8.0),
|
||||||
vertical: paddingOnButtons,
|
|
||||||
),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
shop.name,
|
shop.name,
|
||||||
style: shop.id == selectedItem
|
style: shop.id == selectedItem
|
||||||
? theme.textTheme.bodyMedium?.copyWith(
|
? theme.textTheme.titleMedium
|
||||||
color: Colors.white,
|
?.copyWith(color: Colors.white)
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
)
|
|
||||||
: theme.textTheme.bodyMedium,
|
: theme.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
|
||||||
if (shops.last != shop) {
|
|
||||||
row.add(const Spacer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: row,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Row> _buildButtonRows(BuildContext context) {
|
|
||||||
var theme = Theme.of(context);
|
|
||||||
var rows = <Row>[];
|
|
||||||
var currentRow = <int>[];
|
|
||||||
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 = <int>[];
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,8 @@ class Product {
|
||||||
required this.imageUrl,
|
required this.imageUrl,
|
||||||
required this.category,
|
required this.category,
|
||||||
required this.price,
|
required this.price,
|
||||||
required this.hasDiscount,
|
required this.description,
|
||||||
|
this.hasDiscount = false,
|
||||||
this.discountPrice,
|
this.discountPrice,
|
||||||
this.quantity = 1,
|
this.quantity = 1,
|
||||||
});
|
});
|
||||||
|
@ -36,4 +37,7 @@ class Product {
|
||||||
|
|
||||||
/// Quantity for the product.
|
/// Quantity for the product.
|
||||||
int quantity;
|
int quantity;
|
||||||
|
|
||||||
|
/// The description of the product.
|
||||||
|
final String description;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue