From a0fe3b6f675d33566c547537f9bedac0a8143e31 Mon Sep 17 00:00:00 2001 From: markkiepe Date: Sun, 26 May 2024 19:27:47 +0200 Subject: [PATCH] feat: intial user story code --- example/.gitignore | 53 ++++++ example/README.md | 16 ++ example/analysis_options.yaml | 7 + example/environment_config.yaml | 8 + example/lib/main.dart | 22 +++ .../lib/src/configuration/configuration.dart | 165 ++++++++++++++++++ example/lib/src/models/my_product.dart | 26 +++ example/lib/src/models/my_shop.dart | 8 + example/lib/src/routes.dart | 31 ++++ example/lib/src/services/order_service.dart | 7 + example/lib/src/services/shop_service.dart | 47 +++++ example/lib/src/ui/homepage.dart | 22 +++ example/lib/src/utils/go_router.dart | 26 +++ example/lib/src/utils/theme.dart | 31 ++++ example/pubspec.yaml | 56 ++++++ lib/main.dart | 4 + .../default_order_detail_configuration.dart | 70 ++++++++ .../flutter_shopping_configuration.dart | 46 +++++ lib/src/go_router.dart | 28 +++ lib/src/routes.dart | 17 ++ .../flutter_shopping_userstory_go_router.dart | 98 +++++++++++ ...flutter_shopping_userstory_navigation.dart | 43 +++++ .../widgets/default_order_failed_widget.dart | 65 +++++++ .../widgets/default_order_succes_widget.dart | 66 +++++++ pubspec.yaml | 5 +- 25 files changed, 965 insertions(+), 2 deletions(-) create mode 100644 example/.gitignore create mode 100644 example/README.md create mode 100644 example/analysis_options.yaml create mode 100644 example/environment_config.yaml create mode 100644 example/lib/main.dart create mode 100644 example/lib/src/configuration/configuration.dart create mode 100644 example/lib/src/models/my_product.dart create mode 100644 example/lib/src/models/my_shop.dart create mode 100644 example/lib/src/routes.dart create mode 100644 example/lib/src/services/order_service.dart create mode 100644 example/lib/src/services/shop_service.dart create mode 100644 example/lib/src/ui/homepage.dart create mode 100644 example/lib/src/utils/go_router.dart create mode 100644 example/lib/src/utils/theme.dart create mode 100644 example/pubspec.yaml create mode 100644 lib/main.dart create mode 100644 lib/src/config/default_order_detail_configuration.dart create mode 100644 lib/src/config/flutter_shopping_configuration.dart create mode 100644 lib/src/go_router.dart create mode 100644 lib/src/routes.dart create mode 100644 lib/src/user_stores/flutter_shopping_userstory_go_router.dart create mode 100644 lib/src/user_stores/flutter_shopping_userstory_navigation.dart create mode 100644 lib/src/widgets/default_order_failed_widget.dart create mode 100644 lib/src/widgets/default_order_succes_widget.dart diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..8760a85 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,53 @@ +# 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/ diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..2b3fce4 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# 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. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..2a97d5c --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,7 @@ +include: package:flutter_iconica_analysis/analysis_options.yaml + +analyzer: + exclude: + +linter: + rules: diff --git a/example/environment_config.yaml b/example/environment_config.yaml new file mode 100644 index 0000000..9e1dd5d --- /dev/null +++ b/example/environment_config.yaml @@ -0,0 +1,8 @@ +environment_config: + dotenv_path: dotenv + path: env_config.dart + fields: + STRIPE_PUBLISHABLE_KEY: + dotenv: true + config_field: false + default: "" diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..1157c00 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,22 @@ +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), + ); +} diff --git a/example/lib/src/configuration/configuration.dart b/example/lib/src/configuration/configuration.dart new file mode 100644 index 0000000..67cbd4b --- /dev/null +++ b/example/lib/src/configuration/configuration.dart @@ -0,0 +1,165 @@ +import "package:example/src/models/my_product.dart"; +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_order_details/flutter_order_details.dart"; +import "package:flutter_product_page/flutter_product_page.dart"; +import "package:flutter_shopping/main.dart"; +import "package:flutter_shopping_cart/flutter_shopping_cart.dart"; +import "package:go_router/go_router.dart"; + +// (REQUIRED): Create your own instance of the ProductService. +final ProductService productService = ProductService([]); + +FlutterShoppingConfiguration getFlutterShoppingConfiguration() => + FlutterShoppingConfiguration( + // (REQUIRED): Shop builder configuration + shopBuilder: (BuildContext context) => ProductPageScreen( + configuration: ProductPageConfiguration( + // (REQUIRED): List of shops that should be displayed + // If there is only one, make a list with just one shop. + shops: getShops(), + + // (REQUIRED): Function to add a product to the cart + onAddToCart: (ProductPageProduct product) => + productService.addProduct(product as MyProduct), + + // (REQUIRED): Function to get the products for a shop + getProducts: (String shopId) => Future.value( + getShopContent(shopId), + ), + + // (REQUIRED): Function to navigate to the shopping cart + onNavigateToShoppingCart: () => 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: (ProductPageProduct 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: (shopId) => productService.clear(), + + // (RECOMMENDED) The shop that is initially selected. + // Must be one of the shops in the [shops] list. + initialShop: getShops().first, + + // (RECOMMENDED) Localizations for the product page. + localizations: const ProductPageLocalization(), + + // (OPTIONAL) Appbar + appBar: AppBar( + title: const Text("Shop"), + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors.white, + ), + onPressed: () { + context.go(homePage); + }, + ), + ), + ), + ), + + // (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) => 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) title above product list: + title: "Products", + + // (OPTIONAL) custom appbar: + appBar: AppBar( + title: const Text("Shopping Cart"), + leading: IconButton( + icon: const Icon( + Icons.arrow_back, + color: Colors.white, + ), + onPressed: () { + context.go(FlutterShoppingRoutes.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; + }, + ); diff --git a/example/lib/src/models/my_product.dart b/example/lib/src/models/my_product.dart new file mode 100644 index 0000000..d066d31 --- /dev/null +++ b/example/lib/src/models/my_product.dart @@ -0,0 +1,26 @@ +import "package:flutter_product_page/flutter_product_page.dart"; +import "package:flutter_shopping_cart/flutter_shopping_cart.dart"; + +class MyProduct extends ShoppingCartProduct with ProductPageProduct { + MyProduct({ + required super.id, + required super.name, + required super.price, + required this.category, + required this.imageUrl, + this.discountPrice, + this.hasDiscount = false, + }); + + @override + final String category; + + @override + final String imageUrl; + + @override + final double? discountPrice; + + @override + final bool hasDiscount; +} diff --git a/example/lib/src/models/my_shop.dart b/example/lib/src/models/my_shop.dart new file mode 100644 index 0000000..ddf05b9 --- /dev/null +++ b/example/lib/src/models/my_shop.dart @@ -0,0 +1,8 @@ +import "package:flutter_product_page/flutter_product_page.dart"; + +class MyShop extends ProductPageShop { + const MyShop({ + required super.id, + required super.name, + }); +} diff --git a/example/lib/src/routes.dart b/example/lib/src/routes.dart new file mode 100644 index 0000000..4df9e53 --- /dev/null +++ b/example/lib/src/routes.dart @@ -0,0 +1,31 @@ +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/main.dart"; +import "package:go_router/go_router.dart"; +import "package:hooks_riverpod/hooks_riverpod.dart"; + +const String homePage = "/"; + +final routerProvider = Provider( + (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(), + ), + ), + ], + ), +); diff --git a/example/lib/src/services/order_service.dart b/example/lib/src/services/order_service.dart new file mode 100644 index 0000000..2dbaa45 --- /dev/null +++ b/example/lib/src/services/order_service.dart @@ -0,0 +1,7 @@ +import "package:example/src/models/my_product.dart"; +import "package:flutter_order_details/flutter_order_details.dart"; + +/// Example implementation of storing an order in a database. +void storeOrderInDatabase(List products, OrderResult result) { + return; +} diff --git a/example/lib/src/services/shop_service.dart b/example/lib/src/services/shop_service.dart new file mode 100644 index 0000000..414076d --- /dev/null +++ b/example/lib/src/services/shop_service.dart @@ -0,0 +1,47 @@ +import "package:example/src/models/my_product.dart"; +import "package:example/src/models/my_shop.dart"; +import "package:flutter_product_page/flutter_product_page.dart"; + +/// This function should have your own implementation. Generally this would +/// contain some API call to fetch the list of shops. +List getShops() => [ + 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 getProducts(String shopId) => [ + MyProduct( + id: "1", + name: "White bread", + price: 2.99, + category: "Loaves", + imageUrl: "https://via.placeholder.com/150", + hasDiscount: true, + discountPrice: 1.99, + ), + MyProduct( + id: "2", + name: "Brown bread", + price: 2.99, + category: "Loaves", + imageUrl: "https://via.placeholder.com/150", + ), + MyProduct( + id: "3", + name: "Cheese sandwich", + price: 1.99, + category: "Sandwiches", + imageUrl: "https://via.placeholder.com/150", + ), + ]; diff --git a/example/lib/src/ui/homepage.dart b/example/lib/src/ui/homepage.dart new file mode 100644 index 0000000..dbfe6f6 --- /dev/null +++ b/example/lib/src/ui/homepage.dart @@ -0,0 +1,22 @@ +import "package:flutter/material.dart"; +import "package:flutter_shopping/main.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(FlutterShoppingRoutes.shop); + }, + ), + ), + ), + ); +} diff --git a/example/lib/src/utils/go_router.dart b/example/lib/src/utils/go_router.dart new file mode 100644 index 0000000..d7b239a --- /dev/null +++ b/example/lib/src/utils/go_router.dart @@ -0,0 +1,26 @@ +import "package:flutter/material.dart"; +import "package:go_router/go_router.dart"; + +CustomTransitionPage buildScreenWithFadeTransition({ + required BuildContext context, + required GoRouterState state, + required Widget child, +}) => + CustomTransitionPage( + key: state.pageKey, + child: child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition(opacity: animation, child: child), + ); + +CustomTransitionPage buildScreenWithoutTransition({ + required BuildContext context, + required GoRouterState state, + required Widget child, +}) => + CustomTransitionPage( + key: state.pageKey, + child: child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + child, + ); diff --git a/example/lib/src/utils/theme.dart b/example/lib/src/utils/theme.dart new file mode 100644 index 0000000..9c527c2 --- /dev/null +++ b/example/lib/src/utils/theme.dart @@ -0,0 +1,31 @@ +import "package:flutter/material.dart"; + +ThemeData getTheme() => ThemeData( + textTheme: const TextTheme( + labelMedium: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: Color.fromRGBO(0, 0, 0, 1), + ), + titleMedium: TextStyle( + fontSize: 16, + color: Color.fromRGBO(60, 60, 59, 1), + fontWeight: FontWeight.w700, + ), + ), + inputDecorationTheme: const InputDecorationTheme( + fillColor: Color.fromRGBO(255, 255, 255, 1), + ), + colorScheme: const ColorScheme.light( + primary: Color.fromRGBO(64, 87, 122, 1), + secondary: Color.fromRGBO(255, 255, 255, 1), + surface: Color.fromRGBO(250, 249, 246, 1), + ), + appBarTheme: const AppBarTheme( + backgroundColor: Color.fromRGBO(64, 87, 122, 1), + titleTextStyle: TextStyle( + fontSize: 28, + color: Color.fromRGBO(255, 255, 255, 1), + ), + ), + ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..d34731d --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,56 @@ +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+1 + +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 + + # Iconica packages + + ## Userstories + flutter_shopping: + path: ../ + + ## Normal Packages + flutter_product_page: + git: + url: https://github.com/Iconica-Development/flutter_product_page + ref: fix/missing-features + flutter_shopping_cart: + git: + url: https://github.com/Iconica-Development/flutter_shopping_cart + ref: fix/missing-features + flutter_order_details: + git: + url: https://github.com/Iconica-Development/flutter_order_details + ref: feat/v1.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: + - dotenv + # - 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 diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..bd87a53 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,4 @@ +export "package:flutter_shopping/src/config/flutter_shopping_configuration.dart"; +export "package:flutter_shopping/src/routes.dart"; +export "package:flutter_shopping/src/user_stores/flutter_shopping_userstory_go_router.dart"; +export "package:flutter_shopping/src/user_stores/flutter_shopping_userstory_navigation.dart"; diff --git a/lib/src/config/default_order_detail_configuration.dart b/lib/src/config/default_order_detail_configuration.dart new file mode 100644 index 0000000..da9ffe1 --- /dev/null +++ b/lib/src/config/default_order_detail_configuration.dart @@ -0,0 +1,70 @@ +import "package:flutter/material.dart"; +import "package:flutter_order_details/flutter_order_details.dart"; +import "package:flutter_shopping/main.dart"; +import "package:flutter_shopping/src/user_stores/flutter_shopping_userstory_navigation.dart"; +import "package:go_router/go_router.dart"; + +/// TODO +OrderDetailConfiguration getDefaultOrderDetailConfiguration( + BuildContext context, + FlutterShoppingConfiguration configuration, +) => + OrderDetailConfiguration( + steps: [ + OrderDetailStep( + formKey: GlobalKey(), + stepName: "Basic Information", + fields: [ + OrderTextInput( + title: "First name", + outputKey: "first_name", + textController: TextEditingController(), + ), + OrderTextInput( + title: "Last name", + outputKey: "last_name", + textController: TextEditingController(), + ), + OrderEmailInput( + title: "Your email address", + outputKey: "email", + textController: TextEditingController(), + subtitle: "* We will send your order confirmation here", + // hint: "" + ), + ], + ), + OrderDetailStep( + formKey: GlobalKey(), + stepName: "Adress Information", + fields: [ + OrderAdresInput( + title: "Your adress", + outputKey: "adres", + textController: TextEditingController(), + ), + ], + ), + OrderDetailStep( + formKey: GlobalKey(), + stepName: "Payment Information", + fields: [ + OrderChoiceInput( + title: "Payment option", + outputKey: "payment_option", + items: ["Pay now", "Pay later"], + ), + ], + ), + ], + onCompleted: (OrderResult result) async { + await onCompleteOrderDetails(context, configuration, result); + }, + appBar: AppBar( + title: const Text("Order Details"), + leading: IconButton( + icon: const Icon(Icons.close, color: Colors.white), + onPressed: () => context.go(FlutterShoppingRoutes.shoppingCart), + ), + ), + ); diff --git a/lib/src/config/flutter_shopping_configuration.dart b/lib/src/config/flutter_shopping_configuration.dart new file mode 100644 index 0000000..e11a582 --- /dev/null +++ b/lib/src/config/flutter_shopping_configuration.dart @@ -0,0 +1,46 @@ +import "package:flutter/material.dart"; +import "package:flutter_order_details/flutter_order_details.dart"; + +/// TODO +class FlutterShoppingConfiguration { + /// TODO + const FlutterShoppingConfiguration({ + required this.shopBuilder, + required this.shoppingCartBuilder, + required this.onCompleteUserStory, + this.showOrderDetails = false, + this.orderDetailsBuilder, + this.onCompleteOrderDetails, + this.orderSuccessBuilder, + this.orderFailedBuilder, + }) : assert( + showOrderDetails && orderDetailsBuilder != null || + !showOrderDetails && orderDetailsBuilder == null, + "showOrderDetails and orderDetailsBuilder must be both set or unset.", + ); + + /// TODO + final Widget Function(BuildContext context) shopBuilder; + + /// TODO + final Widget Function(BuildContext context) shoppingCartBuilder; + + /// TODO + final Function(BuildContext context) onCompleteUserStory; + + /// TODO + final bool showOrderDetails; + + /// TODO + final Widget Function(BuildContext context)? orderDetailsBuilder; + + /// Allows you to execute actions before + final Future Function(BuildContext context, OrderResult result)? + onCompleteOrderDetails; + + /// TODO + final Widget Function(BuildContext context)? orderSuccessBuilder; + + /// TODO + final Widget Function(BuildContext context)? orderFailedBuilder; +} diff --git a/lib/src/go_router.dart b/lib/src/go_router.dart new file mode 100644 index 0000000..b2fc911 --- /dev/null +++ b/lib/src/go_router.dart @@ -0,0 +1,28 @@ +import "package:flutter/material.dart"; +import "package:go_router/go_router.dart"; + +/// TODO +CustomTransitionPage buildScreenWithFadeTransition({ + required BuildContext context, + required GoRouterState state, + required Widget child, +}) => + CustomTransitionPage( + key: state.pageKey, + child: child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + FadeTransition(opacity: animation, child: child), + ); + +/// TODO +CustomTransitionPage buildScreenWithoutTransition({ + required BuildContext context, + required GoRouterState state, + required Widget child, +}) => + CustomTransitionPage( + key: state.pageKey, + child: child, + transitionsBuilder: (context, animation, secondaryAnimation, child) => + child, + ); diff --git a/lib/src/routes.dart b/lib/src/routes.dart new file mode 100644 index 0000000..4b2c52c --- /dev/null +++ b/lib/src/routes.dart @@ -0,0 +1,17 @@ +/// TODO +mixin FlutterShoppingRoutes { + /// TODO + static const String shop = "/shop"; + + /// TODO + static const String shoppingCart = "/shopping-cart"; + + /// TODO + static const String orderDetails = "/order-details"; + + /// TODO + static const String orderSuccess = "/order-success"; + + /// TODO + static const String orderFailed = "/order-failed"; +} diff --git a/lib/src/user_stores/flutter_shopping_userstory_go_router.dart b/lib/src/user_stores/flutter_shopping_userstory_go_router.dart new file mode 100644 index 0000000..6629092 --- /dev/null +++ b/lib/src/user_stores/flutter_shopping_userstory_go_router.dart @@ -0,0 +1,98 @@ +import "package:flutter/material.dart"; +import "package:flutter_order_details/flutter_order_details.dart"; +import "package:flutter_shopping/main.dart"; +import "package:flutter_shopping/src/config/default_order_detail_configuration.dart"; +import "package:flutter_shopping/src/go_router.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"; + +/// TODO +List getShoppingStoryRoutes({ + required FlutterShoppingConfiguration configuration, +}) => + [ + GoRoute( + name: "shop", + path: FlutterShoppingRoutes.shop, + pageBuilder: (BuildContext context, GoRouterState state) => + buildScreenWithFadeTransition( + context: context, + state: state, + child: configuration.shopBuilder(context), + ), + ), + GoRoute( + name: "shoppingCart", + path: FlutterShoppingRoutes.shoppingCart, + pageBuilder: (BuildContext context, GoRouterState state) => + buildScreenWithFadeTransition( + context: context, + state: state, + child: configuration.shoppingCartBuilder(context), + ), + ), + GoRoute( + name: "orderDetails", + path: FlutterShoppingRoutes.orderDetails, + pageBuilder: (BuildContext context, GoRouterState state) { + if (configuration.showOrderDetails && + configuration.orderDetailsBuilder != null) { + return buildScreenWithFadeTransition( + context: context, + state: state, + child: configuration.orderDetailsBuilder!(context), + ); + } + + return buildScreenWithFadeTransition( + context: context, + state: state, + child: OrderDetailScreen( + configuration: + getDefaultOrderDetailConfiguration(context, configuration), + ), + ); + }, + ), + GoRoute( + name: "orderSuccess", + path: FlutterShoppingRoutes.orderSuccess, + pageBuilder: (BuildContext context, GoRouterState state) { + if (configuration.orderSuccessBuilder != null) { + return buildScreenWithFadeTransition( + context: context, + state: state, + child: configuration.orderSuccessBuilder!(context), + ); + } + + return buildScreenWithFadeTransition( + context: context, + state: state, + child: DefaultOrderSucces(configuration: configuration), + ); + }, + ), + GoRoute( + name: "orderFailed", + path: FlutterShoppingRoutes.orderFailed, + pageBuilder: (BuildContext context, GoRouterState state) { + if (configuration.orderFailedBuilder != null) { + return buildScreenWithFadeTransition( + context: context, + state: state, + child: configuration.orderFailedBuilder!(context), + ); + } + + return buildScreenWithFadeTransition( + context: context, + state: state, + child: DefaultOrderFailed( + configuration: configuration, + ), + ); + }, + ), + ]; diff --git a/lib/src/user_stores/flutter_shopping_userstory_navigation.dart b/lib/src/user_stores/flutter_shopping_userstory_navigation.dart new file mode 100644 index 0000000..121ef64 --- /dev/null +++ b/lib/src/user_stores/flutter_shopping_userstory_navigation.dart @@ -0,0 +1,43 @@ +import "package:flutter/material.dart"; +import "package:flutter_order_details/flutter_order_details.dart"; +import "package:flutter_shopping/main.dart"; +import "package:go_router/go_router.dart"; + +/// TODO +Future 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(FlutterShoppingRoutes.orderSuccess); + } else { + go(FlutterShoppingRoutes.orderFailed); + } +} + +/// TODO +void onCompleteShoppingCart( + BuildContext context, +) { + context.go(FlutterShoppingRoutes.orderDetails); +} + +/// TODO +void onCompleteProductPage( + BuildContext context, +) { + context.go(FlutterShoppingRoutes.shoppingCart); +} diff --git a/lib/src/widgets/default_order_failed_widget.dart b/lib/src/widgets/default_order_failed_widget.dart new file mode 100644 index 0000000..a3bce79 --- /dev/null +++ b/lib/src/widgets/default_order_failed_widget.dart @@ -0,0 +1,65 @@ +import "package:flutter/material.dart"; +import "package:flutter_shopping/main.dart"; + +/// TODO +class DefaultOrderFailed extends StatelessWidget { + /// TODO + const DefaultOrderFailed({ + required this.configuration, + super.key, + }); + + /// TODO + final FlutterShoppingConfiguration configuration; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + + var finishOrderButton = FilledButton( + onPressed: () => configuration.onCompleteUserStory(context), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32.0, + vertical: 8.0, + ), + child: Text("Go back".toUpperCase()), + ), + ); + + var content = Column( + children: [ + const Spacer(), + const Icon( + Icons.error, + size: 100, + color: Colors.red, + ), + const SizedBox(height: 16), + Text( + "Uh oh.", + style: theme.textTheme.titleLarge, + ), + const SizedBox(height: 32), + Text( + "It seems that something went wrong.", + style: theme.textTheme.bodyMedium, + ), + Text( + "Please try again later.", + style: theme.textTheme.bodyMedium, + ), + const Spacer(), + finishOrderButton, + ], + ); + + return Scaffold( + body: SafeArea( + child: Center( + child: content, + ), + ), + ); + } +} diff --git a/lib/src/widgets/default_order_succes_widget.dart b/lib/src/widgets/default_order_succes_widget.dart new file mode 100644 index 0000000..645560f --- /dev/null +++ b/lib/src/widgets/default_order_succes_widget.dart @@ -0,0 +1,66 @@ +import "package:flutter/material.dart"; +import "package:flutter_shopping/main.dart"; + +/// TODO +class DefaultOrderSucces extends StatelessWidget { + /// TODO + const DefaultOrderSucces({ + required this.configuration, + super.key, + }); + + /// TODO + final FlutterShoppingConfiguration configuration; + + @override + Widget build(BuildContext context) { + var theme = Theme.of(context); + + var finishOrderButton = FilledButton( + onPressed: () => configuration.onCompleteUserStory(context), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32.0, + vertical: 8.0, + ), + child: Text("Finish Order".toUpperCase()), + ), + ); + + var content = Column( + children: [ + const Spacer(), + Text("#123456", style: theme.textTheme.titleLarge), + const SizedBox(height: 16), + Text( + "Order Succesfully Placed!", + style: theme.textTheme.titleLarge, + ), + Text( + "Thank you for your order!", + style: theme.textTheme.bodyMedium, + ), + const SizedBox(height: 16), + Text( + "Your order will be delivered soon.", + style: theme.textTheme.bodyMedium, + ), + const SizedBox(height: 16), + Text( + "Do you want to order again?", + style: theme.textTheme.bodyMedium, + ), + const Spacer(), + finishOrderButton, + ], + ); + + return Scaffold( + body: SafeArea( + child: Center( + child: content, + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7ec6b07..2a3083d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,14 +9,15 @@ environment: dependencies: flutter: sdk: flutter + go_router: any flutter_product_page: git: url: https://github.com/Iconica-Development/flutter_product_page - ref: 1.0.0 + ref: fix/missing-features flutter_shopping_cart: git: url: https://github.com/Iconica-Development/flutter_shopping_cart - ref: 1.0.0 + ref: fix/missing-features flutter_order_details: git: url: https://github.com/Iconica-Development/flutter_order_details