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; library flutter_order_details;
export "src/configuration/order_detail_configuration.dart"; export "src/configuration/order_detail_configuration.dart";
export "src/configuration/order_detail_localization.dart"; export "src/configuration/order_detail_translations.dart";
export "src/configuration/order_detail_title_style.dart"; export "src/order_detail_screen.dart";
export "src/models/order_result.dart"; export "src/widgets/order_succes.dart";
export "src/widgets/order_detail_screen.dart";

View file

@ -4,54 +4,82 @@ import "package:animated_toggle/animated_toggle.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter_form_wizard/flutter_form.dart"; import "package:flutter_form_wizard/flutter_form.dart";
import "package:flutter_order_details/flutter_order_details.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. /// Configuration for the order detail screen.
class OrderDetailConfiguration { class OrderDetailConfiguration {
/// Constructor for the order detail configuration. /// Constructor for the order detail configuration.
OrderDetailConfiguration({ OrderDetailConfiguration({
required this.onCompleted, required this.shoppingService,
required this.onNextStep,
required this.onStepsCompleted,
required this.onCompleteOrderDetails,
this.pages = _defaultPages, this.pages = _defaultPages,
this.localization = const OrderDetailLocalization(), this.translations = const OrderDetailTranslations(),
this.appBar = _defaultAppBar, this.appBar = _defaultAppBar,
this.nextbuttonBuilder = _defaultNextButtonBuilder, 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. /// 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. /// Each step contains a list of fields that the user has to fill in.
final List<FlutterFormPage> Function(BuildContext context) pages; final List<FlutterFormPage> Function(BuildContext context) pages;
/// Callback function that is called when the user has completed the order. /// 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. /// 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. /// Localization for the order detail screen.
final OrderDetailLocalization localization; final OrderDetailTranslations translations;
/// Optional app bar that you can pass to the order detail screen. /// Optional app bar that you can pass to the order detail screen.
final AppBar Function( final AppBar Function(
BuildContext context, BuildContext context,
OrderDetailLocalization localizations, String title,
) appBar; ) appBar;
/// Optional next button builder that you can pass to the order detail screen. /// Optional next button builder that you can pass to the order detail screen.
final Widget Function( final Widget Function(
int a, int currentStep,
// ignore: avoid_positional_boolean_parameters // ignore: avoid_positional_boolean_parameters
bool b, bool checkingPages,
BuildContext context, BuildContext context,
OrderDetailConfiguration configuration, OrderDetailConfiguration configuration,
FlutterFormController controller, FlutterFormController controller,
) nextbuttonBuilder; ) 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( AppBar _defaultAppBar(
BuildContext context, BuildContext context,
OrderDetailLocalization localizations, String title,
) { ) {
var theme = Theme.of(context); var theme = Theme.of(context);
return AppBar( return AppBar(
title: Text( title: Text(
localizations.orderDetailsTitle, title,
style: theme.textTheme.headlineLarge, 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. /// Localizations for the order detail page.
class OrderDetailLocalization { class OrderDetailTranslations {
/// Constructor for the order detail localization. /// Constructor for the order detail localization.
const OrderDetailLocalization({ const OrderDetailTranslations({
this.nextButton = "Order", this.nextButton = "Order",
this.completeButton = "Complete", this.completeButton = "Complete",
this.orderDetailsTitle = "Information", 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) { Widget build(BuildContext context) {
var controller = FlutterFormController(); var controller = FlutterFormController();
return Scaffold( return Scaffold(
appBar: widget.configuration.appBar appBar: widget.configuration.appBar.call(
.call(context, widget.configuration.localization), context,
widget.configuration.translations.orderDetailsTitle,
),
body: FlutterForm( body: FlutterForm(
formController: controller, formController: controller,
options: FlutterFormOptions( options: FlutterFormOptions(
nextButton: (a, b) => widget.configuration.nextbuttonBuilder( nextButton: (pageNumber, checkingPages) =>
a, widget.configuration.nextbuttonBuilder(
b, pageNumber,
checkingPages,
context, context,
widget.configuration, widget.configuration,
controller, controller,
), ),
pages: widget.configuration.pages.call(context), pages: widget.configuration.pages.call(context),
onFinished: (data) { onFinished: (data) async {
widget.configuration.onCompleted.call(data); 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/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. /// Default order success widget.
class DefaultOrderSucces extends StatelessWidget { class DefaultOrderSucces extends StatelessWidget {
/// Constructor for the DefaultOrderSucces. /// Constructor for the DefaultOrderSucces.
const DefaultOrderSucces({ const DefaultOrderSucces({
required this.configuration, required this.configuration,
required this.orderDetails,
super.key, super.key,
}); });
/// Configuration for the user-story. /// Configuration for the user-stor
final FlutterShoppingConfiguration configuration; final OrderDetailConfiguration configuration;
/// Order details.
final Map<int, Map<String, dynamic>> orderDetails;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
var discountedProducts = configuration
.shoppingService.productService.products
.where((product) => product.hasDiscount)
.toList();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
@ -42,16 +52,19 @@ class DefaultOrderSucces extends StatelessWidget {
height: 4, height: 4,
), ),
Text( Text(
"Thank you Peter for your order!", "Thank you ${orderDetails[0]!['name']} for your order!",
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
), ),
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
Text( 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" " 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, style: theme.textTheme.bodyMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
@ -84,9 +97,19 @@ class DefaultOrderSucces extends StatelessWidget {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: [ children: [
const SizedBox(width: 32), const SizedBox(width: 32),
_discount(context), // _discount(context),
const SizedBox(width: 8), // const SizedBox(width: 8),
_discount(context), // _discount(context),
for (var product in discountedProducts) ...[
_discount(
context,
product,
configuration.shoppingService.shopService.selectedShop!,
),
const SizedBox(
width: 8,
),
],
const SizedBox(width: 32), const SizedBox(width: 32),
], ],
), ),
@ -98,7 +121,8 @@ class DefaultOrderSucces extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: FilledButton( child: FilledButton(
onPressed: () async { onPressed: () async {
configuration.onCompleteUserStory.call(context); configuration.onCompleteOrderDetails
.call(context, configuration);
}, },
style: theme.filledButtonTheme.style?.copyWith( style: theme.filledButtonTheme.style?.copyWith(
backgroundColor: WidgetStateProperty.all( 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); var theme = Theme.of(context);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -139,14 +163,11 @@ Widget _discount(BuildContext context) {
child: Stack( child: Stack(
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.only( borderRadius: BorderRadius.circular(
topLeft: Radius.circular(10), 10,
topRight: Radius.circular(10),
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
), ),
child: Image.network( child: Image.network(
"https://picsum.photos/150", product.imageUrl,
width: double.infinity, width: double.infinity,
height: double.infinity, height: double.infinity,
fit: BoxFit.cover, fit: BoxFit.cover,
@ -166,7 +187,7 @@ Widget _discount(BuildContext context) {
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
"Butcher Puurvlees", shop.name,
style: theme.textTheme.headlineSmall?.copyWith( style: theme.textTheme.headlineSmall?.copyWith(
color: Colors.white, color: Colors.white,
), ),
@ -189,7 +210,7 @@ Widget _discount(BuildContext context) {
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 8.0), padding: const EdgeInsets.only(left: 8.0),
child: Text( child: Text(
"Chicken legs, now for 4,99", "${product.name}, now for ${product.price.toStringAsFixed(2)}",
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,
), ),
), ),

View file

@ -1,10 +1,10 @@
name: flutter_order_details name: flutter_order_details
description: "A Flutter module for order details." description: "A Flutter module for order details."
version: 2.0.0 version: 2.0.0
publish_to: 'none' publish_to: "none"
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: ">=3.3.0 <4.0.0"
dependencies: dependencies:
flutter: flutter:
@ -17,6 +17,16 @@ dependencies:
git: git:
url: https://github.com/Iconica-Development/flutter_form_wizard url: https://github.com/Iconica-Development/flutter_form_wizard
ref: 6.5.0 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: dev_dependencies:
flutter_test: flutter_test:
@ -27,4 +37,3 @@ dev_dependencies:
ref: 7.0.0 ref: 7.0.0
flutter: 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 { abstract class OrderService {
/// Create an order /// Create an order
Future<void> createOrder( Future<void> createOrder(
int shopId, String shopId,
List<Product> products, 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 /// Retrieve a list of categories
List<String> getCategories(); 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 { class LocalOrderService with ChangeNotifier implements OrderService {
@override @override
Future<void> createOrder( Future<void> createOrder(
int shopId, String shopId,
List<Product> products, List<Product> products,
Map<String, dynamic> clientInformation, Map<int, Map<String, dynamic>> clientInformation,
) { ) async {
// No use case for this method yet // Create the order
throw UnimplementedError(); notifyListeners();
} }
} }

View file

@ -62,4 +62,7 @@ class LocalProductService with ChangeNotifier implements ProductService {
]; ];
return Future.value(_products); 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_shop_service.dart";
export "local_shopping_cart_service.dart";
export "local_shopping_service.dart";