mirror of
https://github.com/Iconica-Development/flutter_shopping.git
synced 2025-05-19 08:53:46 +02:00
feat: add interface to flutter_product_page
This commit is contained in:
parent
2426416c42
commit
ee22dc98e6
21 changed files with 550 additions and 740 deletions
|
@ -2,11 +2,7 @@
|
||||||
/// detailed view of each product.
|
/// detailed view of each product.
|
||||||
library flutter_product_page;
|
library flutter_product_page;
|
||||||
|
|
||||||
export "src/configuration/product_page_category_styling_configuration.dart";
|
|
||||||
export "src/configuration/product_page_configuration.dart";
|
export "src/configuration/product_page_configuration.dart";
|
||||||
export "src/configuration/product_page_content.dart";
|
|
||||||
export "src/configuration/product_page_localization.dart";
|
|
||||||
export "src/configuration/product_page_shop_selector_style.dart";
|
export "src/configuration/product_page_shop_selector_style.dart";
|
||||||
export "src/models/product_page_shop.dart";
|
export "src/configuration/product_page_translations.dart";
|
||||||
export "src/ui/product_page.dart";
|
export "src/product_page_screen.dart";
|
||||||
export "src/ui/product_page_screen.dart";
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_nested_categories/flutter_nested_categories.dart"
|
|
||||||
show CategoryHeaderStyling;
|
|
||||||
|
|
||||||
/// Configuration for the styling of the category list on the product page.
|
|
||||||
/// This configuration allows to customize the title, header styling and
|
|
||||||
/// the collapsible behavior of the categories.
|
|
||||||
class ProductPageCategoryStylingConfiguration {
|
|
||||||
/// Constructor to create a new instance of
|
|
||||||
/// [ProductPageCategoryStylingConfiguration].
|
|
||||||
const ProductPageCategoryStylingConfiguration({
|
|
||||||
this.headerStyling,
|
|
||||||
this.headerCentered = false,
|
|
||||||
this.customTitle,
|
|
||||||
this.title,
|
|
||||||
this.titleStyle,
|
|
||||||
this.titleCentered = false,
|
|
||||||
this.isCategoryCollapsible = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Optional title for the category list. This will be displayed at the
|
|
||||||
/// top of the list.
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
/// Optional custom title widget for the category list. This will be
|
|
||||||
/// displayed at the top of the list. If set, the text title will be
|
|
||||||
/// ignored.
|
|
||||||
final Widget? customTitle;
|
|
||||||
|
|
||||||
/// Optional title style for the title of the category list. This will
|
|
||||||
/// be applied to the title of the category list. If not set, the default
|
|
||||||
/// text style will be used.
|
|
||||||
final TextStyle? titleStyle;
|
|
||||||
|
|
||||||
/// Configure if the title should be centered.
|
|
||||||
///
|
|
||||||
/// Default is false.
|
|
||||||
final bool titleCentered;
|
|
||||||
|
|
||||||
/// Optional header styling for the categories. This will be applied to
|
|
||||||
/// the name of the categories. If not set, the default text style will
|
|
||||||
/// be used.
|
|
||||||
final CategoryHeaderStyling? headerStyling;
|
|
||||||
|
|
||||||
/// Configure if the category header should be centered.
|
|
||||||
///
|
|
||||||
/// Default is false.
|
|
||||||
final bool headerCentered;
|
|
||||||
|
|
||||||
/// Configure if the category should be collapsible.
|
|
||||||
///
|
|
||||||
/// Default is true.
|
|
||||||
final bool isCategoryCollapsible;
|
|
||||||
}
|
|
|
@ -1,109 +1,48 @@
|
||||||
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/flutter_product_page.dart";
|
||||||
import "package:flutter_product_page/src/ui/widgets/product_item_popup.dart";
|
import "package:flutter_product_page/src/widgets/product_item_popup.dart";
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// Configuration for the product page.
|
/// Configuration for the product page.
|
||||||
class ProductPageConfiguration {
|
class ProductPageConfiguration {
|
||||||
/// Constructor for the product page configuration.
|
/// Constructor for the product page configuration.
|
||||||
ProductPageConfiguration({
|
ProductPageConfiguration({
|
||||||
|
required this.shoppingService,
|
||||||
required this.shops,
|
required this.shops,
|
||||||
required this.getProducts,
|
required this.getProducts,
|
||||||
required this.onAddToCart,
|
required this.onAddToCart,
|
||||||
required this.onNavigateToShoppingCart,
|
required this.onNavigateToShoppingCart,
|
||||||
this.navigateToShoppingCartBuilder = _defaultNavigateToShoppingCartBuilder,
|
required this.getProductsInShoppingCart,
|
||||||
|
this.shoppingCartButtonBuilder = _defaultShoppingCartButtonBuilder,
|
||||||
this.initialShopId,
|
this.initialShopId,
|
||||||
this.productBuilder,
|
this.productBuilder,
|
||||||
this.onShopSelectionChange,
|
this.onShopSelectionChange,
|
||||||
this.getProductsInShoppingCart,
|
this.translations = const ProductPageTranslations(),
|
||||||
this.localizations = const ProductPageLocalization(),
|
|
||||||
this.shopSelectorStyle = ShopSelectorStyle.spacedWrap,
|
this.shopSelectorStyle = ShopSelectorStyle.spacedWrap,
|
||||||
this.categoryStylingConfiguration =
|
|
||||||
const ProductPageCategoryStylingConfiguration(),
|
|
||||||
this.pagePadding = const EdgeInsets.all(4),
|
this.pagePadding = const EdgeInsets.all(4),
|
||||||
this.appBar = _defaultAppBar,
|
this.appBar = _defaultAppBar,
|
||||||
this.bottomNavigationBar,
|
this.bottomNavigationBar,
|
||||||
Function(
|
this.onProductDetail = _onProductDetail,
|
||||||
BuildContext context,
|
this.discountDescription = _defaultDiscountDescription,
|
||||||
Product product,
|
this.noContentBuilder = _defaultNoContentBuilder,
|
||||||
)? onProductDetail,
|
this.errorBuilder = _defaultErrorBuilder,
|
||||||
String Function(
|
});
|
||||||
Product product,
|
|
||||||
)? getDiscountDescription,
|
|
||||||
Widget Function(
|
|
||||||
BuildContext context,
|
|
||||||
Product product,
|
|
||||||
)? productPopupBuilder,
|
|
||||||
Widget Function(
|
|
||||||
BuildContext context,
|
|
||||||
)? noContentBuilder,
|
|
||||||
Widget Function(
|
|
||||||
BuildContext context,
|
|
||||||
Object? error,
|
|
||||||
StackTrace? stackTrace,
|
|
||||||
)? errorBuilder,
|
|
||||||
}) {
|
|
||||||
_productPopupBuilder = productPopupBuilder;
|
|
||||||
_productPopupBuilder ??=
|
|
||||||
(BuildContext context, Product product) => ProductItemPopup(
|
|
||||||
product: product,
|
|
||||||
configuration: this,
|
|
||||||
);
|
|
||||||
|
|
||||||
_onProductDetail = onProductDetail;
|
/// The shopping service that is used
|
||||||
_onProductDetail ??= (BuildContext context, Product product) async {
|
final ShoppingService shoppingService;
|
||||||
var theme = Theme.of(context);
|
|
||||||
|
|
||||||
await showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: theme.colorScheme.surface,
|
|
||||||
builder: (context) => _productPopupBuilder!(
|
|
||||||
context,
|
|
||||||
product,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
_noContentBuilder = noContentBuilder;
|
|
||||||
_noContentBuilder ??= (BuildContext context) {
|
|
||||||
var theme = Theme.of(context);
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
"No content",
|
|
||||||
style: theme.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
_errorBuilder = errorBuilder;
|
|
||||||
_errorBuilder ??=
|
|
||||||
(BuildContext context, Object? error, StackTrace? stackTrace) {
|
|
||||||
var theme = Theme.of(context);
|
|
||||||
return Center(
|
|
||||||
child: Text(
|
|
||||||
"Error: $error",
|
|
||||||
style: theme.textTheme.titleLarge,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
_getDiscountDescription = getDiscountDescription;
|
|
||||||
_getDiscountDescription ??= (Product product) =>
|
|
||||||
"${product.name}, now for ${product.discountPrice} each";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The shop that is initially selected.
|
/// The shop that is initially selected.
|
||||||
final String? initialShopId;
|
final String? initialShopId;
|
||||||
|
|
||||||
/// A list of all the shops that the user must be able to navigate from.
|
/// A list of all the shops that the user must be able to navigate from.
|
||||||
final Future<List<ProductPageShop>> shops;
|
final Future<List<Shop>> Function() shops;
|
||||||
|
|
||||||
/// A function that returns all the products that belong to a certain shop.
|
/// A function that returns all the products that belong to a certain shop.
|
||||||
/// The function must return a [ProductPageContent] object.
|
/// The function must return a [List<Product>].
|
||||||
final Future<ProductPageContent> Function(ProductPageShop shop) getProducts;
|
final Future<List<Product>> Function(Shop shop) getProducts;
|
||||||
|
|
||||||
/// The localizations for the product page.
|
/// The localizations for the product page.
|
||||||
final ProductPageLocalization localizations;
|
final ProductPageTranslations translations;
|
||||||
|
|
||||||
/// Builder for the product item. These items will be displayed in the list
|
/// Builder for the product item. These items will be displayed in the list
|
||||||
/// for each product in their seperated category. This builder should only
|
/// for each product in their seperated category. This builder should only
|
||||||
|
@ -111,55 +50,24 @@ class ProductPageConfiguration {
|
||||||
/// in-case the developer does not override it.
|
/// 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)?
|
/// The builder for the product popup. This builder should return a widget
|
||||||
_productPopupBuilder;
|
Function(
|
||||||
|
BuildContext context,
|
||||||
/// The builder for the product popup. This popup will be displayed when the
|
Product product,
|
||||||
/// user clicks on a product. This builder should only build the widget that
|
String closeText,
|
||||||
/// displays the content of one specific product.
|
) onProductDetail;
|
||||||
/// This builder has a default in-case the developer
|
|
||||||
Widget Function(BuildContext context, Product product)
|
|
||||||
get productPopupBuilder => _productPopupBuilder!;
|
|
||||||
|
|
||||||
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!;
|
|
||||||
|
|
||||||
late Widget Function(BuildContext context)? _noContentBuilder;
|
|
||||||
|
|
||||||
/// The no content builder is used when a shop has no products. This builder
|
|
||||||
/// has a default in-case the developer does not override it.
|
|
||||||
Function(BuildContext context)? get noContentBuilder => _noContentBuilder;
|
|
||||||
|
|
||||||
/// 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(
|
Widget Function(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ProductPageConfiguration configuration,
|
ProductPageConfiguration configuration,
|
||||||
ShoppingCartNotifier notifier,
|
) shoppingCartButtonBuilder;
|
||||||
) navigateToShoppingCartBuilder;
|
|
||||||
|
|
||||||
late Widget Function(
|
/// The function that returns the discount description for a product.
|
||||||
BuildContext context,
|
String Function(
|
||||||
Object? error,
|
Product product,
|
||||||
StackTrace? stackTrace,
|
) discountDescription;
|
||||||
)? _errorBuilder;
|
|
||||||
|
|
||||||
/// The error builder is used when an error occurs. This builder has a default
|
|
||||||
/// in-case the developer does not override it.
|
|
||||||
Widget Function(BuildContext context, Object? error, StackTrace? stackTrace)?
|
|
||||||
get errorBuilder => _errorBuilder;
|
|
||||||
|
|
||||||
late String Function(Product product)? _getDiscountDescription;
|
|
||||||
|
|
||||||
/// The function that returns the description of the discount for a product.
|
|
||||||
/// This allows you to translate and give custom messages for each product.
|
|
||||||
String Function(Product product)? get getDiscountDescription =>
|
|
||||||
_getDiscountDescription!;
|
|
||||||
|
|
||||||
/// This function must be implemented by the developer and should handle the
|
/// This function must be implemented by the developer and should handle the
|
||||||
/// adding of a product to the cart.
|
/// adding of a product to the cart.
|
||||||
|
@ -167,11 +75,11 @@ class ProductPageConfiguration {
|
||||||
|
|
||||||
/// This function gets executed when the user changes the shop selection.
|
/// This function gets executed when the user changes the shop selection.
|
||||||
/// This function always fires upon first load with the initial shop as well.
|
/// This function always fires upon first load with the initial shop as well.
|
||||||
final Function(ProductPageShop shop)? onShopSelectionChange;
|
final Function(Shop shop)? onShopSelectionChange;
|
||||||
|
|
||||||
/// This function must be implemented by the developer and should handle the
|
/// This function must be implemented by the developer and should handle the
|
||||||
/// navigation to the shopping cart overview page.
|
/// navigation to the shopping cart overview page.
|
||||||
final int Function()? getProductsInShoppingCart;
|
final int Function() getProductsInShoppingCart;
|
||||||
|
|
||||||
/// This function must be implemented by the developer and should handle the
|
/// This function must be implemented by the developer and should handle the
|
||||||
/// navigation to the shopping cart overview page.
|
/// navigation to the shopping cart overview page.
|
||||||
|
@ -180,9 +88,6 @@ class ProductPageConfiguration {
|
||||||
/// The style of the shop selector.
|
/// The style of the shop selector.
|
||||||
final ShopSelectorStyle shopSelectorStyle;
|
final ShopSelectorStyle shopSelectorStyle;
|
||||||
|
|
||||||
/// The styling configuration for the category list.
|
|
||||||
final ProductPageCategoryStylingConfiguration categoryStylingConfiguration;
|
|
||||||
|
|
||||||
/// The padding for the page.
|
/// The padding for the page.
|
||||||
final EdgeInsets pagePadding;
|
final EdgeInsets pagePadding;
|
||||||
|
|
||||||
|
@ -191,6 +96,20 @@ class ProductPageConfiguration {
|
||||||
|
|
||||||
/// 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 AppBar Function(BuildContext context)? appBar;
|
final AppBar Function(BuildContext context)? appBar;
|
||||||
|
|
||||||
|
/// Builder for the no content widget. This builder is used when there is no
|
||||||
|
/// content to display.
|
||||||
|
final Widget Function(
|
||||||
|
BuildContext context,
|
||||||
|
)? noContentBuilder;
|
||||||
|
|
||||||
|
/// Builder for the error widget. This builder is used when there is an error
|
||||||
|
/// to display.
|
||||||
|
Widget Function(
|
||||||
|
BuildContext context,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
) errorBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppBar _defaultAppBar(
|
AppBar _defaultAppBar(
|
||||||
|
@ -210,21 +129,21 @@ AppBar _defaultAppBar(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _defaultNavigateToShoppingCartBuilder(
|
Widget _defaultShoppingCartButtonBuilder(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ProductPageConfiguration configuration,
|
ProductPageConfiguration configuration,
|
||||||
ShoppingCartNotifier notifier,
|
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: notifier,
|
listenable: configuration.shoppingService.shoppingCartService,
|
||||||
builder: (context, widget) => Padding(
|
builder: (context, widget) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 60),
|
padding: const EdgeInsets.symmetric(horizontal: 60),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
onPressed: configuration.getProductsInShoppingCart?.call() != 0
|
onPressed: configuration
|
||||||
|
.shoppingService.shoppingCartService.products.isNotEmpty
|
||||||
? configuration.onNavigateToShoppingCart
|
? configuration.onNavigateToShoppingCart
|
||||||
: null,
|
: null,
|
||||||
style: theme.filledButtonTheme.style?.copyWith(
|
style: theme.filledButtonTheme.style?.copyWith(
|
||||||
|
@ -238,7 +157,7 @@ Widget _defaultNavigateToShoppingCartBuilder(
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.localizations.navigateToShoppingCart,
|
configuration.translations.navigateToShoppingCart,
|
||||||
style: theme.textTheme.displayLarge,
|
style: theme.textTheme.displayLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -247,3 +166,51 @@ Widget _defaultNavigateToShoppingCartBuilder(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _onProductDetail(
|
||||||
|
BuildContext context,
|
||||||
|
Product product,
|
||||||
|
String closeText,
|
||||||
|
) async {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
|
await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
backgroundColor: theme.colorScheme.surface,
|
||||||
|
builder: (context) => ProductItemPopup(
|
||||||
|
product: product,
|
||||||
|
closeText: closeText,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _defaultDiscountDescription(
|
||||||
|
Product product,
|
||||||
|
) =>
|
||||||
|
"${product.name}, now for ${product.discountPrice} each";
|
||||||
|
|
||||||
|
Widget _defaultNoContentBuilder(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
"No content",
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _defaultErrorBuilder(
|
||||||
|
BuildContext context,
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stackTrace,
|
||||||
|
) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
return Center(
|
||||||
|
child: Text(
|
||||||
|
"Error: $error",
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
|
||||||
|
|
||||||
/// Return type that contains the products and an optional discounted product.
|
|
||||||
class ProductPageContent {
|
|
||||||
/// Default constructor for this class.
|
|
||||||
const ProductPageContent({
|
|
||||||
required this.products,
|
|
||||||
this.discountedProduct,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// List of products that belong to the shop.
|
|
||||||
final List<Product> products;
|
|
||||||
|
|
||||||
/// Optional highlighted discounted product to display.
|
|
||||||
final Product? discountedProduct;
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
/// Localization for the product page
|
/// Localization for the product page
|
||||||
class ProductPageLocalization {
|
class ProductPageTranslations {
|
||||||
/// Default constructor
|
/// Default constructor
|
||||||
const ProductPageLocalization({
|
const ProductPageTranslations({
|
||||||
this.navigateToShoppingCart = "View shopping cart",
|
this.navigateToShoppingCart = "View shopping cart",
|
||||||
this.discountTitle = "Weekly offer",
|
this.discountTitle = "Weekly offer",
|
||||||
this.failedToLoadImageExplenation = "Failed to load image",
|
this.failedToLoadImageExplenation = "Failed to load image",
|
|
@ -1,18 +0,0 @@
|
||||||
/// The product page shop class contains all the required information
|
|
||||||
/// that needs to be known about a certain shop.
|
|
||||||
///
|
|
||||||
/// In your own implemententation, you must extend from this class so you can
|
|
||||||
/// add more fields to this class to suit your needs.
|
|
||||||
class ProductPageShop {
|
|
||||||
/// The default constructor for this class.
|
|
||||||
const ProductPageShop({
|
|
||||||
required this.id,
|
|
||||||
required this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// The unique identifier for the shop.
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
/// The name of the shop.
|
|
||||||
final String name;
|
|
||||||
}
|
|
238
packages/flutter_product_page/lib/src/product_page_screen.dart
Normal file
238
packages/flutter_product_page/lib/src/product_page_screen.dart
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_product_page/flutter_product_page.dart";
|
||||||
|
import "package:flutter_product_page/src/services/category_service.dart";
|
||||||
|
import "package:flutter_product_page/src/widgets/shop_selector.dart";
|
||||||
|
import "package:flutter_product_page/src/widgets/weekly_discount.dart";
|
||||||
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
|
/// A page that displays products.
|
||||||
|
class ProductPageScreen extends StatelessWidget {
|
||||||
|
/// Constructor for the product page.
|
||||||
|
const ProductPageScreen({
|
||||||
|
required this.configuration,
|
||||||
|
this.initialBuildShopId,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Configuration for the product page.
|
||||||
|
final ProductPageConfiguration configuration;
|
||||||
|
|
||||||
|
/// An optional initial shop ID to select. This overrides the initialShopId
|
||||||
|
/// from the configuration.
|
||||||
|
final String? initialBuildShopId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Scaffold(
|
||||||
|
appBar: configuration.appBar!.call(context),
|
||||||
|
bottomNavigationBar: configuration.bottomNavigationBar,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: configuration.pagePadding,
|
||||||
|
child: FutureBuilder(
|
||||||
|
// ignore: discarded_futures
|
||||||
|
future: configuration.shops(),
|
||||||
|
builder: (BuildContext context, AsyncSnapshot data) {
|
||||||
|
if (data.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Center(child: CircularProgressIndicator.adaptive()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.hasError) {
|
||||||
|
return configuration.errorBuilder(
|
||||||
|
context,
|
||||||
|
data.error,
|
||||||
|
data.stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Shop>? shops = data.data;
|
||||||
|
|
||||||
|
if (shops == null || shops.isEmpty) {
|
||||||
|
return configuration.errorBuilder(context, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (initialBuildShopId != null) {
|
||||||
|
Shop? initialShop;
|
||||||
|
|
||||||
|
for (var shop in shops) {
|
||||||
|
if (shop.id == initialBuildShopId) {
|
||||||
|
initialShop = shop;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.shoppingService.shopService
|
||||||
|
.selectShop(initialShop ?? shops.first);
|
||||||
|
} else if (configuration.initialShopId != null) {
|
||||||
|
Shop? initialShop;
|
||||||
|
|
||||||
|
for (var shop in shops) {
|
||||||
|
if (shop.id == configuration.initialShopId) {
|
||||||
|
initialShop = shop;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration.shoppingService.shopService
|
||||||
|
.selectShop(initialShop ?? shops.first);
|
||||||
|
} else {
|
||||||
|
configuration.shoppingService.shopService
|
||||||
|
.selectShop(shops.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: configuration.shoppingService.shopService,
|
||||||
|
builder: (BuildContext context, Widget? _) {
|
||||||
|
configuration.onShopSelectionChange?.call(
|
||||||
|
configuration.shoppingService.shopService.selectedShop!,
|
||||||
|
);
|
||||||
|
return _ProductPage(
|
||||||
|
configuration: configuration,
|
||||||
|
shops: shops,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProductPage extends StatelessWidget {
|
||||||
|
const _ProductPage({
|
||||||
|
required this.configuration,
|
||||||
|
required this.shops,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ProductPageConfiguration configuration;
|
||||||
|
|
||||||
|
final List<Shop> shops;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var pageContent = SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ShopSelector(
|
||||||
|
configuration: configuration,
|
||||||
|
shops: shops,
|
||||||
|
onTap: configuration.shoppingService.shopService.selectShop,
|
||||||
|
),
|
||||||
|
_ShopContents(
|
||||||
|
configuration: configuration,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
pageContent,
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: configuration.shoppingCartButtonBuilder(
|
||||||
|
context,
|
||||||
|
configuration,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShopContents extends StatelessWidget {
|
||||||
|
const _ShopContents({
|
||||||
|
required this.configuration,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ProductPageConfiguration configuration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
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(
|
||||||
|
configuration.shoppingService.shopService.selectedShop!,
|
||||||
|
),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator.adaptive());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return configuration.errorBuilder(
|
||||||
|
context,
|
||||||
|
snapshot.error,
|
||||||
|
snapshot.stackTrace,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var productPageContent = snapshot.data;
|
||||||
|
|
||||||
|
if (productPageContent == null || productPageContent.isEmpty) {
|
||||||
|
return configuration.noContentBuilder!(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Discounted product
|
||||||
|
if (discountedproducts.isNotEmpty) ...[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: WeeklyDiscount(
|
||||||
|
configuration: configuration,
|
||||||
|
product: discountedproducts.first,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
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,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,14 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_nested_categories/flutter_nested_categories.dart";
|
import "package:flutter_nested_categories/flutter_nested_categories.dart";
|
||||||
import "package:flutter_product_page/src/services/shopping_cart_notifier.dart";
|
import "package:flutter_product_page/flutter_product_page.dart";
|
||||||
import "package:flutter_product_page/src/ui/components/product_item.dart";
|
import "package:flutter_product_page/src/widgets/product_item.dart";
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// A function that is called when a product is added to the cart.
|
|
||||||
Product onAddToCartWrapper(
|
|
||||||
ProductPageConfiguration configuration,
|
|
||||||
ShoppingCartNotifier shoppingCartNotifier,
|
|
||||||
Product product,
|
|
||||||
) {
|
|
||||||
shoppingCartNotifier.productsChanged();
|
|
||||||
|
|
||||||
configuration.onAddToCart(product);
|
|
||||||
|
|
||||||
return product;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates a [CategoryList] from a list of [Product]s and a
|
/// Generates a [CategoryList] from a list of [Product]s and a
|
||||||
/// [ProductPageConfiguration].
|
/// [ProductPageConfiguration].
|
||||||
Widget getCategoryList(
|
Widget getCategoryList(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ProductPageConfiguration configuration,
|
ProductPageConfiguration configuration,
|
||||||
ShoppingCartNotifier shoppingCartNotifier,
|
|
||||||
List<Product> products,
|
List<Product> products,
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
@ -44,12 +30,9 @@ Widget getCategoryList(
|
||||||
: ProductItem(
|
: ProductItem(
|
||||||
product: product,
|
product: product,
|
||||||
onProductDetail: configuration.onProductDetail,
|
onProductDetail: configuration.onProductDetail,
|
||||||
onAddToCart: (Product product) => onAddToCartWrapper(
|
onAddToCart: (Product product) =>
|
||||||
configuration,
|
configuration.onAddToCart(product),
|
||||||
shoppingCartNotifier,
|
translations: configuration.translations,
|
||||||
product,
|
|
||||||
),
|
|
||||||
localizations: configuration.localizations,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_product_page/src/models/product_page_shop.dart";
|
|
||||||
|
|
||||||
/// A service that provides the currently selected shop.
|
|
||||||
class SelectedShopService extends ChangeNotifier {
|
|
||||||
/// Creates a [SelectedShopService].
|
|
||||||
SelectedShopService();
|
|
||||||
|
|
||||||
ProductPageShop? _selectedShop;
|
|
||||||
|
|
||||||
/// Updates the selected shop.
|
|
||||||
void selectShop(ProductPageShop shop) {
|
|
||||||
if (_selectedShop == shop) return;
|
|
||||||
|
|
||||||
_selectedShop = shop;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The currently selected shop.
|
|
||||||
ProductPageShop? get selectedShop => _selectedShop;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
|
|
||||||
/// Class that notifies listeners when the products in the shopping cart have
|
|
||||||
/// changed.
|
|
||||||
class ShoppingCartNotifier extends ChangeNotifier {
|
|
||||||
/// Notifies listeners that the products in the shopping cart have changed.
|
|
||||||
void productsChanged() {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,252 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_product_page/flutter_product_page.dart";
|
|
||||||
import "package:flutter_product_page/src/services/category_service.dart";
|
|
||||||
import "package:flutter_product_page/src/services/selected_shop_service.dart";
|
|
||||||
import "package:flutter_product_page/src/services/shopping_cart_notifier.dart";
|
|
||||||
import "package:flutter_product_page/src/ui/components/shop_selector.dart";
|
|
||||||
import "package:flutter_product_page/src/ui/components/weekly_discount.dart";
|
|
||||||
|
|
||||||
/// A page that displays products.
|
|
||||||
class ProductPage extends StatelessWidget {
|
|
||||||
/// Constructor for the product page.
|
|
||||||
ProductPage({
|
|
||||||
required this.configuration,
|
|
||||||
this.initialBuildShopId,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Configuration for the product page.
|
|
||||||
final ProductPageConfiguration configuration;
|
|
||||||
|
|
||||||
/// An optional initial shop ID to select. This overrides the initialShopId
|
|
||||||
/// from the configuration.
|
|
||||||
final String? initialBuildShopId;
|
|
||||||
|
|
||||||
late final SelectedShopService _selectedShopService = SelectedShopService();
|
|
||||||
|
|
||||||
late final ShoppingCartNotifier _shoppingCartNotifier =
|
|
||||||
ShoppingCartNotifier();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Padding(
|
|
||||||
padding: configuration.pagePadding,
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: configuration.shops,
|
|
||||||
builder: (BuildContext context, AsyncSnapshot data) {
|
|
||||||
if (data.connectionState == ConnectionState.waiting) {
|
|
||||||
return const Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: CircularProgressIndicator.adaptive(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.hasError) {
|
|
||||||
return configuration.errorBuilder!(
|
|
||||||
context,
|
|
||||||
data.error,
|
|
||||||
data.stackTrace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ProductPageShop>? shops = data.data;
|
|
||||||
|
|
||||||
if (shops == null || shops.isEmpty) {
|
|
||||||
return configuration.errorBuilder!(context, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialBuildShopId != null) {
|
|
||||||
ProductPageShop? initialShop;
|
|
||||||
|
|
||||||
for (var shop in shops) {
|
|
||||||
if (shop.id == initialBuildShopId) {
|
|
||||||
initialShop = shop;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedShopService.selectShop(initialShop ?? shops.first);
|
|
||||||
} else if (configuration.initialShopId != null) {
|
|
||||||
ProductPageShop? initialShop;
|
|
||||||
|
|
||||||
for (var shop in shops) {
|
|
||||||
if (shop.id == configuration.initialShopId) {
|
|
||||||
initialShop = shop;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectedShopService.selectShop(initialShop ?? shops.first);
|
|
||||||
} else {
|
|
||||||
_selectedShopService.selectShop(shops.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListenableBuilder(
|
|
||||||
listenable: _selectedShopService,
|
|
||||||
builder: (BuildContext context, Widget? _) {
|
|
||||||
configuration.onShopSelectionChange?.call(
|
|
||||||
_selectedShopService.selectedShop!,
|
|
||||||
);
|
|
||||||
return _ProductPage(
|
|
||||||
configuration: configuration,
|
|
||||||
selectedShopService: _selectedShopService,
|
|
||||||
shoppingCartNotifier: _shoppingCartNotifier,
|
|
||||||
shops: shops,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ProductPage extends StatelessWidget {
|
|
||||||
const _ProductPage({
|
|
||||||
required this.configuration,
|
|
||||||
required this.selectedShopService,
|
|
||||||
required this.shoppingCartNotifier,
|
|
||||||
required this.shops,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ProductPageConfiguration configuration;
|
|
||||||
final SelectedShopService selectedShopService;
|
|
||||||
final ShoppingCartNotifier shoppingCartNotifier;
|
|
||||||
|
|
||||||
final List<ProductPageShop> shops;
|
|
||||||
|
|
||||||
void _onTapChangeShop(ProductPageShop shop) {
|
|
||||||
selectedShopService.selectShop(shop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
var pageContent = SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
ShopSelector(
|
|
||||||
configuration: configuration,
|
|
||||||
selectedShopService: selectedShopService,
|
|
||||||
shops: shops,
|
|
||||||
onTap: _onTapChangeShop,
|
|
||||||
),
|
|
||||||
_ShopContents(
|
|
||||||
configuration: configuration,
|
|
||||||
selectedShopService: selectedShopService,
|
|
||||||
shoppingCartNotifier: shoppingCartNotifier,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Stack(
|
|
||||||
children: [
|
|
||||||
pageContent,
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: configuration.navigateToShoppingCartBuilder(
|
|
||||||
context,
|
|
||||||
configuration,
|
|
||||||
shoppingCartNotifier,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShopContents extends StatelessWidget {
|
|
||||||
const _ShopContents({
|
|
||||||
required this.configuration,
|
|
||||||
required this.selectedShopService,
|
|
||||||
required this.shoppingCartNotifier,
|
|
||||||
});
|
|
||||||
|
|
||||||
final ProductPageConfiguration configuration;
|
|
||||||
final SelectedShopService selectedShopService;
|
|
||||||
final ShoppingCartNotifier shoppingCartNotifier;
|
|
||||||
|
|
||||||
@override
|
|
||||||
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!,
|
|
||||||
),
|
|
||||||
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, 12, 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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
import "package:flutter/material.dart";
|
|
||||||
import "package:flutter_product_page/src/configuration/product_page_configuration.dart";
|
|
||||||
import "package:flutter_product_page/src/ui/product_page.dart";
|
|
||||||
|
|
||||||
/// A screen that displays a product page. This screen contains a Scaffold,
|
|
||||||
/// in which the body is a SafeArea that contains a ProductPage widget.
|
|
||||||
///
|
|
||||||
/// If you do not wish to create a Scaffold you can use the
|
|
||||||
/// [ProductPage] widget directly.
|
|
||||||
class ProductPageScreen extends StatelessWidget {
|
|
||||||
/// Constructor for the product page screen.
|
|
||||||
const ProductPageScreen({
|
|
||||||
required this.configuration,
|
|
||||||
this.initialBuildShopId,
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Configuration for the product page.
|
|
||||||
final ProductPageConfiguration configuration;
|
|
||||||
|
|
||||||
/// An optional initial shop ID to select. This overrides the initialShopId
|
|
||||||
/// from the configuration.
|
|
||||||
final String? initialBuildShopId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) => Scaffold(
|
|
||||||
appBar: configuration.appBar!.call(context),
|
|
||||||
body: SafeArea(
|
|
||||||
child: ProductPage(
|
|
||||||
configuration: configuration,
|
|
||||||
initialBuildShopId: initialBuildShopId,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: configuration.bottomNavigationBar,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_product_page/flutter_product_page.dart";
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// Horizontal list of items.
|
/// Horizontal list of items.
|
||||||
class HorizontalListItems extends StatelessWidget {
|
class HorizontalListItems extends StatelessWidget {
|
||||||
|
@ -14,7 +14,7 @@ class HorizontalListItems extends StatelessWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
/// List of items.
|
/// List of items.
|
||||||
final List<ProductPageShop> shops;
|
final List<Shop> shops;
|
||||||
|
|
||||||
/// Selected item.
|
/// Selected item.
|
||||||
final String selectedItem;
|
final String selectedItem;
|
||||||
|
@ -26,7 +26,7 @@ class HorizontalListItems extends StatelessWidget {
|
||||||
final double paddingOnButtons;
|
final double paddingOnButtons;
|
||||||
|
|
||||||
/// Callback when an item is tapped.
|
/// Callback when an item is tapped.
|
||||||
final Function(ProductPageShop shop) onTap;
|
final Function(Shop shop) onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
|
@ -1,6 +1,7 @@
|
||||||
import "package:cached_network_image/cached_network_image.dart";
|
import "package:cached_network_image/cached_network_image.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
import "package:flutter_product_page/flutter_product_page.dart";
|
||||||
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
import "package:skeletonizer/skeletonizer.dart";
|
import "package:skeletonizer/skeletonizer.dart";
|
||||||
|
|
||||||
/// Product item widget.
|
/// Product item widget.
|
||||||
|
@ -10,7 +11,7 @@ class ProductItem extends StatelessWidget {
|
||||||
required this.product,
|
required this.product,
|
||||||
required this.onProductDetail,
|
required this.onProductDetail,
|
||||||
required this.onAddToCart,
|
required this.onAddToCart,
|
||||||
required this.localizations,
|
required this.translations,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -18,13 +19,17 @@ 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) onProductDetail;
|
final Function(
|
||||||
|
BuildContext context,
|
||||||
|
Product selectedProduct,
|
||||||
|
String closeText,
|
||||||
|
) 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;
|
||||||
|
|
||||||
/// Localizations for the product page.
|
/// Localizations for the product page.
|
||||||
final ProductPageLocalization localizations;
|
final ProductPageTranslations translations;
|
||||||
|
|
||||||
/// Size of the product image.
|
/// Size of the product image.
|
||||||
static const double imageSize = 44;
|
static const double imageSize = 44;
|
||||||
|
@ -46,7 +51,7 @@ class ProductItem extends StatelessWidget {
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
placeholder: (context, url) => loadingImageSkeleton,
|
placeholder: (context, url) => loadingImageSkeleton,
|
||||||
errorWidget: (context, url, error) => Tooltip(
|
errorWidget: (context, url, error) => Tooltip(
|
||||||
message: localizations.failedToLoadImageExplenation,
|
message: translations.failedToLoadImageExplenation,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 48,
|
width: 48,
|
||||||
height: 48,
|
height: 48,
|
||||||
|
@ -74,7 +79,11 @@ class ProductItem extends StatelessWidget {
|
||||||
var productInformationIcon = Padding(
|
var productInformationIcon = Padding(
|
||||||
padding: const EdgeInsets.only(left: 4),
|
padding: const EdgeInsets.only(left: 4),
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: () => onProductDetail(context, product),
|
onPressed: () => onProductDetail(
|
||||||
|
context,
|
||||||
|
product,
|
||||||
|
translations.close,
|
||||||
|
),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.info_outline,
|
Icons.info_outline,
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
|
@ -1,12 +1,12 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// A popup that displays the product item.
|
/// A popup that displays the product item.
|
||||||
class ProductItemPopup extends StatelessWidget {
|
class ProductItemPopup extends StatelessWidget {
|
||||||
/// Constructor for the product item popup.
|
/// Constructor for the product item popup.
|
||||||
const ProductItemPopup({
|
const ProductItemPopup({
|
||||||
required this.product,
|
required this.product,
|
||||||
required this.configuration,
|
required this.closeText,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ class ProductItemPopup extends StatelessWidget {
|
||||||
final Product product;
|
final Product product;
|
||||||
|
|
||||||
/// Configuration for the product page.
|
/// Configuration for the product page.
|
||||||
final ProductPageConfiguration configuration;
|
final String closeText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -49,7 +49,7 @@ class ProductItemPopup extends StatelessWidget {
|
||||||
vertical: 8.0,
|
vertical: 8.0,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.localizations.close,
|
closeText,
|
||||||
style: theme.textTheme.displayLarge,
|
style: theme.textTheme.displayLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
|
@ -1,15 +1,14 @@
|
||||||
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/services/selected_shop_service.dart";
|
import "package:flutter_product_page/src/widgets/horizontal_list_items.dart";
|
||||||
import "package:flutter_product_page/src/ui/widgets/horizontal_list_items.dart";
|
import "package:flutter_product_page/src/widgets/spaced_wrap.dart";
|
||||||
import "package:flutter_product_page/src/ui/widgets/spaced_wrap.dart";
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// Shop selector widget that displays a list to navigate between shops.
|
/// Shop selector widget that displays a list to navigate between shops.
|
||||||
class ShopSelector extends StatelessWidget {
|
class ShopSelector extends StatelessWidget {
|
||||||
/// Constructor for the shop selector.
|
/// Constructor for the shop selector.
|
||||||
const ShopSelector({
|
const ShopSelector({
|
||||||
required this.configuration,
|
required this.configuration,
|
||||||
required this.selectedShopService,
|
|
||||||
required this.shops,
|
required this.shops,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.paddingBetweenButtons = 4,
|
this.paddingBetweenButtons = 4,
|
||||||
|
@ -21,13 +20,12 @@ class ShopSelector extends StatelessWidget {
|
||||||
final ProductPageConfiguration configuration;
|
final ProductPageConfiguration configuration;
|
||||||
|
|
||||||
/// Service for the selected shop.
|
/// Service for the selected shop.
|
||||||
final SelectedShopService selectedShopService;
|
|
||||||
|
|
||||||
/// List of shops.
|
/// List of shops.
|
||||||
final List<ProductPageShop> shops;
|
final List<Shop> shops;
|
||||||
|
|
||||||
/// Callback when a shop is tapped.
|
/// Callback when a shop is tapped.
|
||||||
final Function(ProductPageShop shop) onTap;
|
final Function(Shop shop) onTap;
|
||||||
|
|
||||||
/// Padding between the buttons.
|
/// Padding between the buttons.
|
||||||
final double paddingBetweenButtons;
|
final double paddingBetweenButtons;
|
||||||
|
@ -44,7 +42,8 @@ class ShopSelector extends StatelessWidget {
|
||||||
if (configuration.shopSelectorStyle == ShopSelectorStyle.spacedWrap) {
|
if (configuration.shopSelectorStyle == ShopSelectorStyle.spacedWrap) {
|
||||||
return SpacedWrap(
|
return SpacedWrap(
|
||||||
shops: shops,
|
shops: shops,
|
||||||
selectedItem: selectedShopService.selectedShop!.id,
|
selectedItem:
|
||||||
|
configuration.shoppingService.shopService.selectedShop!.id,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
width: MediaQuery.of(context).size.width - (16 * 2),
|
width: MediaQuery.of(context).size.width - (16 * 2),
|
||||||
paddingBetweenButtons: paddingBetweenButtons,
|
paddingBetweenButtons: paddingBetweenButtons,
|
||||||
|
@ -54,7 +53,7 @@ class ShopSelector extends StatelessWidget {
|
||||||
|
|
||||||
return HorizontalListItems(
|
return HorizontalListItems(
|
||||||
shops: shops,
|
shops: shops,
|
||||||
selectedItem: selectedShopService.selectedShop!.id,
|
selectedItem: configuration.shoppingService.shopService.selectedShop!.id,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
paddingBetweenButtons: paddingBetweenButtons,
|
paddingBetweenButtons: paddingBetweenButtons,
|
||||||
paddingOnButtons: paddingOnButtons,
|
paddingOnButtons: paddingOnButtons,
|
|
@ -1,5 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_product_page/flutter_product_page.dart";
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// SpacedWrap is a widget that wraps a list of items that are spaced out and
|
/// SpacedWrap is a widget that wraps a list of items that are spaced out and
|
||||||
/// fill the available width.
|
/// fill the available width.
|
||||||
|
@ -16,7 +16,7 @@ class SpacedWrap extends StatelessWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
/// List of items.
|
/// List of items.
|
||||||
final List<ProductPageShop> shops;
|
final List<Shop> shops;
|
||||||
|
|
||||||
/// Selected item.
|
/// Selected item.
|
||||||
final String selectedItem;
|
final String selectedItem;
|
||||||
|
@ -31,7 +31,7 @@ class SpacedWrap extends StatelessWidget {
|
||||||
final double paddingOnButtons;
|
final double paddingOnButtons;
|
||||||
|
|
||||||
/// Callback when an item is tapped.
|
/// Callback when an item is tapped.
|
||||||
final Function(ProductPageShop shop) onTap;
|
final Function(Shop shop) onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
|
@ -1,6 +1,7 @@
|
||||||
import "package:cached_network_image/cached_network_image.dart";
|
import "package:cached_network_image/cached_network_image.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_shopping/flutter_shopping.dart";
|
import "package:flutter_product_page/flutter_product_page.dart";
|
||||||
|
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
|
||||||
|
|
||||||
/// A widget that displays a weekly discount.
|
/// A widget that displays a weekly discount.
|
||||||
class WeeklyDiscount extends StatelessWidget {
|
class WeeklyDiscount extends StatelessWidget {
|
||||||
|
@ -27,7 +28,7 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
var bottomText = Padding(
|
var bottomText = Padding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.getDiscountDescription!(product),
|
configuration.discountDescription(product),
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
||||||
|
@ -50,7 +51,7 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
Icons.error_outline_rounded,
|
Icons.error_outline_rounded,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
Text(configuration.localizations.failedToLoadImageExplenation),
|
Text(configuration.translations.failedToLoadImageExplenation),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -86,7 +87,7 @@ class WeeklyDiscount extends StatelessWidget {
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
configuration.localizations.discountTitle,
|
configuration.translations.discountTitle,
|
||||||
style: theme.textTheme.headlineSmall,
|
style: theme.textTheme.headlineSmall,
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
),
|
),
|
|
@ -1,10 +1,10 @@
|
||||||
name: flutter_product_page
|
name: flutter_product_page
|
||||||
description: "A Flutter module for the product page"
|
description: "A Flutter module for the product page"
|
||||||
publish_to: 'none'
|
publish_to: "none"
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.4 <4.0.0'
|
sdk: ">=3.3.4 <4.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
@ -15,11 +15,17 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_nested_categories
|
url: https://github.com/Iconica-Development/flutter_nested_categories
|
||||||
ref: 0.0.1
|
ref: 0.0.1
|
||||||
flutter_shopping:
|
flutter_shopping_interface:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_shopping
|
url: https://github.com/Iconica-Development/flutter_shopping
|
||||||
path: packages/flutter_shopping
|
path: packages/flutter_shopping_interface
|
||||||
ref: 2.0.0
|
ref: 2.0.0
|
||||||
|
collection: ^1.18.0
|
||||||
|
provider: ^6.1.2
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
flutter_shopping_interface:
|
||||||
|
path: ../flutter_shopping_interface
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -95,90 +95,109 @@ Widget _defaultProductItemBuilder(
|
||||||
ShoppingCartConfig configuration,
|
ShoppingCartConfig configuration,
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return Padding(
|
return ListenableBuilder(
|
||||||
padding: const EdgeInsets.only(bottom: 20),
|
listenable: configuration.service,
|
||||||
child: ListTile(
|
builder: (context, _) => Padding(
|
||||||
contentPadding: const EdgeInsets.only(top: 3, left: 4, bottom: 3),
|
padding: const EdgeInsets.only(bottom: 20),
|
||||||
title: Column(
|
child: ListTile(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
contentPadding: const EdgeInsets.only(top: 3, left: 4, bottom: 3),
|
||||||
children: [
|
title: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
product.name,
|
children: [
|
||||||
style: theme.textTheme.titleMedium,
|
Text(
|
||||||
),
|
product.name,
|
||||||
IconButton(
|
style: theme.textTheme.titleMedium,
|
||||||
onPressed: () async {
|
|
||||||
await showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
backgroundColor: theme.colorScheme.surface,
|
|
||||||
builder: (context) => ProductItemPopup(
|
|
||||||
product: product,
|
|
||||||
configuration: configuration,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.info_outline,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
),
|
||||||
),
|
IconButton(
|
||||||
],
|
padding: EdgeInsets.zero,
|
||||||
),
|
constraints: const BoxConstraints(),
|
||||||
leading: ClipRRect(
|
onPressed: () async {
|
||||||
borderRadius: BorderRadius.circular(6),
|
await showModalBottomSheet(
|
||||||
child: Image.network(
|
context: context,
|
||||||
product.imageUrl,
|
backgroundColor: theme.colorScheme.surface,
|
||||||
|
builder: (context) => ProductItemPopup(
|
||||||
|
product: product,
|
||||||
|
configuration: configuration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
leading: ClipRRect(
|
||||||
trailing: Column(
|
borderRadius: BorderRadius.circular(6),
|
||||||
children: [
|
child: Image.network(
|
||||||
Text(
|
product.imageUrl,
|
||||||
product.price.toStringAsFixed(2),
|
|
||||||
style: theme.textTheme.labelSmall,
|
|
||||||
),
|
),
|
||||||
Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
trailing: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: [
|
||||||
children: [
|
Row(
|
||||||
IconButton(
|
mainAxisSize: MainAxisSize.min,
|
||||||
constraints: const BoxConstraints(),
|
children: [
|
||||||
padding: EdgeInsets.zero,
|
if (product.hasDiscount && product.discountPrice != null) ...[
|
||||||
icon: const Icon(
|
Text(
|
||||||
Icons.remove,
|
product.discountPrice!.toStringAsFixed(2),
|
||||||
color: Colors.black,
|
style: theme.textTheme.labelSmall,
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
configuration.service.removeOneProduct(product),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(2),
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
),
|
||||||
height: 30,
|
] else ...[
|
||||||
width: 30,
|
Text(
|
||||||
child: Text(
|
product.price.toStringAsFixed(2),
|
||||||
"${product.quantity}",
|
style: theme.textTheme.labelSmall,
|
||||||
style: theme.textTheme.titleSmall,
|
),
|
||||||
textAlign: TextAlign.center,
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.remove,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
configuration.service.removeOneProduct(product),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(2),
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
height: 30,
|
||||||
|
width: 30,
|
||||||
|
child: Text(
|
||||||
|
"${product.quantity}",
|
||||||
|
style: theme.textTheme.titleSmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
IconButton(
|
||||||
IconButton(
|
constraints: const BoxConstraints(),
|
||||||
constraints: const BoxConstraints(),
|
padding: EdgeInsets.zero,
|
||||||
padding: EdgeInsets.zero,
|
icon: const Icon(
|
||||||
icon: const Icon(
|
Icons.add,
|
||||||
Icons.add,
|
color: Colors.black,
|
||||||
color: Colors.black,
|
),
|
||||||
|
onPressed: () {
|
||||||
|
configuration.service.addProduct(product);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onPressed: () => configuration.service.addProduct(product),
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -190,9 +209,12 @@ Widget _defaultSumBottomSheetBuilder(
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
var totalPrice = configuration.service.products
|
var totalPrice = configuration.service.products.fold<double>(
|
||||||
.map((product) => product.price * product.quantity)
|
0,
|
||||||
.fold(0.0, (a, b) => a + b);
|
(previousValue, element) =>
|
||||||
|
previousValue +
|
||||||
|
(element.discountPrice ?? element.price) * element.quantity,
|
||||||
|
);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: configuration.bottomPadding,
|
padding: configuration.bottomPadding,
|
||||||
|
@ -218,15 +240,16 @@ Widget _defaultConfirmOrderButton(
|
||||||
Function(List<Product> products) onConfirmOrder,
|
Function(List<Product> products) onConfirmOrder,
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 60),
|
padding: const EdgeInsets.symmetric(horizontal: 60),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
onPressed: () => onConfirmOrder(
|
onPressed: configuration.service.products.isEmpty
|
||||||
configuration.service.products,
|
? null
|
||||||
),
|
: () => onConfirmOrder(
|
||||||
|
configuration.service.products,
|
||||||
|
),
|
||||||
style: theme.filledButtonTheme.style?.copyWith(
|
style: theme.filledButtonTheme.style?.copyWith(
|
||||||
backgroundColor: WidgetStateProperty.all(
|
backgroundColor: WidgetStateProperty.all(
|
||||||
theme.colorScheme.primary,
|
theme.colorScheme.primary,
|
||||||
|
|
|
@ -16,57 +16,6 @@ class ShoppingCartScreen extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
var productBuilder = SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (configuration.titleBuilder != null) ...{
|
|
||||||
configuration.titleBuilder!(
|
|
||||||
context,
|
|
||||||
configuration.translations.cartTitle,
|
|
||||||
),
|
|
||||||
} else ...{
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 32,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
configuration.translations.cartTitle,
|
|
||||||
style: theme.textTheme.titleLarge,
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
ListenableBuilder(
|
|
||||||
listenable: configuration.service,
|
|
||||||
builder: (context, _) {
|
|
||||||
var products = configuration.service.products;
|
|
||||||
|
|
||||||
return Column(
|
|
||||||
children: [
|
|
||||||
for (var product in products)
|
|
||||||
configuration.productItemBuilder(
|
|
||||||
context,
|
|
||||||
product,
|
|
||||||
configuration,
|
|
||||||
),
|
|
||||||
// Additional whitespace at the bottom to make sure the
|
|
||||||
// last product(s) are not hidden by the bottom sheet.
|
|
||||||
SizedBox(
|
|
||||||
height: configuration.confirmOrderButtonHeight +
|
|
||||||
configuration.sumBottomSheetHeight,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: configuration.appBar.call(context),
|
appBar: configuration.appBar.call(context),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
|
@ -75,7 +24,54 @@ class ShoppingCartScreen extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: configuration.pagePadding,
|
padding: configuration.pagePadding,
|
||||||
child: productBuilder,
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
if (configuration.titleBuilder != null) ...{
|
||||||
|
configuration.titleBuilder!(
|
||||||
|
context,
|
||||||
|
configuration.translations.cartTitle,
|
||||||
|
),
|
||||||
|
} else ...{
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 32,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
configuration.translations.cartTitle,
|
||||||
|
style: theme.textTheme.titleLarge,
|
||||||
|
textAlign: TextAlign.start,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
ListenableBuilder(
|
||||||
|
listenable: configuration.service,
|
||||||
|
builder: (context, _) => Column(
|
||||||
|
children: [
|
||||||
|
for (var product in configuration.service.products)
|
||||||
|
configuration.productItemBuilder(
|
||||||
|
context,
|
||||||
|
product,
|
||||||
|
configuration,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Additional whitespace at
|
||||||
|
// the bottom to make sure the last
|
||||||
|
// product(s) are not hidden by the bottom sheet.
|
||||||
|
SizedBox(
|
||||||
|
height: configuration.confirmOrderButtonHeight +
|
||||||
|
configuration.sumBottomSheetHeight,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
|
@ -108,8 +104,7 @@ class _BottomSheet extends StatelessWidget {
|
||||||
),
|
),
|
||||||
ListenableBuilder(
|
ListenableBuilder(
|
||||||
listenable: configuration.service,
|
listenable: configuration.service,
|
||||||
builder: (BuildContext context, Widget? child) =>
|
builder: (context, _) => configuration.confirmOrderButtonBuilder(
|
||||||
configuration.confirmOrderButtonBuilder(
|
|
||||||
context,
|
context,
|
||||||
configuration,
|
configuration,
|
||||||
configuration.onConfirmOrder,
|
configuration.onConfirmOrder,
|
||||||
|
|
Loading…
Reference in a new issue