mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +00:00
Refactor modal form into a new stateful widget
This commit is contained in:
parent
885df45138
commit
64aed4b31a
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:inventree/api.dart';
|
import 'package:inventree/api.dart';
|
||||||
import 'package:inventree/widget/dialogs.dart';
|
import 'package:inventree/widget/dialogs.dart';
|
||||||
import 'package:inventree/widget/fields.dart';
|
import 'package:inventree/widget/fields.dart';
|
||||||
@ -139,7 +140,7 @@ Map<String, dynamic> extractFields(APIResponse response) {
|
|||||||
* @param method is the HTTP method to use to send the form data to the server (e.g. POST / PATCH)
|
* @param method is the HTTP method to use to send the form data to the server (e.g. POST / PATCH)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Future<void> launchApiForm(String title, String url, Map<String, dynamic> fields, {Map<String, dynamic> modelData = const {}, String method = "PATCH", Function? onSuccess, Function? onCancel}) async {
|
Future<void> launchApiForm(BuildContext context, String title, String url, Map<String, dynamic> fields, {Map<String, dynamic> modelData = const {}, String method = "PATCH", Function? onSuccess, Function? onCancel}) async {
|
||||||
|
|
||||||
var options = await InvenTreeAPI().options(url);
|
var options = await InvenTreeAPI().options(url);
|
||||||
|
|
||||||
@ -195,136 +196,169 @@ Future<void> launchApiForm(String title, String url, Map<String, dynamic> fields
|
|||||||
formFields.add(APIFormField(fieldName, remoteField));
|
formFields.add(APIFormField(fieldName, remoteField));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> 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<APIFormField> fields;
|
||||||
|
|
||||||
|
APIFormWidget(this.title, this.url, this.fields, {Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _APIFormWidgetState extends State<APIFormWidget> {
|
||||||
|
|
||||||
|
final _formKey = new GlobalKey<FormState>();
|
||||||
|
|
||||||
|
String title;
|
||||||
|
|
||||||
|
String url;
|
||||||
|
|
||||||
|
List<APIFormField> fields;
|
||||||
|
|
||||||
|
_APIFormWidgetState(this.title, this.url, this.fields) : super();
|
||||||
|
|
||||||
|
List<Widget> _buildForm() {
|
||||||
|
|
||||||
List<Widget> widgets = [];
|
List<Widget> widgets = [];
|
||||||
|
|
||||||
for (var ff in formFields) {
|
for (var field in fields) {
|
||||||
|
|
||||||
if (ff.hidden) {
|
if (field.hidden) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
widgets.add(ff.constructField());
|
widgets.add(field.constructField());
|
||||||
|
|
||||||
if (ff.hasErrors()) {
|
if (field.hasErrors()) {
|
||||||
for (String error in ff.errorMessages()) {
|
for (String error in field.errorMessages()) {
|
||||||
widgets.add(
|
widgets.add(
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
error,
|
error,
|
||||||
style: TextStyle(color: Color.fromRGBO(250, 50, 50, 1))
|
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;
|
return widgets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _save(BuildContext context) async {
|
||||||
List<Widget> _widgets = buildWidgets();
|
|
||||||
|
|
||||||
|
|
||||||
void sendRequest(BuildContext context) async {
|
|
||||||
|
|
||||||
// Package up the form data
|
// Package up the form data
|
||||||
Map<String, String> formData = {};
|
Map<String, String> _data = {};
|
||||||
|
|
||||||
for (var field in formFields) {
|
for (var field in fields) {
|
||||||
formData[field.name] = field.value.toString();
|
_data[field.name] = field.value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
var response = await InvenTreeAPI().patch(
|
// TODO: Handle "POST" forms too!!
|
||||||
|
final response = await InvenTreeAPI().patch(
|
||||||
url,
|
url,
|
||||||
body: formData,
|
body: _data,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.isValid()) {
|
if (!response.isValid()) {
|
||||||
// TODO - Display an error message here...
|
// TODO: Display an error message!
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (response.statusCode) {
|
switch (response.statusCode) {
|
||||||
case 200:
|
case 200:
|
||||||
case 201:
|
case 201:
|
||||||
// Form was validated by the server
|
// Form was successfully validated by the server
|
||||||
|
|
||||||
|
// Hide this form
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|
||||||
if (onSuccess != null) {
|
// TODO: Display a snackBar
|
||||||
onSuccess();
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
// TODO: Run custom onSuccess function
|
||||||
|
return;
|
||||||
case 400:
|
case 400:
|
||||||
|
// Form submission / validation error
|
||||||
|
|
||||||
// Update field errors
|
// Update field errors
|
||||||
for (var field in formFields) {
|
for (var field in fields) {
|
||||||
field.data['errors'] = response.data[field.name];
|
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;
|
break;
|
||||||
|
// TODO: Other status codes?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
// Refresh the form
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
OneContext().showDialog(
|
return Scaffold(
|
||||||
builder: (BuildContext context) {
|
appBar: AppBar(
|
||||||
return StatefulBuilder(
|
title: Text(title),
|
||||||
builder: (context, setState) {
|
actions: [
|
||||||
return AlertDialog(
|
IconButton(
|
||||||
title: Text(title),
|
icon: FaIcon(FontAwesomeIcons.save),
|
||||||
actions: <Widget>[
|
onPressed: () {
|
||||||
// Cancel button
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10().cancel),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.pop(context);
|
|
||||||
|
|
||||||
if (onCancel != null) {
|
if (_formKey.currentState!.validate()) {
|
||||||
onCancel();
|
_formKey.currentState!.save();
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// Save button
|
|
||||||
TextButton(
|
|
||||||
child: Text(L10().save),
|
|
||||||
onPressed: () {
|
|
||||||
// Validate the form
|
|
||||||
if (_formKey.currentState!.validate()) {
|
|
||||||
_formKey.currentState!.save();
|
|
||||||
|
|
||||||
setState(() {
|
_save(context);
|
||||||
sendRequest(context);
|
}
|
||||||
_widgets = buildWidgets();
|
},
|
||||||
});
|
)
|
||||||
}
|
]
|
||||||
},
|
),
|
||||||
)
|
body: Form(
|
||||||
],
|
key: _formKey,
|
||||||
content: Form(
|
child: SingleChildScrollView(
|
||||||
key: _formKey,
|
child: Column(
|
||||||
child: SingleChildScrollView(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
child: Column(
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: _buildForm(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: _widgets,
|
padding: EdgeInsets.all(16),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
@ -19,6 +19,7 @@ import 'package:inventree/widget/spinner.dart';
|
|||||||
import 'package:inventree/widget/drawer.dart';
|
import 'package:inventree/widget/drawer.dart';
|
||||||
|
|
||||||
class InvenTreeHomePage extends StatefulWidget {
|
class InvenTreeHomePage extends StatefulWidget {
|
||||||
|
|
||||||
InvenTreeHomePage({Key? key}) : super(key: key);
|
InvenTreeHomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -60,7 +60,9 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.edit),
|
icon: FaIcon(FontAwesomeIcons.edit),
|
||||||
tooltip: L10().edit,
|
tooltip: L10().edit,
|
||||||
onPressed: _editPartDialog,
|
onPressed: () {
|
||||||
|
_editPartDialog(context);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -170,7 +172,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _editPartDialog() {
|
void _editPartDialog(BuildContext context) {
|
||||||
|
|
||||||
// Values which can be edited
|
// Values which can be edited
|
||||||
var _name;
|
var _name;
|
||||||
@ -180,6 +182,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
var _link;
|
var _link;
|
||||||
|
|
||||||
launchApiForm(
|
launchApiForm(
|
||||||
|
context,
|
||||||
L10().editPart,
|
L10().editPart,
|
||||||
part.url,
|
part.url,
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user