feat: add interface to flutter_order_details

This commit is contained in:
mike doornenbal 2024-07-03 15:40:52 +02:00
parent 1b78b2c674
commit 2426416c42
14 changed files with 139 additions and 145 deletions

View file

@ -2,7 +2,6 @@
library flutter_order_details;
export "src/configuration/order_detail_configuration.dart";
export "src/configuration/order_detail_localization.dart";
export "src/configuration/order_detail_title_style.dart";
export "src/models/order_result.dart";
export "src/widgets/order_detail_screen.dart";
export "src/configuration/order_detail_translations.dart";
export "src/order_detail_screen.dart";
export "src/widgets/order_succes.dart";

View file

@ -4,54 +4,82 @@ import "package:animated_toggle/animated_toggle.dart";
import "package:flutter/material.dart";
import "package:flutter_form_wizard/flutter_form.dart";
import "package:flutter_order_details/flutter_order_details.dart";
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
/// Configuration for the order detail screen.
class OrderDetailConfiguration {
/// Constructor for the order detail configuration.
OrderDetailConfiguration({
required this.onCompleted,
required this.shoppingService,
required this.onNextStep,
required this.onStepsCompleted,
required this.onCompleteOrderDetails,
this.pages = _defaultPages,
this.localization = const OrderDetailLocalization(),
this.translations = const OrderDetailTranslations(),
this.appBar = _defaultAppBar,
this.nextbuttonBuilder = _defaultNextButtonBuilder,
this.orderSuccessBuilder = _defaultOrderSuccess,
});
/// The shopping service that is used
final ShoppingService shoppingService;
/// The different steps that the user has to go through to complete the order.
/// Each step contains a list of fields that the user has to fill in.
final List<FlutterFormPage> Function(BuildContext context) pages;
/// Callback function that is called when the user has completed the order.
/// The result of the order is passed as an argument to the function.
final Function(dynamic value) onCompleted;
final Function(
String shopId,
List<Product> products,
Map<int, Map<String, dynamic>> value,
OrderDetailConfiguration configuration,
) onStepsCompleted;
/// Callback function that is called when the user has completed a step.
final Function(int currentStep, Map<String, dynamic> data) onNextStep;
/// Localization for the order detail screen.
final OrderDetailLocalization localization;
final OrderDetailTranslations translations;
/// Optional app bar that you can pass to the order detail screen.
final AppBar Function(
BuildContext context,
OrderDetailLocalization localizations,
String title,
) appBar;
/// Optional next button builder that you can pass to the order detail screen.
final Widget Function(
int a,
int currentStep,
// ignore: avoid_positional_boolean_parameters
bool b,
bool checkingPages,
BuildContext context,
OrderDetailConfiguration configuration,
FlutterFormController controller,
) nextbuttonBuilder;
/// Optional builder for the order success screen.
final Widget Function(
BuildContext context,
OrderDetailConfiguration,
Map<int, Map<String, dynamic>> orderDetails,
) orderSuccessBuilder;
/// This function is called after the order has been completed and
/// the success screen has been shown.
final Function(BuildContext context, OrderDetailConfiguration configuration)
onCompleteOrderDetails;
}
AppBar _defaultAppBar(
BuildContext context,
OrderDetailLocalization localizations,
String title,
) {
var theme = Theme.of(context);
return AppBar(
title: Text(
localizations.orderDetailsTitle,
title,
style: theme.textTheme.headlineLarge,
),
);
@ -535,3 +563,13 @@ List<FlutterFormPage> _defaultPages(BuildContext context) {
),
];
}
Widget _defaultOrderSuccess(
BuildContext context,
OrderDetailConfiguration configuration,
Map<int, Map<String, dynamic>> orderDetails,
) =>
DefaultOrderSucces(
configuration: configuration,
orderDetails: orderDetails,
);

View file

@ -1,14 +0,0 @@
/// An enum to define the style of the title in the order detail.
enum OrderDetailTitleStyle {
/// The title displayed as a textlabel above the field.
text,
/// The title displayed as a label inside the field.
/// NOTE: Not all fields support this. Such as, but not limited to:
/// - Dropdown
/// - Time Picker
label,
/// Does not display any form of title.
none,
}

View file

@ -1,7 +1,7 @@
/// Localizations for the order detail page.
class OrderDetailLocalization {
class OrderDetailTranslations {
/// Constructor for the order detail localization.
const OrderDetailLocalization({
const OrderDetailTranslations({
this.nextButton = "Order",
this.completeButton = "Complete",
this.orderDetailsTitle = "Information",

View file

@ -1,14 +0,0 @@
/// OrderResult model.
/// When an user completes the field and presses the complete button,
/// the `onComplete` method returns an instance of this class that contains
/// all the developer-specified `outputKey`s and the value that was provided
/// by the user.
class OrderResult {
/// Constructor of the order result class.
OrderResult({
required this.order,
});
/// Map of `outputKey`s and their respected values.
final Map<String, dynamic> order;
}

View file

@ -22,23 +22,33 @@ class _OrderDetailScreenState extends State<OrderDetailScreen> {
Widget build(BuildContext context) {
var controller = FlutterFormController();
return Scaffold(
appBar: widget.configuration.appBar
.call(context, widget.configuration.localization),
appBar: widget.configuration.appBar.call(
context,
widget.configuration.translations.orderDetailsTitle,
),
body: FlutterForm(
formController: controller,
options: FlutterFormOptions(
nextButton: (a, b) => widget.configuration.nextbuttonBuilder(
a,
b,
nextButton: (pageNumber, checkingPages) =>
widget.configuration.nextbuttonBuilder(
pageNumber,
checkingPages,
context,
widget.configuration,
controller,
),
pages: widget.configuration.pages.call(context),
onFinished: (data) {
widget.configuration.onCompleted.call(data);
onFinished: (data) async {
widget.configuration.onStepsCompleted.call(
widget.configuration.shoppingService.shopService.selectedShop!.id,
widget.configuration.shoppingService.shoppingCartService.products,
data,
widget.configuration,
);
},
onNext: (step, data) {
widget.configuration.onNextStep.call(step, data);
},
onNext: (step, data) {},
),
),
);

View file

@ -1,21 +1,31 @@
import "package:flutter/material.dart";
import "package:flutter_shopping/flutter_shopping.dart";
import "package:flutter_order_details/flutter_order_details.dart";
import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
/// Default order success widget.
class DefaultOrderSucces extends StatelessWidget {
/// Constructor for the DefaultOrderSucces.
const DefaultOrderSucces({
required this.configuration,
required this.orderDetails,
super.key,
});
/// Configuration for the user-story.
final FlutterShoppingConfiguration configuration;
/// Configuration for the user-stor
final OrderDetailConfiguration configuration;
/// Order details.
final Map<int, Map<String, dynamic>> orderDetails;
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var discountedProducts = configuration
.shoppingService.productService.products
.where((product) => product.hasDiscount)
.toList();
return Scaffold(
appBar: AppBar(
title: Text(
@ -42,16 +52,19 @@ class DefaultOrderSucces extends StatelessWidget {
height: 4,
),
Text(
"Thank you Peter for your order!",
"Thank you ${orderDetails[0]!['name']} for your order!",
style: theme.textTheme.bodyMedium,
),
const SizedBox(
height: 16,
),
Text(
"The order was placed at Bakkerij de Goudkorst."
"The order was placed"
// ignore: lines_longer_than_80_chars
" at ${configuration.shoppingService.shopService.selectedShop?.name}."
" You can pick this"
" up on Monday, February 7 at 1:00 PM.",
" up ${orderDetails[1]!['date']} at"
" ${orderDetails[1]!['multipleChoice']}.",
style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center,
),
@ -84,9 +97,19 @@ class DefaultOrderSucces extends StatelessWidget {
scrollDirection: Axis.horizontal,
children: [
const SizedBox(width: 32),
_discount(context),
const SizedBox(width: 8),
_discount(context),
// _discount(context),
// const SizedBox(width: 8),
// _discount(context),
for (var product in discountedProducts) ...[
_discount(
context,
product,
configuration.shoppingService.shopService.selectedShop!,
),
const SizedBox(
width: 8,
),
],
const SizedBox(width: 32),
],
),
@ -98,7 +121,8 @@ class DefaultOrderSucces extends StatelessWidget {
width: double.infinity,
child: FilledButton(
onPressed: () async {
configuration.onCompleteUserStory.call(context);
configuration.onCompleteOrderDetails
.call(context, configuration);
},
style: theme.filledButtonTheme.style?.copyWith(
backgroundColor: WidgetStateProperty.all(
@ -125,7 +149,7 @@ class DefaultOrderSucces extends StatelessWidget {
}
}
Widget _discount(BuildContext context) {
Widget _discount(BuildContext context, Product product, Shop shop) {
var theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
@ -139,14 +163,11 @@ Widget _discount(BuildContext context) {
child: Stack(
children: [
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
borderRadius: BorderRadius.circular(
10,
),
child: Image.network(
"https://picsum.photos/150",
product.imageUrl,
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
@ -166,7 +187,7 @@ Widget _discount(BuildContext context) {
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
"Butcher Puurvlees",
shop.name,
style: theme.textTheme.headlineSmall?.copyWith(
color: Colors.white,
),
@ -189,7 +210,7 @@ Widget _discount(BuildContext context) {
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
"Chicken legs, now for 4,99",
"${product.name}, now for ${product.price.toStringAsFixed(2)}",
style: theme.textTheme.bodyMedium,
),
),

View file

@ -1,10 +1,10 @@
name: flutter_order_details
description: "A Flutter module for order details."
version: 2.0.0
publish_to: 'none'
publish_to: "none"
environment:
sdk: '>=3.3.0 <4.0.0'
sdk: ">=3.3.0 <4.0.0"
dependencies:
flutter:
@ -17,6 +17,16 @@ dependencies:
git:
url: https://github.com/Iconica-Development/flutter_form_wizard
ref: 6.5.0
flutter_shopping_interface:
git:
url: https://github.com/Iconica-Development/flutter_shopping
path: packages/flutter_shopping_interface
ref: 2.0.0
collection: ^1.18.0
dependency_overrides:
flutter_shopping_interface:
path: ../flutter_shopping_interface
dev_dependencies:
flutter_test:
@ -27,4 +37,3 @@ dev_dependencies:
ref: 7.0.0
flutter:

View file

@ -1,65 +0,0 @@
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,
),
),
);
}
}

View file

@ -5,8 +5,8 @@ import "package:flutter_shopping_interface/src/model/product.dart";
abstract class OrderService {
/// Create an order
Future<void> createOrder(
int shopId,
String shopId,
List<Product> products,
Map<String, dynamic> clientInformation,
Map<int, Map<String, dynamic>> clientInformation,
);
}

View file

@ -11,4 +11,7 @@ abstract class ProductService with ChangeNotifier {
/// Retrieve a list of categories
List<String> getCategories();
/// Get current Products
List<Product> get products;
}

View file

@ -5,11 +5,11 @@ import "package:flutter_shopping_interface/flutter_shopping_interface.dart";
class LocalOrderService with ChangeNotifier implements OrderService {
@override
Future<void> createOrder(
int shopId,
String shopId,
List<Product> products,
Map<String, dynamic> clientInformation,
) {
// No use case for this method yet
throw UnimplementedError();
Map<int, Map<String, dynamic>> clientInformation,
) async {
// Create the order
notifyListeners();
}
}

View file

@ -62,4 +62,7 @@ class LocalProductService with ChangeNotifier implements ProductService {
];
return Future.value(_products);
}
@override
List<Product> get products => _products;
}

View file

@ -1 +1,5 @@
export "local_order_service.dart";
export "local_product_service.dart";
export "local_shop_service.dart";
export "local_shopping_cart_service.dart";
export "local_shopping_service.dart";