From 346b1a150ffc24cddd9131b2d944f6994aa5c472 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 28 Nov 2025 23:53:10 +1100 Subject: [PATCH] Attachments refactor (#737) * refactor attachment code into its own file * Add getters * Remove custom models for each type of attachment * Refactor existing widgets * Fix double camera open bug * Remove dead code * Remove unused imports * Refactor common code * format * Update release notes --- assets/release_notes.md | 5 + lib/inventree/attachment.dart | 176 ++++++++++++++++++++ lib/inventree/company.dart | 25 --- lib/inventree/model.dart | 170 ------------------- lib/inventree/part.dart | 25 --- lib/inventree/purchase_order.dart | 25 --- lib/inventree/sales_order.dart | 47 +----- lib/inventree/stock.dart | 25 --- lib/widget/attachment_widget.dart | 92 ++++++---- lib/widget/company/company_detail.dart | 49 +++--- lib/widget/order/purchase_order_detail.dart | 58 +++---- lib/widget/order/sales_order_detail.dart | 58 +++---- lib/widget/order/so_shipment_detail.dart | 49 +++--- lib/widget/part/part_detail.dart | 47 +++--- lib/widget/stock/stock_detail.dart | 49 +++--- 15 files changed, 381 insertions(+), 519 deletions(-) create mode 100644 lib/inventree/attachment.dart diff --git a/assets/release_notes.md b/assets/release_notes.md index 17fd96d1..cbde0048 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,3 +1,8 @@ +### x.xx.x - Month Year +--- + +- Fixes bug which launched camera twice when uploading an attachment + ### 0.21.1 - November 2025 --- diff --git a/lib/inventree/attachment.dart b/lib/inventree/attachment.dart new file mode 100644 index 00000000..3f1b9130 --- /dev/null +++ b/lib/inventree/attachment.dart @@ -0,0 +1,176 @@ +import "dart:io"; + +import "package:flutter/cupertino.dart"; +import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; +import "package:inventree/api.dart"; +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/widget/fields.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:path/path.dart" as path; + +class InvenTreeAttachment extends InvenTreeModel { + // Class representing an "attachment" file + InvenTreeAttachment() : super(); + + InvenTreeAttachment.fromJson(Map json) + : super.fromJson(json); + + @override + InvenTreeAttachment createFromJson(Map json) => + InvenTreeAttachment.fromJson(json); + + @override + String get URL => "attachment/"; + + @override + Map> formFields() { + Map> fields = {"link": {}, "comment": {}}; + + if (!hasLink) { + fields.remove("link"); + } + + return fields; + } + + // The model type of the instance this attachment is associated with + String get modelType => getString("model_type"); + + // The ID of the instance this attachment is associated with + int get modelId => getInt("model_id"); + + String get attachment => getString("attachment"); + + bool get hasAttachment => attachment.isNotEmpty; + + // Return the filename of the attachment + String get filename { + return attachment.split("/").last; + } + + IconData get icon { + String fn = filename.toLowerCase(); + + if (fn.endsWith(".pdf")) { + return TablerIcons.file_type_pdf; + } else if (fn.endsWith(".csv")) { + return TablerIcons.file_type_csv; + } else if (fn.endsWith(".doc") || fn.endsWith(".docx")) { + return TablerIcons.file_type_doc; + } else if (fn.endsWith(".xls") || fn.endsWith(".xlsx")) { + return TablerIcons.file_type_xls; + } + + // Image formats + final List img_formats = [".png", ".jpg", ".gif", ".bmp", ".svg"]; + + for (String fmt in img_formats) { + if (fn.endsWith(fmt)) { + return TablerIcons.file_type_jpg; + } + } + + return TablerIcons.file; + } + + String get comment => getString("comment"); + + DateTime? get uploadDate { + if (jsondata.containsKey("upload_date")) { + return DateTime.tryParse((jsondata["upload_date"] ?? "") as String); + } else { + return null; + } + } + + // Return a count of how many attachments exist against the specified model ID + Future countAttachments(String modelType, int modelId) async { + Map filters = {}; + + if (!api.supportsModernAttachments) { + return 0; + } + + filters["model_type"] = modelType; + filters["model_id"] = modelId.toString(); + + return count(filters: filters); + } + + Future uploadAttachment( + File attachment, + String modelType, + int modelId, { + String comment = "", + Map fields = const {}, + }) async { + // Ensure that the correct reference field is set + Map data = Map.from(fields); + + String url = URL; + + if (comment.isNotEmpty) { + data["comment"] = comment; + } + + data["model_type"] = modelType; + data["model_id"] = modelId.toString(); + + final APIResponse response = await InvenTreeAPI().uploadFile( + url, + attachment, + method: "POST", + name: "attachment", + fields: data, + ); + + return response.successful(); + } + + Future uploadImage( + String modelType, + int modelId, { + String prefix = "InvenTree", + }) async { + bool result = false; + + await FilePickerDialog.pickImageFromCamera().then((File? file) { + if (file != null) { + String dir = path.dirname(file.path); + String ext = path.extension(file.path); + String now = DateTime.now().toIso8601String().replaceAll(":", "-"); + + // Rename the file with a unique name + String filename = "${dir}/${prefix}_image_${now}${ext}"; + + try { + return file.rename(filename).then((File renamed) { + return uploadAttachment(renamed, modelType, modelId).then(( + success, + ) { + result = success; + showSnackIcon( + result ? L10().imageUploadSuccess : L10().imageUploadFailure, + success: result, + ); + }); + }); + } catch (error, stackTrace) { + sentryReportError("uploadImage", error, stackTrace); + showSnackIcon(L10().imageUploadFailure, success: false); + } + } + }); + + return result; + } + + /* + * Download this attachment file + */ + Future downloadAttachment() async { + await InvenTreeAPI().downloadFile(attachment); + } +} diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index a2a3f21d..90ed77c1 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -111,31 +111,6 @@ class InvenTreeCompany extends InvenTreeModel { InvenTreeCompany.fromJson(json); } -/* - * Class representing an attachment file against a Company object - */ -class InvenTreeCompanyAttachment extends InvenTreeAttachment { - InvenTreeCompanyAttachment() : super(); - - InvenTreeCompanyAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - String get REFERENCE_FIELD => "company"; - - @override - String get REF_MODEL_TYPE => "company"; - - @override - String get URL => InvenTreeAPI().supportsModernAttachments - ? "attachment/" - : "company/attachment/"; - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreeCompanyAttachment.fromJson(json); -} - /* * The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database */ diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index e08a053e..f87b2de5 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -1,5 +1,4 @@ import "dart:async"; -import "dart:io"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:flutter/material.dart"; @@ -15,7 +14,6 @@ import "package:inventree/helpers.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/dialogs.dart"; -import "package:inventree/widget/fields.dart"; // Paginated response object class InvenTreePageResponse { @@ -934,171 +932,3 @@ class InvenTreeUserSetting extends InvenTreeGlobalSetting { @override String get URL => "settings/user/"; } - -class InvenTreeAttachment extends InvenTreeModel { - // Class representing an "attachment" file - InvenTreeAttachment() : super(); - - InvenTreeAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - String get URL => "attachment/"; - - @override - Map> formFields() { - Map> fields = {"link": {}, "comment": {}}; - - if (!hasLink) { - fields.remove("link"); - } - - return fields; - } - - // Override this reference field for any subclasses - // Note: This is used for the *legacy* attachment API - String get REFERENCE_FIELD => ""; - - // Override this reference field for any subclasses - // Note: This is used for the *modern* attachment API - String get REF_MODEL_TYPE => ""; - - String get attachment => getString("attachment"); - - bool get hasAttachment => attachment.isNotEmpty; - - // Return the filename of the attachment - String get filename { - return attachment.split("/").last; - } - - IconData get icon { - String fn = filename.toLowerCase(); - - if (fn.endsWith(".pdf")) { - return TablerIcons.file_type_pdf; - } else if (fn.endsWith(".csv")) { - return TablerIcons.file_type_csv; - } else if (fn.endsWith(".doc") || fn.endsWith(".docx")) { - return TablerIcons.file_type_doc; - } else if (fn.endsWith(".xls") || fn.endsWith(".xlsx")) { - return TablerIcons.file_type_xls; - } - - // Image formats - final List img_formats = [".png", ".jpg", ".gif", ".bmp", ".svg"]; - - for (String fmt in img_formats) { - if (fn.endsWith(fmt)) { - return TablerIcons.file_type_jpg; - } - } - - return TablerIcons.file; - } - - String get comment => getString("comment"); - - DateTime? get uploadDate { - if (jsondata.containsKey("upload_date")) { - return DateTime.tryParse((jsondata["upload_date"] ?? "") as String); - } else { - return null; - } - } - - // Return a count of how many attachments exist against the specified model ID - Future countAttachments(int modelId) { - Map filters = {}; - - if (InvenTreeAPI().supportsModernAttachments) { - filters["model_type"] = REF_MODEL_TYPE; - filters["model_id"] = modelId.toString(); - } else { - filters[REFERENCE_FIELD] = modelId.toString(); - } - - return count(filters: filters); - } - - Future uploadAttachment( - File attachment, - int modelId, { - String comment = "", - Map fields = const {}, - }) async { - // Ensure that the correct reference field is set - Map data = Map.from(fields); - - String url = URL; - - if (comment.isNotEmpty) { - data["comment"] = comment; - } - - if (InvenTreeAPI().supportsModernAttachments) { - url = "attachment/"; - data["model_id"] = modelId.toString(); - data["model_type"] = REF_MODEL_TYPE; - } else { - if (REFERENCE_FIELD.isEmpty) { - sentryReportMessage( - "uploadAttachment called with empty 'REFERENCE_FIELD'", - ); - return false; - } - - data[REFERENCE_FIELD] = modelId.toString(); - } - - final APIResponse response = await InvenTreeAPI().uploadFile( - url, - attachment, - method: "POST", - name: "attachment", - fields: data, - ); - - return response.successful(); - } - - Future uploadImage(int modelId, {String prefix = "InvenTree"}) async { - bool result = false; - - await FilePickerDialog.pickImageFromCamera().then((File? file) { - if (file != null) { - String dir = path.dirname(file.path); - String ext = path.extension(file.path); - String now = DateTime.now().toIso8601String().replaceAll(":", "-"); - - // Rename the file with a unique name - String filename = "${dir}/${prefix}_image_${now}${ext}"; - - try { - file.rename(filename).then((File renamed) { - uploadAttachment(renamed, modelId).then((success) { - result = success; - showSnackIcon( - result ? L10().imageUploadSuccess : L10().imageUploadFailure, - success: result, - ); - }); - }); - } catch (error, stackTrace) { - sentryReportError("uploadImage", error, stackTrace); - showSnackIcon(L10().imageUploadFailure, success: false); - } - } - }); - - return result; - } - - /* - * Download this attachment file - */ - Future downloadAttachment() async { - await InvenTreeAPI().downloadFile(attachment); - } -} diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 23e306c1..fc523992 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -547,28 +547,3 @@ class InvenTreePartPricing extends InvenTreeModel { double? get saleHistoryMin => getDoubleOrNull("sale_history_min"); double? get saleHistoryMax => getDoubleOrNull("sale_history_max"); } - -/* - * Class representing an attachment file against a Part object - */ -class InvenTreePartAttachment extends InvenTreeAttachment { - InvenTreePartAttachment() : super(); - - InvenTreePartAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - String get REFERENCE_FIELD => "part"; - - @override - String get REF_MODEL_TYPE => "part"; - - @override - String get URL => InvenTreeAPI().supportsModernAttachments - ? "attachment/" - : "part/attachment/"; - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreePartAttachment.fromJson(json); -} diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 59874dc1..65af3610 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -336,28 +336,3 @@ class InvenTreePOExtraLineItem extends InvenTreeExtraLineItem { ); } } - -/* - * Class representing an attachment file against a PurchaseOrder object - */ -class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { - InvenTreePurchaseOrderAttachment() : super(); - - InvenTreePurchaseOrderAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - String get REFERENCE_FIELD => "order"; - - @override - String get REF_MODEL_TYPE => "purchaseorder"; - - @override - String get URL => InvenTreeAPI().supportsModernAttachments - ? "attachment/" - : "order/po/attachment/"; - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreePurchaseOrderAttachment.fromJson(json); -} diff --git a/lib/inventree/sales_order.dart b/lib/inventree/sales_order.dart index 8b661f4a..77c3469d 100644 --- a/lib/inventree/sales_order.dart +++ b/lib/inventree/sales_order.dart @@ -334,7 +334,7 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel { /* * Class representing an allocation of stock against a SalesOrderShipment */ -class InvenTreeSalesOrderAllocation extends InvenTreeAttachment { +class InvenTreeSalesOrderAllocation extends InvenTreeModel { InvenTreeSalesOrderAllocation() : super(); InvenTreeSalesOrderAllocation.fromJson(Map json) @@ -428,48 +428,3 @@ class InvenTreeSalesOrderAllocation extends InvenTreeAttachment { } } } - -/* - * Class representing an attachment file against a SalesOrder object - */ -class InvenTreeSalesOrderAttachment extends InvenTreeAttachment { - InvenTreeSalesOrderAttachment() : super(); - - InvenTreeSalesOrderAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreeSalesOrderAttachment.fromJson(json); - - @override - String get REFERENCE_FIELD => "order"; - - @override - String get REF_MODEL_TYPE => "salesorder"; - - @override - String get URL => InvenTreeAPI().supportsModernAttachments - ? "attachment/" - : "order/so/attachment/"; -} - -class InvenTreeSalesOrderShipmentAttachment extends InvenTreeAttachment { - InvenTreeSalesOrderShipmentAttachment() : super(); - - InvenTreeSalesOrderShipmentAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreeSalesOrderShipmentAttachment.fromJson(json); - - @override - String get REFERENCE_FIELD => "shipment"; - - @override - String get REF_MODEL_TYPE => "salesordershipment"; - - @override - String get URL => "attachment/"; -} diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 1a45e73e..4b5bf3b0 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -575,31 +575,6 @@ class InvenTreeStockItem extends InvenTreeModel { } } -/* - * Class representing an attachment file against a StockItem object - */ -class InvenTreeStockItemAttachment extends InvenTreeAttachment { - InvenTreeStockItemAttachment() : super(); - - InvenTreeStockItemAttachment.fromJson(Map json) - : super.fromJson(json); - - @override - String get REFERENCE_FIELD => "stock_item"; - - @override - String get REF_MODEL_TYPE => "stockitem"; - - @override - String get URL => InvenTreeAPI().supportsModernAttachments - ? "attachment/" - : "stock/attachment/"; - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreeStockItemAttachment.fromJson(json); -} - class InvenTreeStockLocation extends InvenTreeModel { InvenTreeStockLocation() : super(); diff --git a/lib/widget/attachment_widget.dart b/lib/widget/attachment_widget.dart index b66462f3..721909d9 100644 --- a/lib/widget/attachment_widget.dart +++ b/lib/widget/attachment_widget.dart @@ -2,14 +2,13 @@ import "dart:io"; import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; +import "package:inventree/api.dart"; +import "package:inventree/inventree/attachment.dart"; +import "package:inventree/widget/link_icon.dart"; import "package:one_context/one_context.dart"; -import "package:inventree/api.dart"; import "package:inventree/l10.dart"; import "package:inventree/app_colors.dart"; - -import "package:inventree/inventree/model.dart"; - import "package:inventree/widget/fields.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; @@ -23,13 +22,13 @@ import "package:inventree/widget/refreshable_state.dart"; */ class AttachmentWidget extends StatefulWidget { const AttachmentWidget( - this.attachmentClass, + this.modelType, this.modelId, this.imagePrefix, this.hasUploadPermission, ) : super(); - final InvenTreeAttachment attachmentClass; + final String modelType; final int modelId; final bool hasUploadPermission; final String imagePrefix; @@ -54,15 +53,15 @@ class _AttachmentWidgetState extends RefreshableState { IconButton( icon: Icon(TablerIcons.camera), onPressed: () async { - widget.attachmentClass.uploadImage( - widget.modelId, - prefix: widget.imagePrefix, - ); - FilePickerDialog.pickImageFromCamera().then((File? file) { - upload(context, file).then((_) { - refresh(context); - }); - }); + InvenTreeAttachment() + .uploadImage( + widget.modelType, + widget.modelId, + prefix: widget.imagePrefix, + ) + .then((_) { + refresh(context); + }); }, ), IconButton( @@ -83,8 +82,9 @@ class _AttachmentWidgetState extends RefreshableState { showLoadingOverlay(); - final bool result = await widget.attachmentClass.uploadAttachment( + final bool result = await InvenTreeAttachment().uploadAttachment( file, + widget.modelType, widget.modelId, ); @@ -168,25 +168,24 @@ class _AttachmentWidgetState extends RefreshableState { Future request(BuildContext context) async { Map filters = {}; - if (InvenTreeAPI().supportsModernAttachments) { - filters["model_type"] = widget.attachmentClass.REF_MODEL_TYPE; - filters["model_id"] = widget.modelId.toString(); - } else { - filters[widget.attachmentClass.REFERENCE_FIELD] = widget.modelId - .toString(); - } + filters["model_type"] = widget.modelType; + filters["model_id"] = widget.modelId.toString(); - await widget.attachmentClass.list(filters: filters).then((var results) { - attachments.clear(); + List _attachments = []; + InvenTreeAttachment().list(filters: filters).then((var results) { for (var result in results) { if (result is InvenTreeAttachment) { - attachments.add(result); + _attachments.add(result); } } - }); - setState(() {}); + if (mounted) { + setState(() { + attachments = _attachments; + }); + } + }); } @override @@ -240,3 +239,40 @@ class _AttachmentWidgetState extends RefreshableState { return tiles; } } + +/* + * Return a ListTile to display attachments for the specified model + */ +ListTile? ShowAttachmentsItem( + BuildContext context, + String modelType, + int modelId, + String imagePrefix, + int attachmentCount, + bool hasUploadPermission, +) { + if (!InvenTreeAPI().supportsModernAttachments) { + return null; + } + + return ListTile( + title: Text(L10().attachments), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + trailing: LinkIcon( + text: attachmentCount > 0 ? attachmentCount.toString() : null, + ), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AttachmentWidget( + modelType, + modelId, + imagePrefix, + hasUploadPermission, + ), + ), + ); + }, + ); +} diff --git a/lib/widget/company/company_detail.dart b/lib/widget/company/company_detail.dart index 5d06e581..8947104b 100644 --- a/lib/widget/company/company_detail.dart +++ b/lib/widget/company/company_detail.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; +import "package:inventree/inventree/attachment.dart"; import "package:inventree/l10.dart"; import "package:inventree/api.dart"; @@ -184,15 +185,15 @@ class _CompanyDetailState extends RefreshableState { } }); - InvenTreeCompanyAttachment().countAttachments(widget.company.pk).then(( - value, - ) { - if (mounted) { - setState(() { - attachmentCount = value; + InvenTreeAttachment() + .countAttachments(InvenTreeCompany.MODEL_TYPE, widget.company.pk) + .then((value) { + if (mounted) { + setState(() { + attachmentCount = value; + }); + } }); - } - }); } Future editCompany(BuildContext context) async { @@ -393,29 +394,19 @@ class _CompanyDetailState extends RefreshableState { ); } - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: LinkIcon( - text: attachmentCount > 0 ? attachmentCount.toString() : null, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreeCompanyAttachment(), - widget.company.pk, - widget.company.name, - InvenTreeCompany().canEdit, - ), - ), - ); - }, - ), + ListTile? attachmentTile = ShowAttachmentsItem( + context, + InvenTreeCompany.MODEL_TYPE, + widget.company.pk, + widget.company.name, + attachmentCount, + widget.company.canEdit, ); + if (attachmentTile != null) { + tiles.add(attachmentTile); + } + return tiles; } } diff --git a/lib/widget/order/purchase_order_detail.dart b/lib/widget/order/purchase_order_detail.dart index 4201cd36..0829e06c 100644 --- a/lib/widget/order/purchase_order_detail.dart +++ b/lib/widget/order/purchase_order_detail.dart @@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart"; import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/purchase_order.dart"; import "package:inventree/helpers.dart"; +import "package:inventree/inventree/attachment.dart"; import "package:inventree/l10.dart"; import "package:inventree/inventree/model.dart"; @@ -174,8 +175,12 @@ class _PurchaseOrderDetailState /// Upload an image against the current PurchaseOrder Future _uploadImage(BuildContext context) async { - InvenTreePurchaseOrderAttachment() - .uploadImage(widget.order.pk, prefix: widget.order.reference) + InvenTreeAttachment() + .uploadImage( + InvenTreePurchaseOrder.MODEL_TYPE, + widget.order.pk, + prefix: widget.order.reference, + ) .then((result) => refresh(context)); } @@ -295,15 +300,15 @@ class _PurchaseOrderDetailState } } - InvenTreePurchaseOrderAttachment().countAttachments(widget.order.pk).then(( - int value, - ) { - if (mounted) { - setState(() { - attachmentCount = value; + InvenTreeAttachment() + .countAttachments(InvenTreePurchaseOrder.MODEL_TYPE, widget.order.pk) + .then((int value) { + if (mounted) { + setState(() { + attachmentCount = value; + }); + } }); - } - }); if (api.supportsPurchaseOrderDestination && widget.order.destinationId > 0) { @@ -565,30 +570,19 @@ class _PurchaseOrderDetailState ), ); - // Attachments - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: LinkIcon( - text: attachmentCount > 0 ? attachmentCount.toString() : null, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreePurchaseOrderAttachment(), - widget.order.pk, - widget.order.reference, - widget.order.canEdit, - ), - ), - ); - }, - ), + ListTile? attachmentTile = ShowAttachmentsItem( + context, + InvenTreePurchaseOrder.MODEL_TYPE, + widget.order.pk, + widget.order.reference, + attachmentCount, + widget.order.canEdit, ); + if (attachmentTile != null) { + tiles.add(attachmentTile); + } + return tiles; } diff --git a/lib/widget/order/sales_order_detail.dart b/lib/widget/order/sales_order_detail.dart index ec18561c..30692f2b 100644 --- a/lib/widget/order/sales_order_detail.dart +++ b/lib/widget/order/sales_order_detail.dart @@ -3,6 +3,7 @@ import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/sales_order.dart"; +import "package:inventree/inventree/attachment.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/sales_order.dart"; import "package:inventree/preferences.dart"; @@ -108,8 +109,12 @@ class _SalesOrderDetailState extends RefreshableState { /// Upload an image for this order Future _uploadImage(BuildContext context) async { - InvenTreeSalesOrderAttachment() - .uploadImage(widget.order.pk, prefix: widget.order.reference) + InvenTreeAttachment() + .uploadImage( + InvenTreeSalesOrder.MODEL_TYPE, + widget.order.pk, + prefix: widget.order.reference, + ) .then((result) => refresh(context)); } @@ -266,15 +271,15 @@ class _SalesOrderDetailState extends RefreshableState { true, ); - InvenTreeSalesOrderAttachment().countAttachments(widget.order.pk).then(( - int value, - ) { - if (mounted) { - setState(() { - attachmentCount = value; + InvenTreeAttachment() + .countAttachments(InvenTreeSalesOrder.MODEL_TYPE, widget.order.pk) + .then((int value) { + if (mounted) { + setState(() { + attachmentCount = value; + }); + } }); - } - }); // Count number of "extra line items" against this order InvenTreeSOExtraLineItem() @@ -492,30 +497,19 @@ class _SalesOrderDetailState extends RefreshableState { ), ); - // Attachments - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: LinkIcon( - text: attachmentCount > 0 ? attachmentCount.toString() : null, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreeSalesOrderAttachment(), - widget.order.pk, - widget.order.reference, - widget.order.canEdit, - ), - ), - ); - }, - ), + ListTile? attachmentTile = ShowAttachmentsItem( + context, + InvenTreeSalesOrder.MODEL_TYPE, + widget.order.pk, + widget.order.reference, + attachmentCount, + widget.order.canEdit, ); + if (attachmentTile != null) { + tiles.add(attachmentTile); + } + return tiles; } diff --git a/lib/widget/order/so_shipment_detail.dart b/lib/widget/order/so_shipment_detail.dart index 9116fb7f..34984dfd 100644 --- a/lib/widget/order/so_shipment_detail.dart +++ b/lib/widget/order/so_shipment_detail.dart @@ -8,6 +8,7 @@ import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/api.dart"; import "package:inventree/api_form.dart"; import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/attachment.dart"; import "package:inventree/inventree/sales_order.dart"; import "package:inventree/l10.dart"; import "package:inventree/preferences.dart"; @@ -91,8 +92,11 @@ class _SOShipmentDetailWidgetState }); } - InvenTreeSalesOrderShipmentAttachment() - .countAttachments(widget.shipment.pk) + InvenTreeAttachment() + .countAttachments( + InvenTreeSalesOrderShipment.MODEL_TYPE, + widget.shipment.pk, + ) .then((int value) { if (mounted) { setState(() { @@ -104,8 +108,12 @@ class _SOShipmentDetailWidgetState /// Upload an image for this shipment Future _uploadImage(BuildContext context) async { - InvenTreeSalesOrderShipmentAttachment() - .uploadImage(widget.shipment.pk, prefix: widget.shipment.reference) + InvenTreeAttachment() + .uploadImage( + InvenTreeSalesOrderShipment.MODEL_TYPE, + widget.shipment.pk, + prefix: widget.shipment.reference, + ) .then((result) => refresh(context)); } @@ -339,30 +347,19 @@ class _SOShipmentDetailWidgetState ), ); - // Attachments - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: LinkIcon( - text: attachmentCount > 0 ? attachmentCount.toString() : null, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreeSalesOrderShipmentAttachment(), - widget.shipment.pk, - widget.shipment.reference, - widget.shipment.canEdit, - ), - ), - ); - }, - ), + ListTile? attachmentTile = ShowAttachmentsItem( + context, + InvenTreeSalesOrderShipment.MODEL_TYPE, + widget.shipment.pk, + widget.shipment.reference, + attachmentCount, + widget.shipment.canEdit, ); + if (attachmentTile != null) { + tiles.add(attachmentTile); + } + return tiles; } diff --git a/lib/widget/part/part_detail.dart b/lib/widget/part/part_detail.dart index 98cf1737..30b697c4 100644 --- a/lib/widget/part/part_detail.dart +++ b/lib/widget/part/part_detail.dart @@ -4,6 +4,7 @@ import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/barcode/barcode.dart"; +import "package:inventree/inventree/attachment.dart"; import "package:inventree/l10.dart"; import "package:inventree/helpers.dart"; @@ -212,13 +213,15 @@ class _PartDisplayState extends RefreshableState { } // Request the number of attachments - InvenTreePartAttachment().countAttachments(part.pk).then((int value) { - if (mounted) { - setState(() { - attachmentCount = value; + InvenTreeAttachment() + .countAttachments(InvenTreePart.MODEL_TYPE, part.pk) + .then((int value) { + if (mounted) { + setState(() { + attachmentCount = value; + }); + } }); - } - }); // If show pricing information? if (showPricing) { @@ -596,29 +599,19 @@ class _PartDisplayState extends RefreshableState { ), ); - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: LinkIcon( - text: attachmentCount > 0 ? attachmentCount.toString() : null, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreePartAttachment(), - part.pk, - L10().part, - part.canEdit, - ), - ), - ); - }, - ), + ListTile? attachmentTile = ShowAttachmentsItem( + context, + InvenTreePart.MODEL_TYPE, + part.pk, + L10().part, + attachmentCount, + part.canEdit, ); + if (attachmentTile != null) { + tiles.add(attachmentTile); + } + return tiles; } diff --git a/lib/widget/stock/stock_detail.dart b/lib/widget/stock/stock_detail.dart index db295cac..282a9627 100644 --- a/lib/widget/stock/stock_detail.dart +++ b/lib/widget/stock/stock_detail.dart @@ -7,6 +7,7 @@ import "package:inventree/app_colors.dart"; import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/stock.dart"; import "package:inventree/helpers.dart"; +import "package:inventree/inventree/attachment.dart"; import "package:inventree/inventree/sales_order.dart"; import "package:inventree/l10.dart"; import "package:inventree/api.dart"; @@ -255,15 +256,15 @@ class _StockItemDisplayState extends RefreshableState { } // Request the number of attachments - InvenTreeStockItemAttachment().countAttachments(widget.item.pk).then(( - int value, - ) { - if (mounted) { - setState(() { - attachmentCount = value; + InvenTreeAttachment() + .countAttachments(InvenTreeStockItem.MODEL_TYPE, widget.item.pk) + .then((int value) { + if (mounted) { + setState(() { + attachmentCount = value; + }); + } }); - } - }); // Request SalesOrder information if (widget.item.hasSalesOrder) { @@ -837,29 +838,19 @@ class _StockItemDisplayState extends RefreshableState { ), ); - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: LinkIcon( - text: attachmentCount > 0 ? attachmentCount.toString() : null, - ), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreeStockItemAttachment(), - widget.item.pk, - L10().stockItem, - widget.item.canEdit, - ), - ), - ); - }, - ), + ListTile? attachmentTile = ShowAttachmentsItem( + context, + InvenTreeStockItem.MODEL_TYPE, + widget.item.pk, + L10().stockItem, + attachmentCount, + widget.item.canEdit, ); + if (attachmentTile != null) { + tiles.add(attachmentTile); + } + return tiles; } }