mirror of
https://github.com/Iconica-Development/flutter_shopping.git
synced 2025-05-19 00:43:45 +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_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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
|
|
|
@ -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<Product> products,
|
||||
) {
|
||||
var theme = Theme.of(context);
|
||||
var categorizedProducts = <String, List<Product>>{};
|
||||
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),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -33,118 +33,44 @@ class SpacedWrap extends StatelessWidget {
|
|||
/// Callback when an item is tapped.
|
||||
final Function(ProductPageShop shop) onTap;
|
||||
|
||||
Row _buildRow(
|
||||
BuildContext context,
|
||||
List<int> currentRow,
|
||||
double availableRowLength,
|
||||
) {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
|
||||
var row = <Widget>[];
|
||||
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<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.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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue