mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-30 21:05:42 +00:00 
			
		
		
		
	* Add BUILDING.md * Replace scaning library - Out with qr_code_scanner - In with flutter_zxing * Update specs for jdk / kotlin / gradle - NFI what this all means? * Refactor barcode scanning widget * Refactor barcode overlay * Add handlers * Update release notes * Fix AppBar color * Enhance attachment widget * remove unused import * Improved icon * Select theme from main drawer
		
			
				
	
	
		
			219 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| 
 | |
| import "dart:io";
 | |
| 
 | |
| import "package:flutter/material.dart";
 | |
| import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
 | |
| import "package:one_context/one_context.dart";
 | |
| import "package:url_launcher/url_launcher.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";
 | |
| import "package:inventree/widget/refreshable_state.dart";
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * A generic widget for displaying a list of attachments.
 | |
|  *
 | |
|  * To allow use with different "types" of attachments,
 | |
|  * we pass a subclassed instance of the InvenTreeAttachment model.
 | |
|  */
 | |
| class AttachmentWidget extends StatefulWidget {
 | |
| 
 | |
|   const AttachmentWidget(this.attachmentClass, this.modelId, this.imagePrefix, this.hasUploadPermission) : super();
 | |
| 
 | |
|   final InvenTreeAttachment attachmentClass;
 | |
|   final int modelId;
 | |
|   final bool hasUploadPermission;
 | |
|   final String imagePrefix;
 | |
| 
 | |
|   @override
 | |
|   _AttachmentWidgetState createState() => _AttachmentWidgetState();
 | |
| }
 | |
| 
 | |
| 
 | |
| class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
 | |
| 
 | |
|   _AttachmentWidgetState();
 | |
| 
 | |
|   List<InvenTreeAttachment> attachments = [];
 | |
| 
 | |
|   @override
 | |
|   String getAppBarTitle() => L10().attachments;
 | |
| 
 | |
|   @override
 | |
|   List<Widget> appBarActions(BuildContext context) {
 | |
|     if (!widget.hasUploadPermission) return [];
 | |
| 
 | |
|     return [
 | |
|       IconButton(
 | |
|         icon: Icon(TablerIcons.camera),
 | |
|         onPressed: () async {
 | |
|           widget.attachmentClass.uploadImage(
 | |
|             widget.modelId,
 | |
|             prefix: widget.imagePrefix,
 | |
|           );
 | |
|           FilePickerDialog.pickImageFromCamera().then((File? file) {
 | |
|             upload(context, file);
 | |
|           });
 | |
|         }
 | |
|       ),
 | |
|       IconButton(
 | |
|         icon: Icon(TablerIcons.file_upload),
 | |
|         onPressed: () async {
 | |
|           FilePickerDialog.pickFileFromDevice().then((File? file) {
 | |
|             upload(context, file);
 | |
|           });
 | |
|         }
 | |
|       )
 | |
|     ];
 | |
|   }
 | |
| 
 | |
|   Future<void> upload(BuildContext context, File? file) async {
 | |
| 
 | |
|     if (file == null) return;
 | |
| 
 | |
|     showLoadingOverlay(context);
 | |
| 
 | |
|     final bool result = await widget.attachmentClass.uploadAttachment(
 | |
|         file,
 | |
|         widget.modelId
 | |
|     );
 | |
| 
 | |
|     hideLoadingOverlay();
 | |
| 
 | |
|     if (result) {
 | |
|       showSnackIcon(L10().uploadSuccess, success: true);
 | |
|     } else {
 | |
|       showSnackIcon(L10().uploadFailed, success: false);
 | |
|     }
 | |
| 
 | |
|     refresh(context);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    * Delete the specified attachment
 | |
|    */
 | |
|   Future<void> deleteAttachment(BuildContext context, InvenTreeAttachment attachment) async {
 | |
| 
 | |
|     final bool result = await attachment.delete();
 | |
| 
 | |
|     showSnackIcon(
 | |
|       result ? L10().deleteSuccess : L10().deleteFailed,
 | |
|       success: result
 | |
|     );
 | |
| 
 | |
|     refresh(context);
 | |
| 
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    * Display an option context menu for the selected attachment
 | |
|    */
 | |
|   Future<void> showOptionsMenu(BuildContext context, InvenTreeAttachment attachment) async {
 | |
|     OneContext().showDialog(
 | |
|       builder: (BuildContext ctx) {
 | |
|         return SimpleDialog(
 | |
|           title: Text(L10().attachments),
 | |
|           children: [
 | |
|             Divider(),
 | |
|             SimpleDialogOption(
 | |
|               onPressed: () async {
 | |
|                 Navigator.of(ctx).pop();
 | |
|                 deleteAttachment(context, attachment);
 | |
|               },
 | |
|               child: ListTile(
 | |
|                 title: Text(L10().delete),
 | |
|                 leading: Icon(TablerIcons.trash),
 | |
|               )
 | |
|             )
 | |
|           ]
 | |
|         );
 | |
|       }
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   Future<void> request(BuildContext context) async {
 | |
| 
 | |
|     Map<String, String> 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();
 | |
|     }
 | |
| 
 | |
|     await widget.attachmentClass.list(
 | |
|       filters: filters
 | |
|     ).then((var results) {
 | |
|       attachments.clear();
 | |
| 
 | |
|       for (var result in results) {
 | |
|         if (result is InvenTreeAttachment) {
 | |
|           attachments.add(result);
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   @override
 | |
|   List<Widget> getTiles(BuildContext context) {
 | |
| 
 | |
|     List<Widget> tiles = [];
 | |
| 
 | |
|     // An "attachment" can either be a file, or a URL
 | |
|     for (var attachment in attachments) {
 | |
| 
 | |
|       if (attachment.filename.isNotEmpty) {
 | |
|         tiles.add(ListTile(
 | |
|           title: Text(attachment.filename),
 | |
|           subtitle: Text(attachment.comment),
 | |
|           leading: Icon(attachment.icon, color: COLOR_ACTION),
 | |
|           onTap: () async {
 | |
|             showLoadingOverlay(context);
 | |
|             await attachment.downloadAttachment();
 | |
|             hideLoadingOverlay();
 | |
|           },
 | |
|           onLongPress: ()  {
 | |
|             showOptionsMenu(context, attachment);
 | |
|           },
 | |
|         ));
 | |
|       }
 | |
| 
 | |
|       else if (attachment.link.isNotEmpty) {
 | |
|         tiles.add(ListTile(
 | |
|           title: Text(attachment.link),
 | |
|           subtitle: Text(attachment.comment),
 | |
|           leading: Icon(TablerIcons.link, color: COLOR_ACTION),
 | |
|           onTap: () async {
 | |
|             var uri = Uri.tryParse(attachment.link.trimLeft());
 | |
|             if (uri != null && await canLaunchUrl(uri)) {
 | |
|               await launchUrl(uri);
 | |
|             }
 | |
|           },
 | |
|           onLongPress: ()  {
 | |
|             showOptionsMenu(context, attachment);
 | |
|           },
 | |
|         ));
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (tiles.isEmpty) {
 | |
|       tiles.add(ListTile(
 | |
|         leading: Icon(TablerIcons.file_x, color: COLOR_WARNING),
 | |
|         title: Text(L10().attachmentNone),
 | |
|       ));
 | |
|     }
 | |
| 
 | |
|     return tiles;
 | |
|   }
 | |
| }
 |