mirror of
https://github.com/Iconica-Development/flutter_shopping.git
synced 2025-05-19 08:53:46 +02:00
feat: intial user story code
This commit is contained in:
parent
3fc55190cd
commit
a0fe3b6f67
25 changed files with 965 additions and 2 deletions
53
example/.gitignore
vendored
Normal file
53
example/.gitignore
vendored
Normal file
|
@ -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/
|
16
example/README.md
Normal file
16
example/README.md
Normal file
|
@ -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.
|
7
example/analysis_options.yaml
Normal file
7
example/analysis_options.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
include: package:flutter_iconica_analysis/analysis_options.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
8
example/environment_config.yaml
Normal file
8
example/environment_config.yaml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
environment_config:
|
||||||
|
dotenv_path: dotenv
|
||||||
|
path: env_config.dart
|
||||||
|
fields:
|
||||||
|
STRIPE_PUBLISHABLE_KEY:
|
||||||
|
dotenv: true
|
||||||
|
config_field: false
|
||||||
|
default: ""
|
22
example/lib/main.dart
Normal file
22
example/lib/main.dart
Normal file
|
@ -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),
|
||||||
|
);
|
||||||
|
}
|
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/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<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/main.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",
|
||||||
|
),
|
||||||
|
];
|
22
example/lib/src/ui/homepage.dart
Normal file
22
example/lib/src/ui/homepage.dart
Normal file
|
@ -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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
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,
|
||||||
|
);
|
31
example/lib/src/utils/theme.dart
Normal file
31
example/lib/src/utils/theme.dart
Normal file
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
56
example/pubspec.yaml
Normal file
56
example/pubspec.yaml
Normal file
|
@ -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
|
4
lib/main.dart
Normal file
4
lib/main.dart
Normal file
|
@ -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";
|
70
lib/src/config/default_order_detail_configuration.dart
Normal file
70
lib/src/config/default_order_detail_configuration.dart
Normal file
|
@ -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<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: ""
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
OrderDetailStep(
|
||||||
|
formKey: GlobalKey<FormState>(),
|
||||||
|
stepName: "Adress Information",
|
||||||
|
fields: [
|
||||||
|
OrderAdresInput(
|
||||||
|
title: "Your adress",
|
||||||
|
outputKey: "adres",
|
||||||
|
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 {
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
46
lib/src/config/flutter_shopping_configuration.dart
Normal file
46
lib/src/config/flutter_shopping_configuration.dart
Normal file
|
@ -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<bool> Function(BuildContext context, OrderResult result)?
|
||||||
|
onCompleteOrderDetails;
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
final Widget Function(BuildContext context)? orderSuccessBuilder;
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
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";
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
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),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
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 @@
|
||||||
|
/// 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";
|
||||||
|
}
|
|
@ -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<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.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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
];
|
|
@ -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<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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
void onCompleteShoppingCart(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
|
context.go(FlutterShoppingRoutes.orderDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO
|
||||||
|
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/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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
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/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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,14 +9,15 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
go_router: any
|
||||||
flutter_product_page:
|
flutter_product_page:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_product_page
|
url: https://github.com/Iconica-Development/flutter_product_page
|
||||||
ref: 1.0.0
|
ref: fix/missing-features
|
||||||
flutter_shopping_cart:
|
flutter_shopping_cart:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_shopping_cart
|
url: https://github.com/Iconica-Development/flutter_shopping_cart
|
||||||
ref: 1.0.0
|
ref: fix/missing-features
|
||||||
flutter_order_details:
|
flutter_order_details:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_order_details
|
url: https://github.com/Iconica-Development/flutter_order_details
|
||||||
|
|
Loading…
Reference in a new issue