feat: add userstory

This commit is contained in:
mike doornenbal 2024-07-05 15:14:03 +02:00
parent ee22dc98e6
commit 903550659a
50 changed files with 583 additions and 1623 deletions

View file

@ -1,6 +1,8 @@
/// Flutter component for shopping cart.
library flutter_order_details;
export "package:flutter_form_wizard/flutter_form.dart";
export "src/configuration/order_detail_configuration.dart";
export "src/configuration/order_detail_translations.dart";
export "src/order_detail_screen.dart";

View file

@ -2,7 +2,6 @@
import "package:animated_toggle/animated_toggle.dart";
import "package:flutter/material.dart";
import "package:flutter_form_wizard/flutter_form.dart";
import "package:flutter_order_details/flutter_order_details.dart";
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
@ -14,19 +13,25 @@ class OrderDetailConfiguration {
required this.onNextStep,
required this.onStepsCompleted,
required this.onCompleteOrderDetails,
this.pages = _defaultPages,
this.translations = const OrderDetailTranslations(),
this.appBar = _defaultAppBar,
this.nextbuttonBuilder = _defaultNextButtonBuilder,
this.orderSuccessBuilder = _defaultOrderSuccess,
});
this.pages,
this.translations,
this.appBar,
this.nextbuttonBuilder,
this.orderSuccessBuilder,
}) {
pages ??= _defaultPages;
translations ??= const OrderDetailTranslations();
appBar ??= _defaultAppBar;
nextbuttonBuilder ??= _defaultNextButtonBuilder;
orderSuccessBuilder ??= _defaultOrderSuccess;
}
/// The shopping service that is used
final ShoppingService shoppingService;
/// The different steps that the user has to go through to complete the order.
/// Each step contains a list of fields that the user has to fill in.
final List<FlutterFormPage> Function(BuildContext context) pages;
List<FlutterFormPage> Function(BuildContext context)? pages;
/// Callback function that is called when the user has completed the order.
/// The result of the order is passed as an argument to the function.
@ -38,33 +43,37 @@ class OrderDetailConfiguration {
) onStepsCompleted;
/// Callback function that is called when the user has completed a step.
final Function(int currentStep, Map<String, dynamic> data) onNextStep;
final Function(
int currentStep,
Map<String, dynamic> data,
FlutterFormController controller,
) onNextStep;
/// Localization for the order detail screen.
final OrderDetailTranslations translations;
OrderDetailTranslations? translations;
/// Optional app bar that you can pass to the order detail screen.
final AppBar Function(
AppBar Function(
BuildContext context,
String title,
) appBar;
)? appBar;
/// Optional next button builder that you can pass to the order detail screen.
final Widget Function(
Widget Function(
int currentStep,
// ignore: avoid_positional_boolean_parameters
bool checkingPages,
BuildContext context,
OrderDetailConfiguration configuration,
FlutterFormController controller,
) nextbuttonBuilder;
)? nextbuttonBuilder;
/// Optional builder for the order success screen.
final Widget Function(
Widget Function(
BuildContext context,
OrderDetailConfiguration,
Map<int, Map<String, dynamic>> orderDetails,
) orderSuccessBuilder;
)? orderSuccessBuilder;
/// This function is called after the order has been completed and
/// the success screen has been shown.
@ -107,8 +116,11 @@ Widget _defaultNextButtonBuilder(
width: double.infinity,
child: FilledButton(
onPressed: () async {
controller.validateAndSaveCurrentStep();
await controller.autoNextStep();
configuration.onNextStep(
currentStep,
controller.getCurrentStepResults(),
controller,
);
},
style: theme.filledButtonTheme.style?.copyWith(
backgroundColor: WidgetStateProperty.all(

View file

@ -1,5 +1,4 @@
import "package:flutter/material.dart";
import "package:flutter_form_wizard/flutter_form.dart";
import "package:flutter_order_details/flutter_order_details.dart";
/// Order Detail Screen.
@ -22,22 +21,22 @@ class _OrderDetailScreenState extends State<OrderDetailScreen> {
Widget build(BuildContext context) {
var controller = FlutterFormController();
return Scaffold(
appBar: widget.configuration.appBar.call(
appBar: widget.configuration.appBar!.call(
context,
widget.configuration.translations.orderDetailsTitle,
widget.configuration.translations!.orderDetailsTitle,
),
body: FlutterForm(
formController: controller,
options: FlutterFormOptions(
nextButton: (pageNumber, checkingPages) =>
widget.configuration.nextbuttonBuilder(
widget.configuration.nextbuttonBuilder!(
pageNumber,
checkingPages,
context,
widget.configuration,
controller,
),
pages: widget.configuration.pages.call(context),
pages: widget.configuration.pages!.call(context),
onFinished: (data) async {
widget.configuration.onStepsCompleted.call(
widget.configuration.shoppingService.shopService.selectedShop!.id,
@ -47,7 +46,7 @@ class _OrderDetailScreenState extends State<OrderDetailScreen> {
);
},
onNext: (step, data) {
widget.configuration.onNextStep.call(step, data);
},
),
),

View file

@ -24,10 +24,6 @@ dependencies:
ref: 2.0.0
collection: ^1.18.0
dependency_overrides:
flutter_shopping_interface:
path: ../flutter_shopping_interface
dev_dependencies:
flutter_test:
sdk: flutter

View file

@ -1,5 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_product_page/flutter_product_page.dart";
import "package:flutter_product_page/src/widgets/product_item.dart";
import "package:flutter_product_page/src/widgets/product_item_popup.dart";
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
@ -13,20 +14,31 @@ class ProductPageConfiguration {
required this.onAddToCart,
required this.onNavigateToShoppingCart,
required this.getProductsInShoppingCart,
this.shoppingCartButtonBuilder = _defaultShoppingCartButtonBuilder,
this.shoppingCartButtonBuilder,
this.initialShopId,
this.productBuilder,
this.onShopSelectionChange,
this.translations = const ProductPageTranslations(),
this.shopSelectorStyle = ShopSelectorStyle.spacedWrap,
this.pagePadding = const EdgeInsets.all(4),
this.appBar = _defaultAppBar,
this.translations,
this.shopSelectorStyle,
this.pagePadding,
this.appBar,
this.bottomNavigationBar,
this.onProductDetail = _onProductDetail,
this.discountDescription = _defaultDiscountDescription,
this.noContentBuilder = _defaultNoContentBuilder,
this.errorBuilder = _defaultErrorBuilder,
});
this.onProductDetail,
this.discountDescription,
this.noContentBuilder,
this.errorBuilder,
}) {
shoppingCartButtonBuilder ??= _defaultShoppingCartButtonBuilder;
productBuilder ??= _defaultProductBuilder;
appBar ??= _defaultAppBar;
onProductDetail ??= _onProductDetail;
discountDescription ??= _defaultDiscountDescription;
noContentBuilder ??= _defaultNoContentBuilder;
errorBuilder ??= _defaultErrorBuilder;
translations ??= const ProductPageTranslations();
shopSelectorStyle ??= ShopSelectorStyle.row;
pagePadding ??= const EdgeInsets.all(4);
}
/// The shopping service that is used
final ShoppingService shoppingService;
@ -42,32 +54,36 @@ class ProductPageConfiguration {
final Future<List<Product>> Function(Shop shop) getProducts;
/// The localizations for the product page.
final ProductPageTranslations translations;
ProductPageTranslations? translations;
/// Builder for the product item. These items will be displayed in the list
/// 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,
ProductPageConfiguration configuration,
)? productBuilder;
/// The builder for the product popup. This builder should return a widget
Function(
BuildContext context,
Product product,
String closeText,
) onProductDetail;
)? onProductDetail;
/// The builder for the shopping cart. This builder should return a widget
/// that navigates to the shopping cart overview page.
Widget Function(
BuildContext context,
ProductPageConfiguration configuration,
) shoppingCartButtonBuilder;
)? shoppingCartButtonBuilder;
/// The function that returns the discount description for a product.
String Function(
Product product,
) discountDescription;
)? discountDescription;
/// This function must be implemented by the developer and should handle the
/// adding of a product to the cart.
@ -86,20 +102,20 @@ class ProductPageConfiguration {
final Function() onNavigateToShoppingCart;
/// The style of the shop selector.
final ShopSelectorStyle shopSelectorStyle;
ShopSelectorStyle? shopSelectorStyle;
/// The padding for the page.
final EdgeInsets pagePadding;
EdgeInsets? pagePadding;
/// Optional app bar that you can pass to the product page screen.
final Widget? bottomNavigationBar;
/// Optional app bar that you can pass to the order detail screen.
final AppBar Function(BuildContext context)? appBar;
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(
Widget Function(
BuildContext context,
)? noContentBuilder;
@ -109,7 +125,7 @@ class ProductPageConfiguration {
BuildContext context,
Object? error,
StackTrace? stackTrace,
) errorBuilder;
)? errorBuilder;
}
AppBar _defaultAppBar(
@ -157,7 +173,7 @@ Widget _defaultShoppingCartButtonBuilder(
vertical: 12,
),
child: Text(
configuration.translations.navigateToShoppingCart,
configuration.translations!.navigateToShoppingCart,
style: theme.textTheme.displayLarge,
),
),
@ -214,3 +230,15 @@ Widget _defaultErrorBuilder(
),
);
}
Widget _defaultProductBuilder(
BuildContext context,
Product product,
ProductPageConfiguration configuration,
) =>
ProductItem(
product: product,
onProductDetail: configuration.onProductDetail!,
onAddToCart: (Product product) => configuration.onAddToCart(product),
translations: configuration.translations!,
);

View file

@ -23,11 +23,11 @@ class ProductPageScreen extends StatelessWidget {
@override
Widget build(BuildContext context) => Scaffold(
appBar: configuration.appBar!.call(context),
appBar: configuration.appBar?.call(context),
bottomNavigationBar: configuration.bottomNavigationBar,
body: SafeArea(
child: Padding(
padding: configuration.pagePadding,
padding: configuration.pagePadding!,
child: FutureBuilder(
// ignore: discarded_futures
future: configuration.shops(),
@ -43,7 +43,7 @@ class ProductPageScreen extends StatelessWidget {
}
if (data.hasError) {
return configuration.errorBuilder(
return configuration.errorBuilder!(
context,
data.error,
data.stackTrace,
@ -53,7 +53,7 @@ class ProductPageScreen extends StatelessWidget {
List<Shop>? shops = data.data;
if (shops == null || shops.isEmpty) {
return configuration.errorBuilder(context, null, null);
return configuration.errorBuilder!(context, null, null);
}
if (initialBuildShopId != null) {
@ -137,7 +137,7 @@ class _ProductPage extends StatelessWidget {
pageContent,
Align(
alignment: Alignment.bottomCenter,
child: configuration.shoppingCartButtonBuilder(
child: configuration.shoppingCartButtonBuilder!(
context,
configuration,
),
@ -159,7 +159,7 @@ class _ShopContents extends StatelessWidget {
var theme = Theme.of(context);
return Padding(
padding: EdgeInsets.symmetric(
horizontal: configuration.pagePadding.horizontal,
horizontal: configuration.pagePadding!.horizontal,
),
child: FutureBuilder(
// ignore: discarded_futures
@ -172,7 +172,7 @@ class _ShopContents extends StatelessWidget {
}
if (snapshot.hasError) {
return configuration.errorBuilder(
return configuration.errorBuilder!(
context,
snapshot.error,
snapshot.stackTrace,

View file

@ -1,7 +1,6 @@
import "package:flutter/material.dart";
import "package:flutter_nested_categories/flutter_nested_categories.dart";
import "package:flutter_product_page/flutter_product_page.dart";
import "package:flutter_product_page/src/widgets/product_item.dart";
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
/// Generates a [CategoryList] from a list of [Product]s and a
@ -25,15 +24,8 @@ Widget getCategoryList(
categorizedProducts.forEach((categoryName, productList) {
var productWidgets = productList
.map(
(product) => configuration.productBuilder != null
? configuration.productBuilder!(context, product)
: ProductItem(
product: product,
onProductDetail: configuration.onProductDetail,
onAddToCart: (Product product) =>
configuration.onAddToCart(product),
translations: configuration.translations,
),
(product) =>
configuration.productBuilder!(context, product, configuration),
)
.toList();
var category = Category(

View file

@ -28,7 +28,7 @@ class WeeklyDiscount extends StatelessWidget {
var bottomText = Padding(
padding: const EdgeInsets.all(20.0),
child: Text(
configuration.discountDescription(product),
configuration.discountDescription!(product),
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.left,
),
@ -51,7 +51,7 @@ class WeeklyDiscount extends StatelessWidget {
Icons.error_outline_rounded,
color: Colors.red,
),
Text(configuration.translations.failedToLoadImageExplenation),
Text(configuration.translations!.failedToLoadImageExplenation),
],
),
),
@ -87,7 +87,7 @@ class WeeklyDiscount extends StatelessWidget {
horizontal: 16,
),
child: Text(
configuration.translations.discountTitle,
configuration.translations!.discountTitle,
style: theme.textTheme.headlineSmall,
textAlign: TextAlign.left,
),

View file

@ -23,10 +23,6 @@ dependencies:
collection: ^1.18.0
provider: ^6.1.2
dependency_overrides:
flutter_shopping_interface:
path: ../flutter_shopping_interface
dev_dependencies:
flutter_test:
sdk: flutter

View file

@ -1,53 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
.metadata
pubspec.lock
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Platforms
android/
ios/
linux/
macos/
web/
windows/

View file

@ -1,16 +0,0 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -1,7 +0,0 @@
include: package:flutter_iconica_analysis/analysis_options.yaml
analyzer:
exclude:
linter:
rules:

View file

@ -1,22 +0,0 @@
import "package:example/src/routes.dart";
import "package:example/src/utils/theme.dart";
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends HookConsumerWidget {
const MyApp({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) => MaterialApp.router(
debugShowCheckedModeBanner: false,
restorationScopeId: "app",
theme: getTheme(),
routerConfig: ref.read(routerProvider),
);
}

View file

@ -1,189 +0,0 @@
import "package:example/src/routes.dart";
import "package:example/src/services/order_service.dart";
import "package:example/src/services/shop_service.dart";
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
// (REQUIRED): Create your own instance of the ProductService.
final ProductService<Product> productService = ProductService([]);
FlutterShoppingConfiguration getFlutterShoppingConfiguration() =>
FlutterShoppingConfiguration(
// (REQUIRED): Shop builder configuration
shopBuilder: (
BuildContext context,
String? initialBuildShopId,
String? streetName,
) =>
ProductPageScreen(
configuration: ProductPageConfiguration(
// (REQUIRED): List of shops that should be displayed
// If there is only one, make a list with just one shop.
shops: Future.value(getShops()),
// (REQUIRED): Function to add a product to the cart
onAddToCart: productService.addProduct,
// (REQUIRED): Function to get the products for a shop
getProducts: (ProductPageShop shop) =>
Future<ProductPageContent>.value(
getShopContent(shop.id),
),
// (REQUIRED): Function to navigate to the shopping cart
onNavigateToShoppingCart: () async => onCompleteProductPage(context),
// (RECOMMENDED): Function to get the number of products in the
// shopping cart. This is used to display the number of products
// in the shopping cart on the product page.
getProductsInShoppingCart: productService.countProducts,
// (RECOMMENDED) Function that returns the description for a
// product that is on sale.
getDiscountDescription: (product) =>
"""${product.name} for just \$${product.discountPrice?.toStringAsFixed(2)}""",
// (RECOMMENDED) Function that is fired when the shop selection
// changes. You could use this to clear your shopping cart or to
// change the products so they belong to the correct shop again.
onShopSelectionChange: (ProductPageShop shop) =>
productService.clear(),
// (RECOMMENDED) The shop that is initially selected.
// Must be one of the shops in the [shops] list.
initialShopId: getShops().first.id,
// (RECOMMENDED) Localizations for the product page.
localizations: const ProductPageLocalization(),
// (OPTIONAL) Appbar
appBar: (context) => AppBar(
title: const Text("Shop"),
leading: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
context.go(homePage);
},
),
),
),
// (OPTIONAL): Initial build shop id that overrides the initialShop
initialBuildShopId: initialBuildShopId,
),
// (REQUIRED): Shopping cart builder configuration
shoppingCartBuilder: (BuildContext context) => ShoppingCartScreen(
configuration: ShoppingCartConfig(
// (REQUIRED) product service instance:
productService: productService,
// (REQUIRED) product item builder:
productItemBuilder: (context, locale, product, service, config) =>
ListTile(
title: Text(product.name),
subtitle: Text(product.price.toStringAsFixed(2)),
leading: Image.network(
product.imageUrl,
errorBuilder: (context, error, stackTrace) => const Tooltip(
message: "Error loading image",
child: Icon(
Icons.error,
color: Colors.red,
),
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () => productService.removeOneProduct(product),
),
Text("${product.quantity}"),
IconButton(
icon: const Icon(Icons.add),
onPressed: () => productService.addProduct(product),
),
],
),
),
// (OPTIONAL/REQUIRED) on confirm order callback:
// Either use this callback or the placeOrderButtonBuilder.
onConfirmOrder: (products) async => onCompleteShoppingCart(context),
// (RECOMMENDED) localizations:
localizations: const ShoppingCartLocalizations(),
/// (OPTIONAL) no content builder for when there are no products
/// in the shopping cart.
noContentBuilder: (context) => const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 128),
child: Column(
children: [
Icon(
Icons.warning,
),
SizedBox(
height: 16,
),
Text(
"Geen producten in winkelmandje",
),
],
),
),
),
// (OPTIONAL) custom appbar:
appBar: AppBar(
title: const Text("Shopping Cart"),
leading: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
context.go(FlutterShoppingPathRoutes.shop);
},
),
),
),
),
// (REQUIRED): Configuration on what to do when the user story is
// completed.
onCompleteUserStory: (BuildContext context) {
context.go(homePage);
},
// (RECOMMENDED) Handle processing of the order details. This function
// should return true if the order was processed successfully, otherwise
// false.
//
// If this function is not provided, it is assumed that the order is
// always processed successfully.
//
// Example use cases that could be implemented here:
// - Sending and storing the order on a server,
// - Processing payment (if the user decides to pay upfront).
// - And many more...
onCompleteOrderDetails:
(BuildContext context, OrderResult orderDetails) async {
if (orderDetails.order["payment_option"] == "Pay now") {
// Make the user pay upfront.
}
// If all went well, we can store the order in the database.
// Make sure to register whether or not the order was paid.
storeOrderInDatabase(productService.products, orderDetails);
return true;
},
);

View file

@ -1,8 +0,0 @@
import "package:flutter_shopping/flutter_shopping.dart";
class MyShop extends ProductPageShop {
const MyShop({
required super.id,
required super.name,
});
}

View file

@ -1,31 +0,0 @@
import "package:example/src/configuration/configuration.dart";
import "package:example/src/ui/homepage.dart";
import "package:example/src/utils/go_router.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
const String homePage = "/";
final routerProvider = Provider<GoRouter>(
(ref) => GoRouter(
initialLocation: homePage,
routes: [
// Flutter Shopping Story Routes
...getShoppingStoryRoutes(
configuration: getFlutterShoppingConfiguration(),
),
// Home Route
GoRoute(
name: "home",
path: homePage,
pageBuilder: (context, state) => buildScreenWithFadeTransition(
context: context,
state: state,
child: const Homepage(),
),
),
],
),
);

View file

@ -1,6 +0,0 @@
import "package:flutter_shopping/flutter_shopping.dart";
/// Example implementation of storing an order in a database.
void storeOrderInDatabase(List<Product> products, OrderResult result) {
return;
}

View file

@ -1,49 +0,0 @@
import "package:example/src/models/my_shop.dart";
import "package:flutter_shopping/flutter_shopping.dart";
/// This function should have your own implementation. Generally this would
/// contain some API call to fetch the list of shops.
List<MyShop> getShops() => <MyShop>[
const MyShop(id: "1", name: "Shop 1"),
const MyShop(id: "2", name: "Shop 2"),
const MyShop(id: "3", name: "Shop 3"),
];
ProductPageContent getShopContent(String shopId) {
var products = getProducts(shopId);
return ProductPageContent(
discountedProduct: products.first,
products: products,
);
}
/// This function should have your own implementation. Generally this would
/// contain some API call to fetch the list of products for a shop.
List<Product> getProducts(String shopId) => <Product>[
Product(
id: "1",
name: "White bread",
price: 2.99,
category: "Loaves",
imageUrl: "https://via.placeholder.com/150",
hasDiscount: true,
discountPrice: 1.99,
description: "",
),
Product(
id: "2",
name: "Brown bread",
price: 2.99,
category: "Loaves",
imageUrl: "https://via.placeholder.com/150",
description: "",
),
Product(
id: "3",
name: "Cheese sandwich",
price: 1.99,
category: "Sandwiches",
imageUrl: "https://via.placeholder.com/150",
description: "",
),
];

View file

@ -1,20 +0,0 @@
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
class Homepage extends StatelessWidget {
const Homepage({super.key});
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Badge(
label: const Text("1"),
child: IconButton(
icon: const Icon(Icons.shopping_cart_outlined, size: 50),
onPressed: () => context.go(FlutterShoppingPathRoutes.shop),
),
),
),
);
}

View file

@ -1,26 +0,0 @@
import "package:flutter/material.dart";
import "package:go_router/go_router.dart";
CustomTransitionPage buildScreenWithFadeTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) =>
CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
FadeTransition(opacity: animation, child: child),
);
CustomTransitionPage buildScreenWithoutTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) =>
CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
child,
);

View file

@ -1,32 +0,0 @@
import "package:flutter/material.dart";
ThemeData getTheme() => ThemeData(
scaffoldBackgroundColor: const Color.fromRGBO(250, 249, 246, 1),
textTheme: const TextTheme(
labelMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
titleMedium: TextStyle(
fontSize: 16,
color: Color.fromRGBO(60, 60, 59, 1),
fontWeight: FontWeight.w700,
),
),
inputDecorationTheme: const InputDecorationTheme(
fillColor: Colors.white,
),
colorScheme: const ColorScheme.light(
primary: Color.fromRGBO(64, 87, 122, 1),
secondary: Colors.white,
surface: Color.fromRGBO(250, 249, 246, 1),
),
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromRGBO(64, 87, 122, 1),
titleTextStyle: TextStyle(
fontSize: 28,
color: Colors.white,
),
),
);

View file

@ -1,40 +0,0 @@
name: example
description: Demonstrates how to use the flutter_shopping package."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0
environment:
sdk: '>=3.3.4 <4.0.0'
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.20.0
hooks_riverpod: ^2.1.1
go_router: 12.1.3
flutter_shopping:
git:
url: https://github.com/Iconica-Development/flutter_shopping
path: packages/flutter_shopping
ref: 2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_iconica_analysis:
git:
url: https://github.com/Iconica-Development/flutter_iconica_analysis
ref: 7.0.0
flutter:
uses-material-design: true
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic

View file

@ -1,53 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
.metadata
pubspec.lock
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Platforms
/android/
/ios/
/linux/
/macos/
/web/
/windows/

View file

@ -1,16 +0,0 @@
# amazon
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -1,28 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View file

@ -1,22 +0,0 @@
import "package:amazon/src/routes.dart";
import "package:amazon/src/utils/theme.dart";
import "package:flutter/material.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
void main() {
runApp(const ProviderScope(child: MyApp()));
}
class MyApp extends HookConsumerWidget {
const MyApp({
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) => MaterialApp.router(
debugShowCheckedModeBanner: false,
restorationScopeId: "app",
theme: getTheme(),
routerConfig: ref.read(routerProvider),
);
}

View file

@ -1,362 +0,0 @@
import "package:amazon/src/routes.dart";
import "package:amazon/src/services/category_service.dart";
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
// (REQUIRED): Create your own instance of the ProductService.
final ProductService<Product> productService = ProductService([]);
FlutterShoppingConfiguration getFlutterShoppingConfiguration() =>
FlutterShoppingConfiguration(
// (REQUIRED): Shop builder configuration
shopBuilder: (
BuildContext context,
String? initialBuildShopId,
String? streetName,
) {
var theme = Theme.of(context);
return ProductPageScreen(
configuration: ProductPageConfiguration(
// (REQUIRED): List of shops that should be displayed
// If there is only one, make a list with just one shop.
shops: Future.value(getCategories()),
pagePadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4),
// (REQUIRED): Function to add a product to the cart
onAddToCart: (product) {
return productService.addProduct(product);
},
// (REQUIRED): Function to get the products for a shop
getProducts: (ProductPageShop shop) =>
Future<ProductPageContent>.value(
getShopContent(shop.id),
),
// (REQUIRED): Function to navigate to the shopping cart
onNavigateToShoppingCart: () => onCompleteProductPage(context),
shopSelectorStyle: ShopSelectorStyle.row,
navigateToShoppingCartBuilder: (context, productpageinfo, shop) {
return const SizedBox.shrink();
},
bottomNavigationBar: BottomNavigationBar(
fixedColor: theme.primaryColor,
unselectedItemColor: Colors.black,
type: BottomNavigationBarType.fixed,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Home",
),
BottomNavigationBarItem(
icon: Icon(Icons.person_2_outlined),
label: "Profile",
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart_outlined),
label: "Cart",
),
BottomNavigationBarItem(
icon: Icon(Icons.menu),
label: "Menu",
),
],
showSelectedLabels: false,
showUnselectedLabels: false,
onTap: (index) {
switch (index) {
case 0:
// context.go(homePage);
break;
case 1:
break;
case 2:
context.go(FlutterShoppingPathRoutes.shoppingCart);
break;
case 3:
break;
}
},
),
productBuilder: (context, product) => Card(
elevation: 0,
color: const Color.fromARGB(255, 233, 233, 233),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
child: Row(
children: [
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.network(
product.imageUrl,
loadingBuilder: (context, child, loadingProgress) =>
loadingProgress == null
? child
: const Center(
child: CircularProgressIndicator(),
),
errorBuilder: (context, error, stackTrace) =>
const Tooltip(
message: "Error loading image",
child: Icon(
Icons.error,
color: Colors.red,
),
),
),
),
),
Expanded(
flex: 5,
child: ColoredBox(
color: theme.scaffoldBackgroundColor,
child: Padding(
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
product.name,
style: theme.textTheme.titleMedium,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
Row(
children: [
Text(
"4.5",
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.blue,
),
),
const Icon(Icons.star, color: Colors.orange),
const Icon(Icons.star, color: Colors.orange),
const Icon(Icons.star, color: Colors.orange),
const Icon(Icons.star, color: Colors.orange),
const Icon(Icons.star_half,
color: Colors.orange),
Text(
"(3)",
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.grey,
),
),
],
),
Text(
"\$${product.price.toStringAsFixed(2)}",
style: theme.textTheme.titleMedium,
),
Text(
"Gratis bezorging door Amazon",
style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.grey,
),
),
const SizedBox(height: 12),
FilledButton(
onPressed: () {
productService.addProduct(product);
},
child: const Text("In winkelwagen"),
),
],
),
),
),
),
],
),
),
// (RECOMMENDED) The shop that is initially selected.
// Must be one of the shops in the [shops] list.
initialShopId: getCategories().first.id,
// (RECOMMENDED) Localizations for the product page.
localizations: const ProductPageLocalization(),
noContentBuilder: (context) => Center(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 128),
child: Column(
children: [
const Icon(
Icons.warning,
size: 48,
),
const SizedBox(
height: 16,
),
Text(
"Geen producten gevonden",
style: theme.textTheme.titleLarge,
),
],
),
),
),
// (OPTIONAL) Appbar
appBar: (context) => AppBar(
title: const SizedBox(
height: 40,
child: SearchBar(
hintText: "Search products",
leading: Icon(
Icons.search,
color: Colors.black,
),
trailing: [
Icon(
Icons.fit_screen_outlined,
),
],
),
),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
context.go(homePage);
},
),
bottom: AppBar(
backgroundColor: const Color.fromRGBO(203, 237, 230, 1),
title: Row(
children: [
const Icon(Icons.location_on_outlined),
const SizedBox(width: 12),
Expanded(
child: Text(
"Bestemming: ${streetName ?? "Mark - 1234AB Doetinchem Nederland"}",
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleMedium?.copyWith(
color: Colors.black,
),
),
),
],
),
primary: false,
),
),
),
// (OPTIONAL): Initial build shop id that overrides the initialShop
initialBuildShopId: initialBuildShopId,
);
},
// (REQUIRED): Shopping cart builder configuration
shoppingCartBuilder: (BuildContext context) => ShoppingCartScreen(
configuration: ShoppingCartConfig(
// (REQUIRED) product service instance:
productService: productService,
// (REQUIRED) product item builder:
productItemBuilder: (context, locale, product, service, config) =>
ListTile(
title: Text(product.name),
subtitle: Text(product.price.toStringAsFixed(2)),
leading: Image.network(
product.imageUrl,
errorBuilder: (context, error, stackTrace) => const Tooltip(
message: "Error loading image",
child: Icon(
Icons.error,
color: Colors.red,
),
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove),
onPressed: () => productService.removeOneProduct(product),
),
Text("${product.quantity}"),
IconButton(
icon: const Icon(Icons.add),
onPressed: () => productService.addProduct(product),
),
],
),
),
// (OPTIONAL/REQUIRED) on confirm order callback:
// Either use this callback or the placeOrderButtonBuilder.
onConfirmOrder: (products) => onCompleteShoppingCart(context),
// (RECOMMENDED) localizations:
localizations: const ShoppingCartLocalizations(),
/// (OPTIONAL) no content builder for when there are no products
/// in the shopping cart.
noContentBuilder: (context) => const Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 128),
child: Column(
children: [
Icon(
Icons.warning,
),
SizedBox(
height: 16,
),
Text(
"Geen producten in winkelmandje",
),
],
),
),
),
// (OPTIONAL) custom appbar:
appBar: AppBar(
title: const Text("Shopping Cart"),
leading: IconButton(
icon: const Icon(
Icons.arrow_back,
color: Colors.white,
),
onPressed: () {
context.go(FlutterShoppingPathRoutes.shop);
},
),
),
),
),
// (REQUIRED): Configuration on what to do when the user story is
// completed.
onCompleteUserStory: (BuildContext context) {
context.go(homePage);
},
// (RECOMMENDED) Handle processing of the order details. This function
// should return true if the order was processed successfully, otherwise
// false.
//
// If this function is not provided, it is assumed that the order is
// always processed successfully.
//
// Example use cases that could be implemented here:
// - Sending and storing the order on a server,
// - Processing payment (if the user decides to pay upfront).
// - And many more...
// onCompleteOrderDetails:
// (BuildContext context, OrderResult orderDetails) async {
// return true;
// },
);

View file

@ -1,8 +0,0 @@
import 'package:flutter_shopping/flutter_shopping.dart';
class MyCategory extends ProductPageShop {
const MyCategory({
required super.id,
required super.name,
});
}

View file

@ -1,30 +0,0 @@
import "package:amazon/src/configuration/shopping_configuration.dart";
import "package:amazon/src/ui/homepage.dart";
import "package:amazon/src/utils/go_router.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
import "package:hooks_riverpod/hooks_riverpod.dart";
const String homePage = "/";
final routerProvider = Provider<GoRouter>(
(ref) => GoRouter(
initialLocation: homePage,
routes: [
// Flutter Shopping Story Routes
...getShoppingStoryRoutes(
configuration: getFlutterShoppingConfiguration(),
),
// Home Route
GoRoute(
name: "home",
path: homePage,
pageBuilder: (context, state) => buildScreenWithFadeTransition(
context: context,
state: state,
child: const Homepage(),
),
),
],
),
);

View file

@ -1,93 +0,0 @@
import "package:amazon/src/models/my_category.dart";
import "package:flutter_shopping/flutter_shopping.dart";
Map<String, String> categories = {
"Electronics": "Electronica",
"Smart phones": "Telefoons",
"TV's": "TV's",
};
List<Product> allProducts() => [
Product(
id: "1",
name:
"Skar Audio Single 8\" Complete 1,200 Watt EVL Series Subwoofer Bass Package - Includes Loaded Enclosure with...",
price: 2.99,
category: categories["Electronics"]!,
imageUrl:
"https://m.media-amazon.com/images/I/710n3hnbfXL._AC_UY218_.jpg",
description: "",
),
Product(
id: "2",
name:
"Frameo 10.1 Inch WiFi Digital Picture Frame, 1280x800 HD IPS Touch Screen Photo Frame Electronic, 32GB Memory, Auto...",
price: 2.99,
category: categories["Electronics"]!,
imageUrl:
"https://m.media-amazon.com/images/I/61O+aorCp0L._AC_UY218_.jpg",
description: "",
),
Product(
id: "3",
name:
"STREBITO Electronics Precision Screwdriver Sets 142-Piece with 120 Bits Magnetic Repair Tool Kit for iPhone, MacBook,...",
price: 1.99,
category: categories["Electronics"]!,
imageUrl:
"https://m.media-amazon.com/images/I/81-C7lGtQsL._AC_UY218_.jpg",
description: "",
),
Product(
id: "4",
name:
"Samsung Galaxy A15 (SM-155M/DSN), 128GB 6GB RAM, Dual SIM, Factory Unlocked GSM, International Version (Wall...",
price: 1.99,
category: categories["Smart phones"]!,
imageUrl:
"https://m.media-amazon.com/images/I/51rp0nqaPoL._AC_UY218_.jpg",
description: "",
),
Product(
id: "5",
name:
"SAMSUNG Galaxy S24 Ultra Cell Phone, 512GB AI Smartphone, Unlocked Android, 50MP Zoom Camera, Long...",
price: 1.99,
category: categories["Smart phones"]!,
imageUrl:
"https://m.media-amazon.com/images/I/71ZoDT7a2wL._AC_UY218_.jpg",
description: "",
),
];
List<MyCategory> getCategories() => <MyCategory>[
MyCategory(id: "1", name: categories["Electronics"]!),
MyCategory(id: "2", name: categories["Smart phones"]!),
MyCategory(id: "3", name: categories["TV's"]!),
const MyCategory(id: "4", name: "Monitoren"),
const MyCategory(id: "5", name: "Speakers"),
const MyCategory(id: "6", name: "Toetsenborden"),
];
ProductPageContent getShopContent(String shopId) {
var products = getProducts(shopId);
return ProductPageContent(
products: products,
);
}
List<Product> getProducts(String categoryId) {
if (categoryId == "1") {
return allProducts();
} else if (categoryId == "2") {
return allProducts()
.where((product) => product.category == categories["Smart phones"]!)
.toList();
} else if (categoryId == "3") {
return allProducts()
.where((product) => product.category == categories["TV's"]!)
.toList();
} else {
return [];
}
}

View file

@ -1,20 +0,0 @@
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
class Homepage extends StatelessWidget {
const Homepage({super.key});
@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Badge(
label: const Text("1"),
child: IconButton(
icon: const Icon(Icons.shopping_cart_outlined, size: 50),
onPressed: () => context.go(FlutterShoppingPathRoutes.shop),
),
),
),
);
}

View file

@ -1,26 +0,0 @@
import "package:flutter/material.dart";
import "package:go_router/go_router.dart";
CustomTransitionPage buildScreenWithFadeTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) =>
CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
FadeTransition(opacity: animation, child: child),
);
CustomTransitionPage buildScreenWithoutTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) =>
CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
child,
);

View file

@ -1,43 +0,0 @@
import "package:flutter/material.dart";
ThemeData getTheme() => ThemeData(
scaffoldBackgroundColor: const Color.fromRGBO(250, 249, 246, 1),
textTheme: const TextTheme(
labelMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black,
),
titleMedium: TextStyle(
fontSize: 16,
color: Color.fromRGBO(60, 60, 59, 1),
fontWeight: FontWeight.w700,
),
),
inputDecorationTheme: const InputDecorationTheme(
fillColor: Colors.white,
),
colorScheme: const ColorScheme.light(
primary: Color.fromRGBO(161, 203, 211, 1),
secondary: Color.fromRGBO(221, 235, 238, 1),
surface: Color.fromRGBO(255, 255, 255, 1),
),
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromRGBO(161, 220, 218, 1),
foregroundColor: Colors.black,
titleTextStyle: TextStyle(
fontSize: 28,
color: Colors.white,
),
),
filledButtonTheme: FilledButtonThemeData(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(
Colors.yellow,
),
foregroundColor: WidgetStateProperty.all(
Colors.black,
),
),
),
);

View file

@ -1,31 +0,0 @@
name: amazon
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0
environment:
sdk: '>=3.4.1 <4.0.0'
dependencies:
flutter:
sdk: flutter
flutter_hooks: ^0.20.0
hooks_riverpod: ^2.1.1
go_router: 12.1.3
flutter_nested_categories:
git:
url: https://github.com/Iconica-Development/flutter_nested_categories
ref: 0.0.1
flutter_shopping:
git:
url: https://github.com/Iconica-Development/flutter_shopping
path: packages/flutter_shopping
ref: 2.0.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true

View file

@ -1,11 +0,0 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import "package:flutter_test/flutter_test.dart";
void main() {
test("", () {
expect(true, true);
});
}

View file

@ -5,8 +5,5 @@ export "package:flutter_order_details/flutter_order_details.dart";
export "package:flutter_product_page/flutter_product_page.dart";
export "package:flutter_shopping_cart/flutter_shopping_cart.dart";
export "src/config/flutter_shopping_configuration.dart";
export "src/models/product.dart";
export "src/routes.dart";
export "src/user_stores/flutter_shopping_userstory_go_router.dart";
export "src/user_stores/flutter_shopping_userstory_navigation.dart";
export "src/configuration/shopping_configuration.dart";
export "src/flutter_shopping_navigator_userstory.dart";

View file

@ -1,43 +0,0 @@
import "package:flutter/material.dart";
import "package:flutter_order_details/flutter_order_details.dart";
/// Configuration class for the flutter_shopping user-story.
class FlutterShoppingConfiguration {
/// Constructor for the FlutterShoppingConfiguration.
const FlutterShoppingConfiguration({
required this.shopBuilder,
required this.shoppingCartBuilder,
required this.onCompleteUserStory,
this.orderDetailsBuilder,
this.onCompleteOrderDetails,
this.orderSuccessBuilder,
this.orderFailedBuilder,
});
/// Builder for the shop/product page.
final Widget Function(
BuildContext context,
String? initialBuildShopId,
String? streetName,
) shopBuilder;
/// Builder for the shopping cart page.
final Widget Function(BuildContext context) shoppingCartBuilder;
/// Function that is called when the user-story is completed.
final Function(BuildContext context) onCompleteUserStory;
/// Builder for the order details page. This does not have to be set if you
/// are using the default order details page.
final Widget Function(BuildContext context)? orderDetailsBuilder;
/// Allows you to execute actions before
final Future<bool> Function(BuildContext context, OrderResult result)?
onCompleteOrderDetails;
/// Builder for when the order is successful.
final Widget Function(BuildContext context)? orderSuccessBuilder;
/// Builder for when the order failed.
final Widget Function(BuildContext context)? orderFailedBuilder;
}

View file

@ -1,10 +0,0 @@
/// Contains the localized strings for the order details screen.
class OrderDetailsLocalizations {
/// Creates order details localizations
OrderDetailsLocalizations({
this.orderDetailsTitle = "Information",
});
/// Title for the order details screen.
final String orderDetailsTitle;
}

View file

@ -0,0 +1,218 @@
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
/// configuration for the shopping userstory
class ShoppingConfiguration {
/// constructor for the userstory configuration
const ShoppingConfiguration({
/// ProductPage configurations
required this.shoppingService,
this.onGetProducts,
this.onGetShops,
this.onAddToCart,
this.onNavigateToShoppingCart,
this.getProductsInShoppingCart,
this.shoppingCartButtonBuilder,
this.initialShopid,
this.productBuilder,
this.onShopSelectionChange,
this.productPageTranslations,
this.shopSelectorStyle,
this.productPagePagePadding,
this.productPageAppBarBuilder,
this.bottomNavigationBarBuilder,
this.onProductDetail,
this.discountDescription,
this.noContentBuilder,
this.errorBuilder,
/// ShoppingCart configurations
this.onConfirmOrder,
this.productItemBuilder,
this.confirmOrderButtonBuilder,
this.confirmOrderButtonHeight,
this.sumBottomSheetBuilder,
this.sumBottomSheetHeight,
this.titleBuilder,
this.shoppingCartTranslations,
this.shoppingCartPagePadding,
this.shoppingCartBottomPadding,
this.shoppingCartAppBarBuilder,
/// OrderDetail configurations
this.onNextStep,
this.onStepsCompleted,
this.onCompleteOrderDetails,
this.pages,
this.orderDetailTranslations,
this.orderDetailAppBarBuilder,
this.orderDetailNextbuttonBuilder,
this.orderSuccessBuilder,
});
/// The service that will be used for the userstory
final ShoppingService shoppingService;
/// Function that will be called when the products are requested
final Future<List<Product>> Function(String shopId)? onGetProducts;
/// Function that will be called when the shops are requested
final Future<List<Shop>> Function()? onGetShops;
/// Function that will be called when an item is added to the shopping cart
final Function(Product)? onAddToCart;
/// Function that will be called when the user navigates to the shopping cart
final Function()? onNavigateToShoppingCart;
/// Function that will be called to get the amount of
/// products in the shopping cart
final int Function()? getProductsInShoppingCart;
/// Default shopping cart button builder
final Widget Function(BuildContext, ProductPageConfiguration)?
shoppingCartButtonBuilder;
/// Initial shop that will be selected
final String? initialShopid;
/// ProductPage item builder
final Widget Function(
BuildContext,
Product,
ProductPageConfiguration configuration,
)? productBuilder;
/// Function that will be called when the shop selection changes
final Function(Shop)? onShopSelectionChange;
/// Translations for the product page
final ProductPageTranslations? productPageTranslations;
/// Shop selector style
final ShopSelectorStyle? shopSelectorStyle;
/// ProductPage padding
final EdgeInsets? productPagePagePadding;
/// AppBar builder
final AppBar Function(BuildContext)? productPageAppBarBuilder;
/// BottomNavigationBarBuilder
final Widget? bottomNavigationBarBuilder;
/// Function that will be called when the product detail is requested
final Function(BuildContext, Product, String)? onProductDetail;
/// Function that will be called when the discount description is requested
final String Function(Product)? discountDescription;
/// Function that will be called when there are no products
final Widget Function(BuildContext)? noContentBuilder;
/// Function that will be called when there is an error
final Widget Function(BuildContext, Object?, StackTrace?)? errorBuilder;
/// Function that will be called when the order button on
/// the shopping cart page is pressed
final Function(List<Product>)? onConfirmOrder;
/// Shopping cart item builder
final Widget Function(BuildContext, Product, ShoppingCartConfig)?
productItemBuilder;
/// Shopping cart confirm order button builder
final Widget Function(
BuildContext,
ShoppingCartConfig,
dynamic Function(List<Product>),
)? confirmOrderButtonBuilder;
/// The height of the confirm order button
/// This will not set the height of the button itself
/// this is only used to create some extra space on the bottom
/// of the product list so the button doesn't overlap with the
/// last product
final double? confirmOrderButtonHeight;
/// Shopping cart sum bottom sheet builder
final Widget Function(BuildContext, ShoppingCartConfig)?
sumBottomSheetBuilder;
/// The height of the sum bottom sheet
/// This will not set the height of the sheet itself
/// this is only used to create some extra space on the bottom
/// of the product list so the sheet doesn't overlap with the
/// last product
final double? sumBottomSheetHeight;
/// Function to override the title on the shopping cart screen
final Widget Function(BuildContext, String)? titleBuilder;
/// Shopping cart translations
final ShoppingCartTranslations? shoppingCartTranslations;
/// Shopping cart page padding
final EdgeInsets? shoppingCartPagePadding;
/// Shopping cart bottom padding
final EdgeInsets? shoppingCartBottomPadding;
/// Shopping cart app bar builder
final AppBar Function(BuildContext)? shoppingCartAppBarBuilder;
// = _defaultPages,
// = const OrderDetailTranslations(),
// = _defaultAppBar,
// = _defaultNextButtonBuilder,
// = _defaultOrderSuccess,
/// Function that gets called when the user navigates to the next
/// step of the order details
final dynamic Function(
int,
Map<String, dynamic>,
FlutterFormController controller,
)? onNextStep;
/// Function that gets called when the Navigates
/// to the order confirmationp page
final dynamic Function(
String,
List<Product>,
Map<int, Map<String, dynamic>>,
OrderDetailConfiguration,
)? onStepsCompleted;
/// Function that gets called when pressing the complete order
/// button on the confirmation page
final Function(BuildContext, OrderDetailConfiguration)?
onCompleteOrderDetails;
/// The order detail pages that are used in the order detail screen
final List<FlutterFormPage> Function(BuildContext)? pages;
/// The translations for the order detail screen
final OrderDetailTranslations? orderDetailTranslations;
/// The app bar for the order detail screen
final AppBar Function(BuildContext, String)? orderDetailAppBarBuilder;
/// The builder for the next button on the order detail screen
final Widget Function(
int,
// ignore: avoid_positional_boolean_parameters
bool,
BuildContext,
OrderDetailConfiguration,
FlutterFormController,
)? orderDetailNextbuttonBuilder;
/// The builder for the order success screen
final Widget Function(
BuildContext,
OrderDetailConfiguration,
Map<int, Map<String, dynamic>>,
)? orderSuccessBuilder;
}

View file

@ -0,0 +1,209 @@
// ignore_for_file: public_member_api_docs
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:flutter_shopping_local/flutter_shopping_local.dart";
class ShoppingNavigatorUserStory extends StatelessWidget {
const ShoppingNavigatorUserStory({
this.shoppingConfiguration,
super.key,
});
final ShoppingConfiguration? shoppingConfiguration;
@override
Widget build(BuildContext context) => ShoppingProductPage(
shoppingConfiguration: shoppingConfiguration ??
ShoppingConfiguration(
shoppingService: LocalShoppingService(),
),
);
}
class ShoppingProductPage extends StatelessWidget {
const ShoppingProductPage({
required this.shoppingConfiguration,
super.key,
});
final ShoppingConfiguration shoppingConfiguration;
@override
Widget build(BuildContext context) {
var service = shoppingConfiguration.shoppingService;
return ProductPageScreen(
initialBuildShopId: shoppingConfiguration.initialShopid,
configuration: ProductPageConfiguration(
shoppingService: service,
initialShopId: shoppingConfiguration.initialShopid,
shoppingCartButtonBuilder:
shoppingConfiguration.shoppingCartButtonBuilder,
productBuilder: shoppingConfiguration.productBuilder,
onShopSelectionChange: shoppingConfiguration.onShopSelectionChange,
translations: shoppingConfiguration.productPageTranslations,
shopSelectorStyle: shoppingConfiguration.shopSelectorStyle,
pagePadding: shoppingConfiguration.productPagePagePadding,
appBar: shoppingConfiguration.productPageAppBarBuilder,
bottomNavigationBar: shoppingConfiguration.bottomNavigationBarBuilder,
onProductDetail: shoppingConfiguration.onProductDetail,
discountDescription: shoppingConfiguration.discountDescription,
noContentBuilder: shoppingConfiguration.noContentBuilder,
errorBuilder: shoppingConfiguration.errorBuilder,
shops: () async {
if (shoppingConfiguration.onGetShops != null) {
return shoppingConfiguration.onGetShops!();
} else {
return service.shopService.getShops();
}
},
getProducts: (shop) async {
if (shoppingConfiguration.onGetProducts != null) {
return shoppingConfiguration.onGetProducts!(shop.id);
} else {
return service.productService.getProducts(shop.id);
}
},
onAddToCart: (product) {
if (shoppingConfiguration.onAddToCart != null) {
shoppingConfiguration.onAddToCart!(product);
return;
} else {
return service.shoppingCartService.addProduct(product);
}
},
onNavigateToShoppingCart: () async {
if (shoppingConfiguration.onNavigateToShoppingCart != null) {
return shoppingConfiguration.onNavigateToShoppingCart!();
} else {
return Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ShoppingCart(
shoppingConfiguration: shoppingConfiguration,
),
),
);
}
},
getProductsInShoppingCart: () {
if (shoppingConfiguration.getProductsInShoppingCart != null) {
return shoppingConfiguration.getProductsInShoppingCart!();
} else {
return service.shoppingCartService.countProducts();
}
},
),
);
}
}
class ShoppingCart extends StatelessWidget {
const ShoppingCart({
required this.shoppingConfiguration,
super.key,
});
final ShoppingConfiguration shoppingConfiguration;
@override
Widget build(BuildContext context) {
var service = shoppingConfiguration.shoppingService.shoppingCartService;
return ShoppingCartScreen(
configuration: ShoppingCartConfig(
service: service,
productItemBuilder: shoppingConfiguration.productItemBuilder,
confirmOrderButtonBuilder:
shoppingConfiguration.confirmOrderButtonBuilder,
confirmOrderButtonHeight:
shoppingConfiguration.confirmOrderButtonHeight,
sumBottomSheetBuilder: shoppingConfiguration.sumBottomSheetBuilder,
sumBottomSheetHeight: shoppingConfiguration.sumBottomSheetHeight,
titleBuilder: shoppingConfiguration.titleBuilder,
translations: shoppingConfiguration.shoppingCartTranslations,
pagePadding: shoppingConfiguration.shoppingCartPagePadding,
bottomPadding: shoppingConfiguration.shoppingCartBottomPadding,
appBar: shoppingConfiguration.shoppingCartAppBarBuilder,
onConfirmOrder: (products) async {
if (shoppingConfiguration.onConfirmOrder != null) {
return shoppingConfiguration.onConfirmOrder!(products);
} else {
return Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ShoppingOrderDetails(
shoppingConfiguration: shoppingConfiguration,
),
),
);
}
},
),
);
}
}
class ShoppingOrderDetails extends StatelessWidget {
const ShoppingOrderDetails({
required this.shoppingConfiguration,
super.key,
});
final ShoppingConfiguration shoppingConfiguration;
@override
Widget build(BuildContext context) => OrderDetailScreen(
configuration: OrderDetailConfiguration(
shoppingService: shoppingConfiguration.shoppingService,
pages: shoppingConfiguration.pages,
translations: shoppingConfiguration.orderDetailTranslations,
appBar: shoppingConfiguration.orderDetailAppBarBuilder,
nextbuttonBuilder: shoppingConfiguration.orderDetailNextbuttonBuilder,
orderSuccessBuilder: shoppingConfiguration.orderSuccessBuilder,
onNextStep: (currentStep, data, controller) async {
if (shoppingConfiguration.onNextStep != null) {
return shoppingConfiguration.onNextStep!(
currentStep,
data,
controller,
);
} else {
await controller.autoNextStep();
}
},
onStepsCompleted: (shopId, products, data, configuration) async {
if (shoppingConfiguration.onStepsCompleted != null) {
return shoppingConfiguration.onStepsCompleted!(
shopId,
products,
data,
configuration,
);
} else {
return Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => DefaultOrderSucces(
configuration: configuration,
orderDetails: data,
),
),
);
}
},
onCompleteOrderDetails: (context, configuration) async {
if (shoppingConfiguration.onCompleteOrderDetails != null) {
return shoppingConfiguration.onCompleteOrderDetails!(
context,
configuration,
);
} else {
shoppingConfiguration.shoppingService.shoppingCartService.clear();
return Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => ShoppingProductPage(
shoppingConfiguration: shoppingConfiguration,
),
),
);
}
},
),
);
}

View file

@ -1,28 +0,0 @@
import "package:flutter/material.dart";
import "package:go_router/go_router.dart";
/// Builder with a fade transition for when navigating to a new screen.
CustomTransitionPage buildScreenWithFadeTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) =>
CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
FadeTransition(opacity: animation, child: child),
);
/// Builder without a transition for when navigating to a new screen.
CustomTransitionPage buildScreenWithoutTransition<T>({
required BuildContext context,
required GoRouterState state,
required Widget child,
}) =>
CustomTransitionPage<T>(
key: state.pageKey,
child: child,
transitionsBuilder: (context, animation, secondaryAnimation, child) =>
child,
);

View file

@ -1,43 +0,0 @@
/// The product class contains all the information that a product can have.
/// This class is used in the shopping cart and the product page.
class Product {
/// Creates a product.
Product({
required this.id,
required this.name,
required this.imageUrl,
required this.category,
required this.price,
required this.description,
this.hasDiscount = false,
this.discountPrice,
this.quantity = 1,
});
/// The unique identifier for the product.
final String id;
/// The name of the product.
final String name;
/// The image URL of the product.
final String imageUrl;
/// The category of the product.
final String category;
/// The price of the product.
final double price;
/// Whether the product has a discount or not.
final bool hasDiscount;
/// The discounted price of the product. Only used if [hasDiscount] is true.
final double? discountPrice;
/// Quantity for the product.
int quantity;
/// The description of the product.
final String description;
}

View file

@ -1,35 +0,0 @@
/// All the name routes used in the user-story.
mixin FlutterShoppingNameRoutes {
/// The shop name route.
static const String shop = "shop";
/// The shopping cart name route.
static const String shoppingCart = "shoppingcart";
/// The order details name route.
static const String orderDetails = "orderdetails";
/// The order success name route.
static const String orderSuccess = "ordersuccess";
/// The order failed name route.
static const String orderFailed = "orderfailed";
}
/// All the path routes used in the user-story.
mixin FlutterShoppingPathRoutes {
/// The shop page route.
static const String shop = "/shop";
/// The shopping cart page route.
static const String shoppingCart = "/shopping-cart";
/// The order details page route.
static const String orderDetails = "/order-details";
/// The order success page route.
static const String orderSuccess = "/order-success";
/// The order failed page route.
static const String orderFailed = "/order-failed";
}

View file

@ -1,52 +0,0 @@
import "package:flutter_shopping/flutter_shopping.dart";
import "package:flutter_shopping/src/widgets/default_order_failed_widget.dart";
import "package:flutter_shopping/src/widgets/default_order_succes_widget.dart";
import "package:go_router/go_router.dart";
/// All the routes for the shopping story.
List<GoRoute> getShoppingStoryRoutes({
required FlutterShoppingConfiguration configuration,
}) =>
<GoRoute>[
GoRoute(
name: FlutterShoppingNameRoutes.shop,
path: FlutterShoppingPathRoutes.shop,
builder: (context, state) => configuration.shopBuilder(
context,
state.uri.queryParameters["id"],
state.uri.queryParameters["street"],
),
),
GoRoute(
name: FlutterShoppingNameRoutes.shoppingCart,
path: FlutterShoppingPathRoutes.shoppingCart,
builder: (context, state) => configuration.shoppingCartBuilder(context),
),
GoRoute(
name: FlutterShoppingNameRoutes.orderDetails,
path: FlutterShoppingPathRoutes.orderDetails,
builder: (context, state) => configuration.orderDetailsBuilder != null
? configuration.orderDetailsBuilder!(context)
: OrderDetailScreen(
configuration: OrderDetailConfiguration(
onCompleted: (result) {
context.go(FlutterShoppingPathRoutes.orderSuccess);
},
),
),
),
GoRoute(
name: FlutterShoppingNameRoutes.orderSuccess,
path: FlutterShoppingPathRoutes.orderSuccess,
builder: (context, state) => configuration.orderSuccessBuilder != null
? configuration.orderSuccessBuilder!(context)
: DefaultOrderSucces(configuration: configuration),
),
GoRoute(
name: FlutterShoppingNameRoutes.orderFailed,
path: FlutterShoppingPathRoutes.orderFailed,
builder: (context, state) => configuration.orderFailedBuilder != null
? configuration.orderFailedBuilder!(context)
: DefaultOrderFailed(configuration: configuration),
),
];

View file

@ -1,52 +0,0 @@
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:go_router/go_router.dart";
/// Default on complete order details function.
/// This function will navigate to the order success or order failed page.
///
/// You can create your own implementation if you decide to use a different
/// approach.
Future<void> onCompleteOrderDetails(
BuildContext context,
FlutterShoppingConfiguration configuration,
OrderResult result,
) async {
var go = context.go;
var succesful = true;
if (configuration.onCompleteOrderDetails != null) {
var executionResult =
await configuration.onCompleteOrderDetails?.call(context, result);
if (executionResult == null || !executionResult) {
succesful = false;
}
}
if (succesful) {
go(FlutterShoppingPathRoutes.orderSuccess);
} else {
go(FlutterShoppingPathRoutes.orderFailed);
}
}
/// Default on complete shopping cart function.
///
/// You can create your own implementation if you decide to use a different
/// approach.
Future<void> onCompleteShoppingCart(
BuildContext context,
) async {
await context.push(FlutterShoppingPathRoutes.orderDetails);
}
/// Default on complete product page function.
///
/// You can create your own implementation if you decide to use a different
/// approach.
Future<void> onCompleteProductPage(
BuildContext context,
) async {
await context.push(FlutterShoppingPathRoutes.shoppingCart);
}

View file

@ -1,10 +1,10 @@
name: flutter_shopping
description: "A new Flutter project."
publish_to: 'none'
publish_to: "none"
version: 2.0.0
environment:
sdk: '>=3.3.4 <4.0.0'
sdk: ">=3.3.4 <4.0.0"
dependencies:
flutter:
@ -25,6 +25,16 @@ dependencies:
url: https://github.com/Iconica-Development/flutter_shopping
ref: 2.0.0
path: packages/flutter_order_details
flutter_shopping_interface:
git:
url: https://github.com/Iconica-Development/flutter_shopping
ref: 2.0.0
path: packages/flutter_shopping_interface
flutter_shopping_local:
git:
url: https://github.com/Iconica-Development/flutter_shopping
ref: 2.0.0
path: packages/flutter_shopping_local
dev_dependencies:
flutter_test:
@ -35,4 +45,3 @@ dev_dependencies:
ref: 7.0.0
flutter:

View file

@ -11,17 +11,27 @@ class ShoppingCartConfig {
ShoppingCartConfig({
required this.service,
required this.onConfirmOrder,
this.productItemBuilder = _defaultProductItemBuilder,
this.confirmOrderButtonBuilder = _defaultConfirmOrderButton,
this.confirmOrderButtonHeight = 100,
this.sumBottomSheetBuilder = _defaultSumBottomSheetBuilder,
this.sumBottomSheetHeight = 100,
this.productItemBuilder,
this.confirmOrderButtonBuilder,
this.confirmOrderButtonHeight,
this.sumBottomSheetBuilder,
this.sumBottomSheetHeight,
this.titleBuilder,
this.translations = const ShoppingCartTranslations(),
this.pagePadding = const EdgeInsets.symmetric(horizontal: 32),
this.bottomPadding = const EdgeInsets.fromLTRB(44, 0, 44, 32),
this.appBar = _defaultAppBar,
});
this.translations,
this.pagePadding,
this.bottomPadding,
this.appBar,
}) {
productItemBuilder ??= _defaultProductItemBuilder;
confirmOrderButtonBuilder ??= _defaultConfirmOrderButton;
sumBottomSheetBuilder ??= _defaultSumBottomSheetBuilder;
appBar ??= _defaultAppBar;
translations ??= const ShoppingCartTranslations();
pagePadding ??= const EdgeInsets.symmetric(horizontal: 32);
bottomPadding ??= const EdgeInsets.fromLTRB(44, 0, 44, 32);
confirmOrderButtonHeight ??= 100;
sumBottomSheetHeight ??= 100;
}
/// Product service. The product service is used to manage the products in the
/// shopping cart.
@ -29,26 +39,26 @@ class ShoppingCartConfig {
/// Product item builder. This builder is used to build the product item
/// that will be displayed in the shopping cart.
final Widget Function(
Widget Function(
BuildContext context,
Product product,
ShoppingCartConfig configuration,
) productItemBuilder;
)? productItemBuilder;
/// 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(
Widget Function(
BuildContext context,
ShoppingCartConfig configuration,
Function(List<Product> products) onConfirmOrder,
) confirmOrderButtonBuilder;
)? 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.
/// If you override the confirm order button builder, you must provide a
/// height.
final double confirmOrderButtonHeight;
double? confirmOrderButtonHeight;
/// Confirm order callback. This callback is called when the confirm order
/// button is pressed. The callback will not be called if you override the
@ -58,22 +68,22 @@ class ShoppingCartConfig {
/// 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, ShoppingCartConfig configuration)
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.
/// If you override the sum bottom sheet builder, you must provide a height.
final double sumBottomSheetHeight;
double? sumBottomSheetHeight;
/// Padding around the shopping cart. The padding is used to create space
/// around the shopping cart.
final EdgeInsets pagePadding;
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;
EdgeInsets? bottomPadding;
/// Title builder. This builder is used to
/// build the title of the shopping cart.
@ -83,10 +93,10 @@ class ShoppingCartConfig {
)? titleBuilder;
/// Shopping cart translations. The translations for the shopping cart.
final ShoppingCartTranslations translations;
ShoppingCartTranslations? translations;
/// Appbar for the shopping cart screen.
final AppBar Function(BuildContext context) appBar;
AppBar Function(BuildContext context)? appBar;
}
Widget _defaultProductItemBuilder(
@ -217,11 +227,11 @@ Widget _defaultSumBottomSheetBuilder(
);
return Padding(
padding: configuration.bottomPadding,
padding: configuration.bottomPadding!,
child: Row(
children: [
Text(
configuration.translations.sum,
configuration.translations!.sum,
style: theme.textTheme.titleMedium,
),
const Spacer(),
@ -261,7 +271,7 @@ Widget _defaultConfirmOrderButton(
vertical: 12,
),
child: Text(
configuration.translations.placeOrder,
configuration.translations!.placeOrder,
style: theme.textTheme.displayLarge,
),
),

View file

@ -50,7 +50,7 @@ class ProductItemPopup extends StatelessWidget {
vertical: 8.0,
),
child: Text(
configuration.translations.close,
configuration.translations!.close,
style: theme.textTheme.displayLarge,
),
),

View file

@ -17,20 +17,20 @@ class ShoppingCartScreen extends StatelessWidget {
var theme = Theme.of(context);
return Scaffold(
appBar: configuration.appBar.call(context),
appBar: configuration.appBar?.call(context),
body: SafeArea(
child: Stack(
fit: StackFit.expand,
children: [
Padding(
padding: configuration.pagePadding,
padding: configuration.pagePadding!,
child: SingleChildScrollView(
child: Column(
children: [
if (configuration.titleBuilder != null) ...{
configuration.titleBuilder!(
context,
configuration.translations.cartTitle,
configuration.translations!.cartTitle,
),
} else ...{
Padding(
@ -40,7 +40,7 @@ class ShoppingCartScreen extends StatelessWidget {
child: Row(
children: [
Text(
configuration.translations.cartTitle,
configuration.translations!.cartTitle,
style: theme.textTheme.titleLarge,
textAlign: TextAlign.start,
),
@ -53,7 +53,7 @@ class ShoppingCartScreen extends StatelessWidget {
builder: (context, _) => Column(
children: [
for (var product in configuration.service.products)
configuration.productItemBuilder(
configuration.productItemBuilder!(
context,
product,
configuration,
@ -63,8 +63,8 @@ class ShoppingCartScreen extends StatelessWidget {
// the bottom to make sure the last
// product(s) are not hidden by the bottom sheet.
SizedBox(
height: configuration.confirmOrderButtonHeight +
configuration.sumBottomSheetHeight,
height: configuration.confirmOrderButtonHeight! +
configuration.sumBottomSheetHeight!,
),
],
),
@ -100,11 +100,11 @@ class _BottomSheet extends StatelessWidget {
ListenableBuilder(
listenable: configuration.service,
builder: (BuildContext context, Widget? child) =>
configuration.sumBottomSheetBuilder(context, configuration),
configuration.sumBottomSheetBuilder!(context, configuration),
),
ListenableBuilder(
listenable: configuration.service,
builder: (context, _) => configuration.confirmOrderButtonBuilder(
builder: (context, _) => configuration.confirmOrderButtonBuilder!(
context,
configuration,
configuration.onConfirmOrder,

View file

@ -15,9 +15,6 @@ dependencies:
url: https://github.com/Iconica-Development/flutter_shopping
path: packages/flutter_shopping_interface
ref: 2.0.0
dependency_overrides:
flutter_shopping_interface:
path: ../flutter_shopping_interface
dev_dependencies:
flutter_test: