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 dynamicFields = []; String pluginKey = ""; String labelType = ""; @override List 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 handleSuccess( Map submittedData, Map 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?; InvenTreeSettingsManager().setValue(INV_LABEL_DEFAULT_TEMPLATES, { ...?defaultTemplates, labelType: template, }); } } /* * Re-fetch printing options when the plugin changes */ Future 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 updateFields(APIResponse response) async { Map printingFields = extractFields(response); // Find only the fields which are not in the "base" fields List uniqueFields = []; for (String key in printingFields.keys) { if (super.formFields.any((field) => field.name == key)) { continue; } dynamic data = printingFields[key]; Map fieldData = {}; if (data is Map) { fieldData = Map.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 child_map = child as Map; dynamic nested_children = child_map["children"]; if (nested_children != null && nested_children is Map) { Map nested_child_map = nested_children as Map; 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 handlePrintingSuccess( BuildContext context, Map 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) { final responseData = response.data as Map; handlePrintingSuccess(context, responseData, repeatCount + 1); } } }); }); } } /* * Select a particular label, from a provided list of options, * and print against the selected instances. * */ Future 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) { 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> 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); }, ); }