mirror of
https://github.com/Iconica-Development/flutter_shopping.git
synced 2025-05-18 16:33:45 +02:00
Feat/v1.0.0 (#1)
* feat: intial user story code * fix: remove unused import * fix: remove unused asset * feat: readme * feat: dart documentation * fix: component versions * fix: remove unused environment config * fix: feedback
This commit is contained in:
parent
bf3123aef4
commit
33184a3b01
23 changed files with 1003 additions and 14 deletions
157
README.md
157
README.md
|
@ -1,16 +1,161 @@
|
|||
# flutter_shopping
|
||||
|
||||
This component contains TODO...
|
||||
The flutter_shopping user-story allows you to create an user shopping flow within minutes of work. This user-story contains the ability to show products, shopping cart, gathering user information and order succes and failed screens.
|
||||
|
||||
## Features
|
||||
## Setup
|
||||
|
||||
* TODO...
|
||||
(1) Set up your `MyShop` model by extending from the `ProductPageShop` class. The most basic version looks like this:
|
||||
|
||||
```dart
|
||||
class MyShop extends ProductPageShop {
|
||||
const MyShop({
|
||||
required super.id,
|
||||
required super.name,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
(2) Set up your `MyProduct` model by extending from `ShoppingCartProduct` and extending from the mixin `ProductPageProduct`, like this:
|
||||
|
||||
```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;
|
||||
}
|
||||
```
|
||||
|
||||
(3) Finally in your `routes.dart` import all the routes from the user-story:
|
||||
|
||||
```dart
|
||||
...getShoppingStoryRoutes(
|
||||
configuration: ...
|
||||
),
|
||||
```
|
||||
|
||||
(4) Create a new instantiation of the ProductService class:
|
||||
|
||||
```dart
|
||||
final ProductService<MyProduct> productService = ProductService([]);
|
||||
```
|
||||
|
||||
(5) Set up the `FlutterShoppingConfiguration`:
|
||||
|
||||
```dart
|
||||
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<ProductPageContent>.value(
|
||||
getShopContent(shopId),
|
||||
),
|
||||
|
||||
// (REQUIRED): Function to navigate to the shopping cart
|
||||
onNavigateToShoppingCart: () => onCompleteProductPage(context),
|
||||
|
||||
// (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: ...
|
||||
),
|
||||
),
|
||||
|
||||
// (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) => ...
|
||||
|
||||
// (OPTIONAL/REQUIRED) on confirm order callback:
|
||||
// Either use this callback or the placeOrderButtonBuilder.
|
||||
onConfirmOrder: (products) => onCompleteShoppingCart(context),
|
||||
|
||||
// (RECOMMENDED) localizations:
|
||||
localizations: const ShoppingCartLocalizations(),
|
||||
|
||||
// (OPTIONAL) custom appbar:
|
||||
appBar: ...
|
||||
),
|
||||
),
|
||||
|
||||
// (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 {
|
||||
...
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
For more information about the component specific items please take a look at their repositories:
|
||||
|
||||
- [flutter_product_page](https://github.com/Iconica-Development/flutter_product_page/)
|
||||
- [flutter_shopping_cart](https://github.com/Iconica-Development/flutter_shopping_cart)
|
||||
- [flutter_order_details](https://github.com/Iconica-Development/flutter_order_details)
|
||||
|
||||
## Usage
|
||||
|
||||
First, TODO...
|
||||
|
||||
For a more detailed example you can see the [example](https://github.com/Iconica-Development/flutter_shopping/tree/main/example).
|
||||
For a detailed example you can see the [example](https://github.com/Iconica-Development/flutter_shopping/tree/main/example).
|
||||
|
||||
Or, you could run the example yourself:
|
||||
```
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
|
|
|
@ -1 +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),
|
||||
);
|
||||
}
|
||||
|
|
165
example/lib/src/configuration/configuration.dart
Normal file
165
example/lib/src/configuration/configuration.dart
Normal file
|
@ -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/flutter_shopping.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<MyProduct> 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<ProductPageContent>.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;
|
||||
},
|
||||
);
|
26
example/lib/src/models/my_product.dart
Normal file
26
example/lib/src/models/my_product.dart
Normal file
|
@ -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;
|
||||
}
|
8
example/lib/src/models/my_shop.dart
Normal file
8
example/lib/src/models/my_shop.dart
Normal file
|
@ -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,
|
||||
});
|
||||
}
|
31
example/lib/src/routes.dart
Normal file
31
example/lib/src/routes.dart
Normal file
|
@ -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/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(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
7
example/lib/src/services/order_service.dart
Normal file
7
example/lib/src/services/order_service.dart
Normal file
|
@ -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<MyProduct> products, OrderResult result) {
|
||||
return;
|
||||
}
|
47
example/lib/src/services/shop_service.dart
Normal file
47
example/lib/src/services/shop_service.dart
Normal file
|
@ -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<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<MyProduct> getProducts(String shopId) => <MyProduct>[
|
||||
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",
|
||||
),
|
||||
];
|
20
example/lib/src/ui/homepage.dart
Normal file
20
example/lib/src/ui/homepage.dart
Normal file
|
@ -0,0 +1,20 @@
|
|||
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(FlutterShoppingRoutes.shop),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
26
example/lib/src/utils/go_router.dart
Normal file
26
example/lib/src/utils/go_router.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
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,
|
||||
);
|
32
example/lib/src/utils/theme.dart
Normal file
32
example/lib/src/utils/theme.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
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,
|
||||
),
|
||||
),
|
||||
);
|
|
@ -1,6 +1,6 @@
|
|||
name: example
|
||||
description: "Demonstrates how to use the flutter_shopping user story."
|
||||
publish_to: 'none'
|
||||
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:
|
||||
|
@ -9,9 +9,30 @@ environment:
|
|||
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: 1.1.0
|
||||
flutter_shopping_cart:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_shopping_cart
|
||||
ref: 1.1.0
|
||||
flutter_order_details:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_order_details
|
||||
ref: 1.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
@ -31,4 +52,4 @@ flutter:
|
|||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# style: italic
|
||||
|
|
7
lib/flutter_shopping.dart
Normal file
7
lib/flutter_shopping.dart
Normal file
|
@ -0,0 +1,7 @@
|
|||
/// Flutter Shopping
|
||||
library flutter_shopping;
|
||||
|
||||
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";
|
69
lib/src/config/default_order_detail_configuration.dart
Normal file
69
lib/src/config/default_order_detail_configuration.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_order_details/flutter_order_details.dart";
|
||||
import "package:flutter_shopping/flutter_shopping.dart";
|
||||
import "package:go_router/go_router.dart";
|
||||
|
||||
/// Default order detail configuration for the app.
|
||||
/// This configuration is used to create the order detail page.
|
||||
OrderDetailConfiguration getDefaultOrderDetailConfiguration(
|
||||
BuildContext context,
|
||||
FlutterShoppingConfiguration configuration,
|
||||
) =>
|
||||
OrderDetailConfiguration(
|
||||
steps: [
|
||||
OrderDetailStep(
|
||||
formKey: GlobalKey<FormState>(),
|
||||
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: "your_email@mail.com",
|
||||
),
|
||||
],
|
||||
),
|
||||
OrderDetailStep(
|
||||
formKey: GlobalKey<FormState>(),
|
||||
stepName: "Address Information",
|
||||
fields: [
|
||||
OrderAddressInput(
|
||||
title: "Your address",
|
||||
outputKey: "address",
|
||||
textController: TextEditingController(),
|
||||
),
|
||||
],
|
||||
),
|
||||
OrderDetailStep(
|
||||
formKey: GlobalKey<FormState>(),
|
||||
stepName: "Payment Information",
|
||||
fields: [
|
||||
OrderChoiceInput(
|
||||
title: "Payment option",
|
||||
outputKey: "payment_option",
|
||||
items: ["Pay now", "Pay later"],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
onCompleted: (OrderResult result) async =>
|
||||
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),
|
||||
),
|
||||
),
|
||||
);
|
39
lib/src/config/flutter_shopping_configuration.dart
Normal file
39
lib/src/config/flutter_shopping_configuration.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
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) 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;
|
||||
}
|
28
lib/src/go_router.dart
Normal file
28
lib/src/go_router.dart
Normal file
|
@ -0,0 +1,28 @@
|
|||
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,
|
||||
);
|
17
lib/src/routes.dart
Normal file
17
lib/src/routes.dart
Normal file
|
@ -0,0 +1,17 @@
|
|||
/// All the routes used in the user-story.
|
||||
mixin FlutterShoppingRoutes {
|
||||
/// 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";
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_order_details/flutter_order_details.dart";
|
||||
import "package:flutter_shopping/flutter_shopping.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";
|
||||
|
||||
/// All the routes for the shopping story.
|
||||
List<GoRoute> getShoppingStoryRoutes({
|
||||
required FlutterShoppingConfiguration configuration,
|
||||
}) =>
|
||||
<GoRoute>[
|
||||
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.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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
|
@ -0,0 +1,53 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_order_details/flutter_order_details.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(FlutterShoppingRoutes.orderSuccess);
|
||||
} else {
|
||||
go(FlutterShoppingRoutes.orderFailed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Default on complete shopping cart function.
|
||||
///
|
||||
/// You can create your own implementation if you decide to use a different
|
||||
/// approach.
|
||||
void onCompleteShoppingCart(
|
||||
BuildContext context,
|
||||
) {
|
||||
context.go(FlutterShoppingRoutes.orderDetails);
|
||||
}
|
||||
|
||||
/// Default on complete product page function.
|
||||
///
|
||||
/// You can create your own implementation if you decide to use a different
|
||||
/// approach.
|
||||
void onCompleteProductPage(
|
||||
BuildContext context,
|
||||
) {
|
||||
context.go(FlutterShoppingRoutes.shoppingCart);
|
||||
}
|
65
lib/src/widgets/default_order_failed_widget.dart
Normal file
65
lib/src/widgets/default_order_failed_widget.dart
Normal file
|
@ -0,0 +1,65 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_shopping/flutter_shopping.dart";
|
||||
|
||||
/// Default order failed widget.
|
||||
class DefaultOrderFailed extends StatelessWidget {
|
||||
/// Constructor for the DefaultOrderFailed.
|
||||
const DefaultOrderFailed({
|
||||
required this.configuration,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Configuration for the user-story.
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
66
lib/src/widgets/default_order_succes_widget.dart
Normal file
66
lib/src/widgets/default_order_succes_widget.dart
Normal file
|
@ -0,0 +1,66 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_shopping/flutter_shopping.dart";
|
||||
|
||||
/// Default order success widget.
|
||||
class DefaultOrderSucces extends StatelessWidget {
|
||||
/// Constructor for the DefaultOrderSucces.
|
||||
const DefaultOrderSucces({
|
||||
required this.configuration,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Configuration for the user-story.
|
||||
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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,18 +9,19 @@ 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: 1.1.0
|
||||
flutter_shopping_cart:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_shopping_cart
|
||||
ref: 1.0.0
|
||||
ref: 1.1.0
|
||||
flutter_order_details:
|
||||
git:
|
||||
url: https://github.com/Iconica-Development/flutter_order_details
|
||||
ref: feat/v1.0.0
|
||||
ref: 1.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in a new issue