From 5a63ca25138dfc17729016def04e6f67883a14fb Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Thu, 10 Nov 2022 23:21:01 +0100 Subject: [PATCH] Added possibility to upload documents from file system --- lib/core/model/error_message.dart | 3 +- .../scan/view/document_upload_page.dart | 7 +- lib/features/scan/view/scanner_page.dart | 103 +++++++++++++++--- lib/l10n/intl_de.arb | 4 + lib/l10n/intl_en.arb | 3 + pubspec.lock | 2 +- pubspec.yaml | 1 + 7 files changed, 101 insertions(+), 22 deletions(-) diff --git a/lib/core/model/error_message.dart b/lib/core/model/error_message.dart index d602c7b..dbdc392 100644 --- a/lib/core/model/error_message.dart +++ b/lib/core/model/error_message.dart @@ -46,5 +46,6 @@ enum ErrorCode { createSavedViewError, deleteSavedViewError, requestTimedOut, - storagePathAlreadyExists; + storagePathAlreadyExists, + unsupportedFileFormat; } diff --git a/lib/features/scan/view/document_upload_page.dart b/lib/features/scan/view/document_upload_page.dart index 68ea7f5..908335b 100644 --- a/lib/features/scan/view/document_upload_page.dart +++ b/lib/features/scan/view/document_upload_page.dart @@ -29,10 +29,11 @@ import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart'; class DocumentUploadPage extends StatefulWidget { - final Uint8List pdfBytes; + final Uint8List fileBytes; + const DocumentUploadPage({ Key? key, - required this.pdfBytes, + required this.fileBytes, }) : super(key: key); @override @@ -190,7 +191,7 @@ class _DocumentUploadPageState extends State { _isUploadLoading = true; }); await BlocProvider.of(context).addDocument( - widget.pdfBytes, + widget.fileBytes, _formKey.currentState?.value[fkFileName], onConsumptionFinished: (document) { ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar( diff --git a/lib/features/scan/view/scanner_page.dart b/lib/features/scan/view/scanner_page.dart index 6170b73..7230b16 100644 --- a/lib/features/scan/view/scanner_page.dart +++ b/lib/features/scan/view/scanner_page.dart @@ -2,9 +2,13 @@ import 'dart:io'; import 'dart:math'; import 'package:edge_detection/edge_detection.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:mime/mime.dart'; import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; +import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart'; @@ -12,6 +16,7 @@ import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart' import 'package:paperless_mobile/features/scan/view/document_upload_page.dart'; import 'package:paperless_mobile/features/scan/view/widgets/grid_image_item_widget.dart'; import 'package:paperless_mobile/generated/l10n.dart'; +import 'package:paperless_mobile/util.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:permission_handler/permission_handler.dart'; @@ -25,6 +30,14 @@ class ScannerPage extends StatefulWidget { class _ScannerPageState extends State with SingleTickerProviderStateMixin { + static const _supportedExtensions = [ + 'pdf', + 'png', + 'tiff', + 'gif', + 'jpg', + 'jpeg' + ]; late final AnimationController _fabPulsingController; late final Animation _animation; @override @@ -109,18 +122,8 @@ class _ScannerPageState extends State } void _export(BuildContext context) async { - final pw.Document doc = pw.Document(); - - for (var element in BlocProvider.of(context).state) { - final img = pw.MemoryImage(element.readAsBytesSync()); - doc.addPage( - pw.Page( - pageFormat: - PdfPageFormat(img.width!.toDouble(), img.height!.toDouble()), - build: (context) => pw.Image(img), - ), - ); - } + final doc = _buildDocumentFromImageFiles( + BlocProvider.of(context).state); final bytes = await doc.save(); Navigator.of(context).push( MaterialPageRoute( @@ -128,7 +131,7 @@ class _ScannerPageState extends State value: getIt(), child: LabelBlocProvider( child: DocumentUploadPage( - pdfBytes: bytes, + fileBytes: bytes, ), ), ), @@ -144,10 +147,27 @@ class _ScannerPageState extends State } return Center( child: Padding( - padding: EdgeInsets.all(8.0), - child: Text( - S.of(context).documentScannerPageEmptyStateText, - textAlign: TextAlign.center, + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + S.of(context).documentScannerPageEmptyStateText, + textAlign: TextAlign.center, + ), + TextButton( + child: + Text(S.of(context).documentScannerPageAddScanButtonLabel), + onPressed: () => _openDocumentScanner(context), + ), + Text(S.of(context).documentScannerPageOrText), + TextButton( + child: Text(S + .of(context) + .documentScannerPageUploadFromThisDeviceButtonLabel), + onPressed: _onUploadFromFilesystem, + ), + ], ), ), ); @@ -185,4 +205,53 @@ class _ScannerPageState extends State Permission.camera.request(); } } + + void _onUploadFromFilesystem() async { + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: _supportedExtensions, + withData: true, + ); + if (result?.files.single.path != null) { + File file = File(result!.files.single.path!); + + final mimeType = lookupMimeType(file.path) ?? ''; + late Uint8List fileBytes; + if (mimeType.startsWith('image')) { + fileBytes = await _buildDocumentFromImageFiles([file]).save(); + } else { + // pdf + fileBytes = file.readAsBytesSync(); + } + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlocProvider.value( + value: getIt(), + child: LabelBlocProvider( + child: DocumentUploadPage( + fileBytes: fileBytes, + ), + ), + ), + ), + ); + } + } + + pw.Document _buildDocumentFromImageFiles(List files) { + final doc = pw.Document(); + for (final file in files) { + final img = pw.MemoryImage(file.readAsBytesSync()); + doc.addPage( + pw.Page( + pageFormat: + PdfPageFormat(img.width!.toDouble(), img.height!.toDouble()), + build: (context) => pw.Image(img), + ), + ); + } + return doc; + } } diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 63ed183..8dd620d 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -102,6 +102,9 @@ "documentScannerPageTitle": "Scanner", "documentScannerPageResetButtonTooltipText": "Alle scans löschen", "documentScannerPageUploadButtonTooltip": "Dokument hochladen", + "documentScannerPageAddScanButtonLabel": "Scanne ein Dokument", + "documentScannerPageOrText": "oder", + "documentScannerPageUploadFromThisDeviceButtonLabel": "Lade ein Dokument von diesem Gerät hoch", "addTagPageTitle": "Neuer Tag", "addCorrespondentPageTitle": "Neuer Korrespondent", "addDocumentTypePageTitle": "Neuer Dokumententyp", @@ -182,4 +185,5 @@ "referencedDocumentsReadOnlyHintText": "Dies ist eine schreibgeschützte Ansicht! Dokumente können nicht bearbeitet oder entfernt werden.", "editLabelPageConfirmDeletionDialogTitle": "Löschen bestätigen", "editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?" + } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 62cfb06..9ef16d8 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -101,6 +101,9 @@ "documentScannerPageTitle": "Scan", "documentScannerPageResetButtonTooltipText": "Delete all scans", "documentScannerPageUploadButtonTooltip": "Upload to Paperless", + "documentScannerPageAddScanButtonLabel": "Scan a document", + "documentScannerPageOrText": "or", + "documentScannerPageUploadFromThisDeviceButtonLabel": "Upload a document from this device", "addTagPageTitle": "New Tag", "addCorrespondentPageTitle": "New Correspondent", "addDocumentTypePageTitle": "New Document Type", diff --git a/pubspec.lock b/pubspec.lock index 7a6de6e..351e782 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -823,7 +823,7 @@ packages: source: hosted version: "1.8.0" mime: - dependency: transitive + dependency: "direct main" description: name: mime url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 59b3bf3..645e9d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,7 @@ dependencies: flutter_native_splash: ^2.2.11 share_plus: ^6.2.0 introduction_screen: ^3.0.2 + mime: ^1.0.2 dev_dependencies: integration_test: