2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-12-03 18:59:50 +00:00
Files
inventree-app/lib/labels.dart
Oliver 13d95dd1b1 Modern Label printing (#724)
* Basic widget

* Redirect label printing action

* Refactor label printing code

* Construct form elements

* Refactor to allow re-use of forms

* Basic rendering of label printing form

* Remove dead code

* Pass custom handler through to form context

* Refactoring API forms:

- Allow custom pk field name
- Add callback when values change

* linting

* Dynamically rebuild form

* Handle nested fields

* Handle label printing status

* Run dart format

* Update release notes

* Remove unused var

* Enable close icon

* Handle initial plugin default value

* Store default values:

- Selected template (per label type)
- Selected printing plugin

* Dart format

* Fix dart linting

* use setter

* Just use a public field
2025-11-22 07:26:37 +11:00

281 lines
7.7 KiB
Dart

import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:inventree/api.dart";
import "package:inventree/preferences.dart";
import "package:inventree/api_form.dart";
import "package:inventree/l10.dart";
import "package:inventree/widget/progress.dart";
import "package:inventree/widget/snacks.dart";
const String PRINT_LABEL_URL = "api/label/print/";
/*
* Custom form handler for label printing.
* Required to manage dynamic form fields.
*/
class LabelFormWidgetState extends APIFormWidgetState {
LabelFormWidgetState() : super();
List<APIFormField> dynamicFields = [];
String pluginKey = "";
String labelType = "";
@override
List<APIFormField> get formFields {
final baseFields = super.formFields;
if (pluginKey.isEmpty) {
// Handle case where default plugin is provided
final APIFormField pluginField = baseFields.firstWhere(
(field) => field.name == "plugin",
);
if (pluginField.initial_data != null) {
pluginKey = pluginField.value.toString();
onValueChanged("plugin", pluginKey);
}
}
return [...baseFields, ...dynamicFields];
}
@override
void onValueChanged(String field, dynamic value) {
if (field == "plugin") {
onPluginChanged(value.toString());
}
}
@override
Future<void> handleSuccess(
Map<String, dynamic> submittedData,
Map<String, dynamic> responseData,
) async {
super.handleSuccess(submittedData, responseData);
// Save default values to the database
final String? plugin = submittedData["plugin"]?.toString();
final int? template = submittedData["template"] as int?;
// Save default printing plugin
if (plugin != null) {
InvenTreeSettingsManager().setValue(INV_LABEL_DEFAULT_PLUGIN, plugin);
}
// Save default template for this label type
if (labelType.isNotEmpty && template != null) {
final defaultTemplates =
await InvenTreeSettingsManager().getValue(
INV_LABEL_DEFAULT_TEMPLATES,
null,
)
as Map<String, dynamic>?;
InvenTreeSettingsManager().setValue(INV_LABEL_DEFAULT_TEMPLATES, {
...?defaultTemplates,
labelType: template,
});
}
}
/*
* Re-fetch printing options when the plugin changes
*/
Future<void> onPluginChanged(String key) async {
showLoadingOverlay();
InvenTreeAPI().options(PRINT_LABEL_URL, params: {"plugin": key}).then((
APIResponse response,
) {
if (response.isValid()) {
updateFields(response);
hideLoadingOverlay();
}
});
}
/*
* Callback when the server responds with printing options,
* based on the selected printing plugin
*/
Future<void> updateFields(APIResponse response) async {
Map<String, dynamic> printingFields = extractFields(response);
// Find only the fields which are not in the "base" fields
List<APIFormField> uniqueFields = [];
for (String key in printingFields.keys) {
if (super.formFields.any((field) => field.name == key)) {
continue;
}
dynamic data = printingFields[key];
Map<String, dynamic> fieldData = {};
if (data is Map) {
fieldData = Map<String, dynamic>.from(data);
}
APIFormField field = APIFormField(key, fieldData);
field.definition = extractFieldDefinition(
printingFields,
field.lookupPath,
);
if (field.type == "dependent field") {
// Dependent fields must be handled separately
// TODO: This should be refactored into api_form.dart
dynamic child = field.definition["child"];
if (child != null && child is Map) {
Map<String, dynamic> child_map = child as Map<String, dynamic>;
dynamic nested_children = child_map["children"];
if (nested_children != null && nested_children is Map) {
Map<String, dynamic> nested_child_map =
nested_children as Map<String, dynamic>;
for (var field_key in nested_child_map.keys) {
field = APIFormField(field_key, nested_child_map);
field.definition = extractFieldDefinition(
nested_child_map,
field_key,
);
uniqueFields.add(field);
}
}
}
} else {
// This is a "standard" (non-nested) field
uniqueFields.add(field);
}
}
if (mounted) {
setState(() {
dynamicFields = uniqueFields;
});
}
}
}
Future<void> handlePrintingSuccess(
BuildContext context,
Map<String, dynamic> data,
int repeatCount,
) async {
const int MAX_REPEATS = 60;
int id = (data["pk"] ?? -1) as int;
bool complete = (data["complete"] ?? false) as bool;
bool error = data["errors"] != null;
String? output = data["output"] as String?;
if (complete) {
if (output != null && output.isNotEmpty) {
// An output was generated - we can download it!
showSnackIcon(L10().downloading, success: true);
InvenTreeAPI().downloadFile(output);
} else {
// Label was offloaded, likely to an external printer
showSnackIcon(L10().printLabelSuccess, success: true);
}
} else if (error) {
showSnackIcon(L10().printLabelFailure, success: false);
} else if (repeatCount < MAX_REPEATS && id > 0) {
// Printing is not yet complete, but we have a valid output ID
Future.delayed(Duration(milliseconds: 1000), () async {
// Re-query the printing status
InvenTreeAPI().get("data-output/$id/").then((response) {
if (response.statusCode == 200) {
if (response.data is Map<String, dynamic>) {
final responseData = response.data as Map<String, dynamic>;
handlePrintingSuccess(context, responseData, repeatCount + 1);
}
}
});
});
}
}
/*
* Select a particular label, from a provided list of options,
* and print against the selected instances.
*
*/
Future<void> selectAndPrintLabel(
BuildContext context,
String labelType,
int instanceId,
) async {
if (!InvenTreeAPI().isConnected()) {
return;
}
if (!InvenTreeAPI().supportsModernLabelPrinting) {
// Legacy label printing API not supported
showSnackIcon("Label printing not supported by server", success: false);
return;
}
// Fetch default values for label printing
// Default template
final defaultTemplates = await InvenTreeSettingsManager().getValue(
INV_LABEL_DEFAULT_TEMPLATES,
null,
);
int? defaultTemplate;
if (defaultTemplates != null && defaultTemplates is Map<String, dynamic>) {
defaultTemplate = defaultTemplates[labelType] as int?;
}
// Default plugin
final defaultPlugin = await InvenTreeSettingsManager().getValue(
INV_LABEL_DEFAULT_PLUGIN,
null,
);
// Specify a default list of fields for printing
// The selected plugin may optionally extend this list of fields dynamically
Map<String, Map<String, dynamic>> baseFields = {
"template": {
"default": defaultTemplate,
"filters": {
"enabled": true,
"model_type": labelType,
"items": instanceId.toString(),
},
},
"plugin": {
"default": defaultPlugin,
"pk_field": "key",
"filters": {"enabled": true, "mixin": "labels"},
},
"items": {
"hidden": true,
"value": [instanceId],
},
};
final formHandler = LabelFormWidgetState();
formHandler.labelType = labelType;
launchApiForm(
context,
L10().printLabel,
PRINT_LABEL_URL,
baseFields,
method: "POST",
modelData: {"plugin": defaultPlugin, "template": defaultTemplate},
formHandler: formHandler,
onSuccess: (data) async {
handlePrintingSuccess(context, data, 0);
},
);
}