diff --git a/lib/api_form.dart b/lib/api_form.dart index e0cf4578..514d3752 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -1,3 +1,4 @@ +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:inventree/api.dart'; import 'package:inventree/widget/dialogs.dart'; import 'package:inventree/widget/fields.dart'; @@ -139,7 +140,7 @@ Map extractFields(APIResponse response) { * @param method is the HTTP method to use to send the form data to the server (e.g. POST / PATCH) */ -Future launchApiForm(String title, String url, Map fields, {Map modelData = const {}, String method = "PATCH", Function? onSuccess, Function? onCancel}) async { +Future launchApiForm(BuildContext context, String title, String url, Map fields, {Map modelData = const {}, String method = "PATCH", Function? onSuccess, Function? onCancel}) async { var options = await InvenTreeAPI().options(url); @@ -195,136 +196,169 @@ Future launchApiForm(String title, String url, Map fields formFields.add(APIFormField(fieldName, remoteField)); } - List buildWidgets() { + // Now, launch a new widget! + Navigator.push( + context, + MaterialPageRoute(builder: (context) => APIFormWidget(title, url, formFields)) + ); +} + + +class APIFormWidget extends StatefulWidget { + + //! Form title to display + final String title; + + //! API URL + final String url; + + final List fields; + + APIFormWidget(this.title, this.url, this.fields, {Key? key}) : super(key: key); + + @override + _APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields); + +} + + +class _APIFormWidgetState extends State { + + final _formKey = new GlobalKey(); + + String title; + + String url; + + List fields; + + _APIFormWidgetState(this.title, this.url, this.fields) : super(); + + List _buildForm() { + List widgets = []; - for (var ff in formFields) { + for (var field in fields) { - if (ff.hidden) { + if (field.hidden) { continue; } - widgets.add(ff.constructField()); + widgets.add(field.constructField()); - if (ff.hasErrors()) { - for (String error in ff.errorMessages()) { + if (field.hasErrors()) { + for (String error in field.errorMessages()) { widgets.add( - ListTile( - title: Text( - error, - style: TextStyle(color: Color.fromRGBO(250, 50, 50, 1)) - ), + ListTile( + title: Text( + error, + style: TextStyle(color: Color.fromRGBO(250, 50, 50, 1)), ) + ) ); } } - } + // TODO: Add a "Save" button + // widgets.add(Spacer()); + + /* + widgets.add( + TextButton( + child: Text( + L10().save + ), + onPressed: null, + ) + ); + */ + return widgets; } - - List _widgets = buildWidgets(); - - - void sendRequest(BuildContext context) async { + Future _save(BuildContext context) async { // Package up the form data - Map formData = {}; + Map _data = {}; - for (var field in formFields) { - formData[field.name] = field.value.toString(); + for (var field in fields) { + _data[field.name] = field.value.toString(); } - var response = await InvenTreeAPI().patch( + // TODO: Handle "POST" forms too!! + final response = await InvenTreeAPI().patch( url, - body: formData, + body: _data, ); if (!response.isValid()) { - // TODO - Display an error message here... + // TODO: Display an error message! return; } switch (response.statusCode) { case 200: case 201: - // Form was validated by the server + // Form was successfully validated by the server + + // Hide this form Navigator.pop(context); - if (onSuccess != null) { - onSuccess(); - } + // TODO: Display a snackBar - break; + // TODO: Run custom onSuccess function + return; case 400: + // Form submission / validation error // Update field errors - for (var field in formFields) { + for (var field in fields) { field.data['errors'] = response.data[field.name]; - - if (field.hasErrors()) { - print("Field '${field.name}' has errors:"); - for (String error in field.errorMessages()) { - print(" - ${error}"); - } - } } - break; + // TODO: Other status codes? } + + setState(() { + // Refresh the form + }); + } + @override + Widget build(BuildContext context) { - OneContext().showDialog( - builder: (BuildContext context) { - return StatefulBuilder( - builder: (context, setState) { - return AlertDialog( - title: Text(title), - actions: [ - // Cancel button - TextButton( - child: Text(L10().cancel), - onPressed: () { - Navigator.pop(context); + return Scaffold( + appBar: AppBar( + title: Text(title), + actions: [ + IconButton( + icon: FaIcon(FontAwesomeIcons.save), + onPressed: () { - if (onCancel != null) { - onCancel(); - } - }, - ), - // Save button - TextButton( - child: Text(L10().save), - onPressed: () { - // Validate the form - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); - setState(() { - sendRequest(context); - _widgets = buildWidgets(); - }); - } - }, - ) - ], - content: Form( - key: _formKey, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: _widgets, - ) - ) - ) - ); - } - ); - } - ); + _save(context); + } + }, + ) + ] + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildForm(), + ), + padding: EdgeInsets.all(16), + ) + ) + ); + + } } \ No newline at end of file diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 1c5bc55d..f7ce3fba 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -19,6 +19,7 @@ import 'package:inventree/widget/spinner.dart'; import 'package:inventree/widget/drawer.dart'; class InvenTreeHomePage extends StatefulWidget { + InvenTreeHomePage({Key? key}) : super(key: key); @override diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index bb01ea0e..46187b80 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -60,7 +60,9 @@ class _PartDisplayState extends RefreshableState { IconButton( icon: FaIcon(FontAwesomeIcons.edit), tooltip: L10().edit, - onPressed: _editPartDialog, + onPressed: () { + _editPartDialog(context); + }, ) ); } @@ -170,7 +172,7 @@ class _PartDisplayState extends RefreshableState { ); } - void _editPartDialog() { + void _editPartDialog(BuildContext context) { // Values which can be edited var _name; @@ -180,6 +182,7 @@ class _PartDisplayState extends RefreshableState { var _link; launchApiForm( + context, L10().editPart, part.url, {