From 1b78b2c6745b58383a58a6a3ffdc9948cd2a29b8 Mon Sep 17 00:00:00 2001 From: mike doornenbal Date: Wed, 3 Jul 2024 13:31:21 +0200 Subject: [PATCH] feat: add interface to shopping cart --- .../lib/flutter_shopping_cart.dart | 3 +- .../lib/src/config/shopping_cart_config.dart | 169 +++++++++++------- ...s.dart => shopping_cart_translations.dart} | 16 +- .../lib/src/services/product_service.dart | 71 -------- .../lib/src/widgets/product_item_popup.dart | 5 +- .../lib/src/widgets/shopping_cart_screen.dart | 160 +++-------------- packages/flutter_shopping_cart/pubspec.yaml | 14 +- .../src/service/shopping_cart_service.dart | 3 + .../service/local_shopping_cart_service.dart | 3 + 9 files changed, 159 insertions(+), 285 deletions(-) rename packages/flutter_shopping_cart/lib/src/config/{shopping_cart_localizations.dart => shopping_cart_translations.dart} (52%) delete mode 100644 packages/flutter_shopping_cart/lib/src/services/product_service.dart diff --git a/packages/flutter_shopping_cart/lib/flutter_shopping_cart.dart b/packages/flutter_shopping_cart/lib/flutter_shopping_cart.dart index 4b22672..4599843 100644 --- a/packages/flutter_shopping_cart/lib/flutter_shopping_cart.dart +++ b/packages/flutter_shopping_cart/lib/flutter_shopping_cart.dart @@ -2,6 +2,5 @@ library flutter_shopping_cart; export "src/config/shopping_cart_config.dart"; -export "src/config/shopping_cart_localizations.dart"; -export "src/services/product_service.dart"; +export "src/config/shopping_cart_translations.dart"; export "src/widgets/shopping_cart_screen.dart"; diff --git a/packages/flutter_shopping_cart/lib/src/config/shopping_cart_config.dart b/packages/flutter_shopping_cart/lib/src/config/shopping_cart_config.dart index d2a47a0..9c080ad 100644 --- a/packages/flutter_shopping_cart/lib/src/config/shopping_cart_config.dart +++ b/packages/flutter_shopping_cart/lib/src/config/shopping_cart_config.dart @@ -1,73 +1,48 @@ import "package:flutter/material.dart"; -import "package:flutter_shopping/flutter_shopping.dart"; +import "package:flutter_shopping_cart/flutter_shopping_cart.dart"; import "package:flutter_shopping_cart/src/widgets/product_item_popup.dart"; - -Widget _defaultNoContentBuilder(BuildContext context) => - const SizedBox.shrink(); +import "package:flutter_shopping_interface/flutter_shopping_interface.dart"; /// Shopping cart configuration /// /// This class is used to configure the shopping cart. -class ShoppingCartConfig { +class ShoppingCartConfig { /// Creates a shopping cart configuration. ShoppingCartConfig({ - required this.productService, + required this.service, + required this.onConfirmOrder, this.productItemBuilder = _defaultProductItemBuilder, - this.onConfirmOrder, - this.confirmOrderButtonBuilder, + this.confirmOrderButtonBuilder = _defaultConfirmOrderButton, this.confirmOrderButtonHeight = 100, - this.sumBottomSheetBuilder, + this.sumBottomSheetBuilder = _defaultSumBottomSheetBuilder, this.sumBottomSheetHeight = 100, this.titleBuilder, - this.localizations = const ShoppingCartLocalizations(), - this.padding = const EdgeInsets.symmetric(horizontal: 32), + this.translations = const ShoppingCartTranslations(), + this.pagePadding = const EdgeInsets.symmetric(horizontal: 32), this.bottomPadding = const EdgeInsets.fromLTRB(44, 0, 44, 32), - this.appBar, - Widget Function(BuildContext context) noContentBuilder = - _defaultNoContentBuilder, - }) : assert( - confirmOrderButtonBuilder != null || onConfirmOrder != null, - """ -If you override the confirm order button builder, -you cannot use the onConfirmOrder callback.""", - ), - assert( - confirmOrderButtonBuilder == null || onConfirmOrder == null, - """ -If you do not override the confirm order button builder, -you must use the onConfirmOrder callback.""", - ), - _noContentBuilder = noContentBuilder; + this.appBar = _defaultAppBar, + }); - /// Product Service. The service contains all the products that - /// a shopping cart can contain. Each product must extend the [Product] class. - /// The service is used to add, remove, and update products. - /// - /// The service can be seperate for each shopping cart in-case you want to - /// support seperate shopping carts for shop. - ProductService productService = ProductService([]); + /// Product service. The product service is used to manage the products in the + /// shopping cart. + final ShoppingCartService service; /// Product item builder. This builder is used to build the product item /// that will be displayed in the shopping cart. final Widget Function( BuildContext context, - Locale locale, Product product, - ProductService productService, ShoppingCartConfig configuration, ) productItemBuilder; - final Widget Function(BuildContext context) _noContentBuilder; - - /// No content builder. This builder is used to build the no content widget - /// that will be displayed in the shopping cart when there are no products. - Widget Function(BuildContext context) get noContentBuilder => - _noContentBuilder; - /// Confirm order button builder. This builder is used to build the confirm /// order button that will be displayed in the shopping cart. /// If you override this builder, you cannot use the [onConfirmOrder] callback - final Widget Function(BuildContext context)? confirmOrderButtonBuilder; + final Widget Function( + BuildContext context, + ShoppingCartConfig configuration, + Function(List products) onConfirmOrder, + ) confirmOrderButtonBuilder; /// Confirm order button height. The height of the confirm order button. /// This height is used to calculate the bottom padding of the shopping cart. @@ -78,12 +53,13 @@ you must use the onConfirmOrder callback.""", /// Confirm order callback. This callback is called when the confirm order /// button is pressed. The callback will not be called if you override the /// confirm order button builder. - final Function(List products)? onConfirmOrder; + final Function(List products) onConfirmOrder; /// Sum bottom sheet builder. This builder is used to build the sum bottom /// sheet that will be displayed in the shopping cart. The sum bottom sheet /// can be used to display the total sum of the products in the shopping cart. - final Widget Function(BuildContext context)? sumBottomSheetBuilder; + final Widget Function(BuildContext context, ShoppingCartConfig configuration) + sumBottomSheetBuilder; /// Sum bottom sheet height. The height of the sum bottom sheet. /// This height is used to calculate the bottom padding of the shopping cart. @@ -92,31 +68,30 @@ you must use the onConfirmOrder callback.""", /// Padding around the shopping cart. The padding is used to create space /// around the shopping cart. - final EdgeInsets padding; + final EdgeInsets pagePadding; /// Bottom padding of the shopping cart. The bottom padding is used to create /// a padding around the bottom sheet. This padding is ignored when the /// [sumBottomSheetBuilder] is overridden. final EdgeInsets bottomPadding; - /// Title builder. This builder is used to build the title of the shopping - /// cart. The title is displayed at the top of the shopping cart. If you - /// use the title builder, the [title] will be ignored. - final Widget Function(BuildContext context)? titleBuilder; + /// Title builder. This builder is used to + /// build the title of the shopping cart. + final Widget Function( + BuildContext context, + String title, + )? titleBuilder; - /// Shopping cart localizations. The localizations are used to localize the - /// shopping cart. - final ShoppingCartLocalizations localizations; + /// Shopping cart translations. The translations for the shopping cart. + final ShoppingCartTranslations translations; - /// App bar for the shopping cart screen. - final PreferredSizeWidget? appBar; + /// Appbar for the shopping cart screen. + final AppBar Function(BuildContext context) appBar; } Widget _defaultProductItemBuilder( BuildContext context, - Locale locale, Product product, - ProductService service, ShoppingCartConfig configuration, ) { var theme = Theme.of(context); @@ -172,7 +147,8 @@ Widget _defaultProductItemBuilder( Icons.remove, color: Colors.black, ), - onPressed: () => service.removeOneProduct(product), + onPressed: () => + configuration.service.removeOneProduct(product), ), Padding( padding: const EdgeInsets.all(2), @@ -198,7 +174,7 @@ Widget _defaultProductItemBuilder( Icons.add, color: Colors.black, ), - onPressed: () => service.addProduct(product), + onPressed: () => configuration.service.addProduct(product), ), ], ), @@ -207,3 +183,76 @@ Widget _defaultProductItemBuilder( ), ); } + +Widget _defaultSumBottomSheetBuilder( + BuildContext context, + ShoppingCartConfig configuration, +) { + var theme = Theme.of(context); + + var totalPrice = configuration.service.products + .map((product) => product.price * product.quantity) + .fold(0.0, (a, b) => a + b); + + return Padding( + padding: configuration.bottomPadding, + child: Row( + children: [ + Text( + configuration.translations.sum, + style: theme.textTheme.titleMedium, + ), + const Spacer(), + Text( + "€ ${totalPrice.toStringAsFixed(2)}", + style: theme.textTheme.bodyMedium, + ), + ], + ), + ); +} + +Widget _defaultConfirmOrderButton( + BuildContext context, + ShoppingCartConfig configuration, + Function(List products) onConfirmOrder, +) { + var theme = Theme.of(context); + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 60), + child: SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () => onConfirmOrder( + configuration.service.products, + ), + style: theme.filledButtonTheme.style?.copyWith( + backgroundColor: WidgetStateProperty.all( + theme.colorScheme.primary, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 12, + ), + child: Text( + configuration.translations.placeOrder, + style: theme.textTheme.displayLarge, + ), + ), + ), + ), + ); +} + +AppBar _defaultAppBar(BuildContext context) { + var theme = Theme.of(context); + return AppBar( + title: Text( + "Shopping cart", + style: theme.textTheme.headlineLarge, + ), + ); +} diff --git a/packages/flutter_shopping_cart/lib/src/config/shopping_cart_localizations.dart b/packages/flutter_shopping_cart/lib/src/config/shopping_cart_translations.dart similarity index 52% rename from packages/flutter_shopping_cart/lib/src/config/shopping_cart_localizations.dart rename to packages/flutter_shopping_cart/lib/src/config/shopping_cart_translations.dart index b1c793d..a6deeb9 100644 --- a/packages/flutter_shopping_cart/lib/src/config/shopping_cart_localizations.dart +++ b/packages/flutter_shopping_cart/lib/src/config/shopping_cart_translations.dart @@ -1,24 +1,14 @@ -import "package:flutter/material.dart"; - /// Shopping cart localizations -class ShoppingCartLocalizations { +class ShoppingCartTranslations { /// Creates shopping cart localizations - const ShoppingCartLocalizations({ - this.locale = const Locale("en", "US"), + const ShoppingCartTranslations({ this.placeOrder = "Order", this.sum = "Subtotal:", this.cartTitle = "Products", this.close = "close", }); - /// Locale for the shopping cart. - /// This locale will be used to format the currency. - /// Default is English. - final Locale locale; - - /// Localization for the place order button. - /// This text will only be displayed if you're not using the place order - /// button builder. + /// Text for the place order button. final String placeOrder; /// Localization for the sum. diff --git a/packages/flutter_shopping_cart/lib/src/services/product_service.dart b/packages/flutter_shopping_cart/lib/src/services/product_service.dart deleted file mode 100644 index a96989d..0000000 --- a/packages/flutter_shopping_cart/lib/src/services/product_service.dart +++ /dev/null @@ -1,71 +0,0 @@ -import "package:flutter/foundation.dart"; -import "package:flutter_shopping/flutter_shopping.dart"; - -/// Product service. This class is responsible for managing the products. -/// The service is used to add, remove, and update products. -class ProductService extends ChangeNotifier { - /// Creates a product service. - ProductService(this.products); - - /// List of products in the shopping cart. - final List products; - - /// Adds a product to the shopping cart. - void addProduct(T product) { - for (var p in products) { - if (p.id == product.id) { - p.quantity++; - notifyListeners(); - return; - } - } - - products.add(product); - notifyListeners(); - } - - /// Removes a product from the shopping cart. - void removeProduct(T product) { - for (var p in products) { - if (p.id == product.id) { - products.remove(p); - notifyListeners(); - return; - } - } - notifyListeners(); - } - - /// Removes one product from the shopping cart. - void removeOneProduct(T product) { - for (var p in products) { - if (p.id == product.id) { - if (p.quantity > 1) { - p.quantity--; - notifyListeners(); - return; - } - } - } - - products.remove(product); - notifyListeners(); - } - - /// Counts the number of products in the shopping cart. - int countProducts() { - var count = 0; - - for (var product in products) { - count += product.quantity; - } - - return count; - } - - /// Empties the shopping cart. - void clear() { - products.clear(); - notifyListeners(); - } -} diff --git a/packages/flutter_shopping_cart/lib/src/widgets/product_item_popup.dart b/packages/flutter_shopping_cart/lib/src/widgets/product_item_popup.dart index 0c57633..ebe32c0 100644 --- a/packages/flutter_shopping_cart/lib/src/widgets/product_item_popup.dart +++ b/packages/flutter_shopping_cart/lib/src/widgets/product_item_popup.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; -import "package:flutter_shopping/flutter_shopping.dart"; +import "package:flutter_shopping_cart/flutter_shopping_cart.dart"; +import "package:flutter_shopping_interface/flutter_shopping_interface.dart"; /// A popup that displays the product item. class ProductItemPopup extends StatelessWidget { @@ -49,7 +50,7 @@ class ProductItemPopup extends StatelessWidget { vertical: 8.0, ), child: Text( - configuration.localizations.close, + configuration.translations.close, style: theme.textTheme.displayLarge, ), ), diff --git a/packages/flutter_shopping_cart/lib/src/widgets/shopping_cart_screen.dart b/packages/flutter_shopping_cart/lib/src/widgets/shopping_cart_screen.dart index 4ffb093..5d79166 100644 --- a/packages/flutter_shopping_cart/lib/src/widgets/shopping_cart_screen.dart +++ b/packages/flutter_shopping_cart/lib/src/widgets/shopping_cart_screen.dart @@ -1,8 +1,8 @@ import "package:flutter/material.dart"; -import "package:flutter_shopping/flutter_shopping.dart"; +import "package:flutter_shopping_cart/flutter_shopping_cart.dart"; /// Shopping cart screen widget. -class ShoppingCartScreen extends StatelessWidget { +class ShoppingCartScreen extends StatelessWidget { /// Creates a shopping cart screen. const ShoppingCartScreen({ required this.configuration, @@ -10,7 +10,7 @@ class ShoppingCartScreen extends StatelessWidget { }); /// Configuration for the shopping cart screen. - final ShoppingCartConfig configuration; + final ShoppingCartConfig configuration; @override Widget build(BuildContext context) { @@ -20,7 +20,10 @@ class ShoppingCartScreen extends StatelessWidget { child: Column( children: [ if (configuration.titleBuilder != null) ...{ - configuration.titleBuilder!(context), + configuration.titleBuilder!( + context, + configuration.translations.cartTitle, + ), } else ...{ Padding( padding: const EdgeInsets.symmetric( @@ -29,7 +32,7 @@ class ShoppingCartScreen extends StatelessWidget { child: Row( children: [ Text( - configuration.localizations.cartTitle, + configuration.translations.cartTitle, style: theme.textTheme.titleLarge, textAlign: TextAlign.start, ), @@ -38,22 +41,16 @@ class ShoppingCartScreen extends StatelessWidget { ), }, ListenableBuilder( - listenable: configuration.productService, + listenable: configuration.service, builder: (context, _) { - var products = configuration.productService.products; - - if (products.isEmpty) { - return configuration.noContentBuilder(context); - } + var products = configuration.service.products; return Column( children: [ for (var product in products) configuration.productItemBuilder( context, - configuration.localizations.locale, product, - configuration.productService, configuration, ), // Additional whitespace at the bottom to make sure the @@ -71,24 +68,18 @@ class ShoppingCartScreen extends StatelessWidget { ); return Scaffold( - appBar: configuration.appBar ?? - AppBar( - title: Text( - "Shopping cart", - style: theme.textTheme.headlineLarge, - ), - ), + appBar: configuration.appBar.call(context), body: SafeArea( child: Stack( fit: StackFit.expand, children: [ Padding( - padding: configuration.padding, + padding: configuration.pagePadding, child: productBuilder, ), Align( alignment: Alignment.bottomCenter, - child: _BottomSheet( + child: _BottomSheet( configuration: configuration, ), ), @@ -99,124 +90,31 @@ class ShoppingCartScreen extends StatelessWidget { } } -class _BottomSheet extends StatelessWidget { +class _BottomSheet extends StatelessWidget { const _BottomSheet({ required this.configuration, - super.key, - }); - - final ShoppingCartConfig configuration; - - @override - Widget build(BuildContext context) { - var placeOrderButton = ListenableBuilder( - listenable: configuration.productService, - builder: (BuildContext context, Widget? child) => - configuration.confirmOrderButtonBuilder != null - ? configuration.confirmOrderButtonBuilder!(context) - : _DefaultConfirmOrderButton(configuration: configuration), - ); - - var bottomSheet = ListenableBuilder( - listenable: configuration.productService, - builder: (BuildContext context, Widget? child) => - configuration.sumBottomSheetBuilder != null - ? configuration.sumBottomSheetBuilder!(context) - : _DefaultSumBottomSheet(configuration: configuration), - ); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - bottomSheet, - placeOrderButton, - ], - ); - } -} - -class _DefaultConfirmOrderButton extends StatelessWidget { - const _DefaultConfirmOrderButton({ - required this.configuration, - }); - - final ShoppingCartConfig configuration; - - @override - Widget build(BuildContext context) { - var theme = Theme.of(context); - - void onConfirmOrderPressed(List products) { - if (configuration.onConfirmOrder == null) { - return; - } - - if (products.isEmpty) { - return; - } - - configuration.onConfirmOrder!(products); - } - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 60), - child: SizedBox( - width: double.infinity, - child: FilledButton( - onPressed: () => onConfirmOrderPressed( - configuration.productService.products, - ), - style: theme.filledButtonTheme.style?.copyWith( - backgroundColor: WidgetStateProperty.all( - theme.colorScheme.primary, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 12, - ), - child: Text( - configuration.localizations.placeOrder, - style: theme.textTheme.displayLarge, - ), - ), - ), - ), - ); - } -} - -class _DefaultSumBottomSheet extends StatelessWidget { - const _DefaultSumBottomSheet({ - required this.configuration, }); final ShoppingCartConfig configuration; @override - Widget build(BuildContext context) { - var theme = Theme.of(context); - - var totalPrice = configuration.productService.products - .map((product) => product.price * product.quantity) - .fold(0.0, (a, b) => a + b); - - return Padding( - padding: configuration.bottomPadding, - child: Row( + Widget build(BuildContext context) => Column( + mainAxisSize: MainAxisSize.min, children: [ - Text( - configuration.localizations.sum, - style: theme.textTheme.titleMedium, + ListenableBuilder( + listenable: configuration.service, + builder: (BuildContext context, Widget? child) => + configuration.sumBottomSheetBuilder(context, configuration), ), - const Spacer(), - Text( - "€ ${totalPrice.toStringAsFixed(2)}", - style: theme.textTheme.bodyMedium, + ListenableBuilder( + listenable: configuration.service, + builder: (BuildContext context, Widget? child) => + configuration.confirmOrderButtonBuilder( + context, + configuration, + configuration.onConfirmOrder, + ), ), ], - ), - ); - } + ); } diff --git a/packages/flutter_shopping_cart/pubspec.yaml b/packages/flutter_shopping_cart/pubspec.yaml index 1a4ad49..a96628c 100644 --- a/packages/flutter_shopping_cart/pubspec.yaml +++ b/packages/flutter_shopping_cart/pubspec.yaml @@ -1,21 +1,23 @@ name: flutter_shopping_cart description: "A Flutter module for a shopping cart." version: 2.0.0 -publish_to: 'none' +publish_to: "none" environment: - sdk: '>=3.3.0 <4.0.0' + sdk: ">=3.3.0 <4.0.0" flutter: ">=1.17.0" dependencies: flutter: sdk: flutter - flutter_shopping: + flutter_shopping_interface: git: url: https://github.com/Iconica-Development/flutter_shopping - path: packages/flutter_shopping + path: packages/flutter_shopping_interface ref: 2.0.0 - +dependency_overrides: + flutter_shopping_interface: + path: ../flutter_shopping_interface dev_dependencies: flutter_test: @@ -25,4 +27,4 @@ dev_dependencies: url: https://github.com/Iconica-Development/flutter_iconica_analysis ref: 7.0.0 -flutter: \ No newline at end of file +flutter: diff --git a/packages/flutter_shopping_interface/lib/src/service/shopping_cart_service.dart b/packages/flutter_shopping_interface/lib/src/service/shopping_cart_service.dart index 27fabc3..6c2ce51 100644 --- a/packages/flutter_shopping_interface/lib/src/service/shopping_cart_service.dart +++ b/packages/flutter_shopping_interface/lib/src/service/shopping_cart_service.dart @@ -17,4 +17,7 @@ abstract class ShoppingCartService with ChangeNotifier { /// Clears the shopping cart. void clear(); + + /// The list of products in the shopping cart. + List get products; } diff --git a/packages/flutter_shopping_local/lib/service/local_shopping_cart_service.dart b/packages/flutter_shopping_local/lib/service/local_shopping_cart_service.dart index dafa160..d6c944f 100644 --- a/packages/flutter_shopping_local/lib/service/local_shopping_cart_service.dart +++ b/packages/flutter_shopping_local/lib/service/local_shopping_cart_service.dart @@ -54,4 +54,7 @@ class LocalShoppingCartService } notifyListeners(); } + + @override + List get products => _products; }