From d7cbe403950e26b623cae059428fcd02373c2f20 Mon Sep 17 00:00:00 2001 From: Iulian Ciorascu Date: Thu, 9 Feb 2023 16:14:50 +0100 Subject: [PATCH 1/5] Download to the public Downloads folder. Removed ununsed permissions. --- android/app/src/debug/AndroidManifest.xml | 10 +++++++--- android/app/src/main/AndroidManifest.xml | 7 ++++--- lib/core/service/file_service.dart | 13 ++++++++++--- .../view/widgets/document_download_button.dart | 9 +++++++++ 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 3c22aad..251e581 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,11 +1,15 @@ + - - - + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8b89b7a..9ba6f3a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ package="com.example.paperless_mobile"> + android:icon="@mipmap/launcher_icon" + android:requestLegacyExternalStorage="true"> - + diff --git a/lib/core/service/file_service.dart b/lib/core/service/file_service.dart index ccf2de6..103db3b 100644 --- a/lib/core/service/file_service.dart +++ b/lib/core/service/file_service.dart @@ -58,9 +58,16 @@ class FileService { static Future get downloadsDirectory async { if (Platform.isAndroid) { - return (await getExternalStorageDirectories( - type: StorageDirectory.downloads))! - .first; + Directory? directory; + directory = Directory('/storage/emulated/0/Download'); + // Try the default global folder, if it exists + if (!await directory.exists()) { + directory = (await getExternalStorageDirectories( + type: StorageDirectory.downloads, + ))! + .first; + } + return directory; } else if (Platform.isIOS) { return getApplicationDocumentsDirectory(); } else { diff --git a/lib/features/document_details/view/widgets/document_download_button.dart b/lib/features/document_details/view/widgets/document_download_button.dart index ccaf894..0e6845d 100644 --- a/lib/features/document_details/view/widgets/document_download_button.dart +++ b/lib/features/document_details/view/widgets/document_download_button.dart @@ -6,8 +6,10 @@ import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; +import 'package:paperless_mobile/helpers/permission_helpers.dart'; import 'package:paperless_mobile/constants.dart'; import 'package:provider/provider.dart'; +import 'package:permission_handler/permission_handler.dart'; class DocumentDownloadButton extends StatefulWidget { final DocumentModel? document; @@ -47,6 +49,13 @@ class _DocumentDownloadButtonState extends State { context, "This feature is currently only supported on Android!"); return; } + if (true) { + // should check for android versions < 30 + final isGranted = await askForPermission(Permission.storage); + if (!isGranted) { + return; + } + } setState(() => _isDownloadPending = true); final service = context.read(); try { From f504c4908c52ad2224c2f099d0034b400354a636 Mon Sep 17 00:00:00 2001 From: Iulian Ciorascu Date: Sat, 11 Feb 2023 00:55:23 +0100 Subject: [PATCH 2/5] Removed the IOS guard for the Download feature. Enabled the UISupportsDocumentBrowser IOS option so that the App Documents are visible for other apps. --- ios/Runner/Info.plist | 4 +++- .../view/widgets/document_download_button.dart | 15 +++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index a837d1a..367aa2e 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -66,6 +66,8 @@ CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents - + + UISupportsDocumentBrowser + diff --git a/lib/features/document_details/view/widgets/document_download_button.dart b/lib/features/document_details/view/widgets/document_download_button.dart index 0e6845d..498d390 100644 --- a/lib/features/document_details/view/widgets/document_download_button.dart +++ b/lib/features/document_details/view/widgets/document_download_button.dart @@ -37,20 +37,19 @@ class _DocumentDownloadButtonState extends State { width: 16, ) : const Icon(Icons.download), - onPressed: Platform.isAndroid && widget.document != null && widget.enabled + onPressed: widget.document != null && widget.enabled ? () => _onDownload(widget.document!) : null, ).paddedOnly(right: 4); } Future _onDownload(DocumentModel document) async { - if (!Platform.isAndroid) { - showSnackBar( - context, "This feature is currently only supported on Android!"); - return; - } - if (true) { - // should check for android versions < 30 + // if (!Platform.isAndroid) { + // showSnackBar( + // context, "This feature is currently only supported on Android!"); + // return; + // } + if (Platform.isAndroid && androidInfo!.version.sdkInt! < 30) { final isGranted = await askForPermission(Permission.storage); if (!isGranted) { return; From 6bbc396039977dd3462c2fa07c363ca0705fdaf2 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord <79228196+astubenbord@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:32:59 +0100 Subject: [PATCH 3/5] Update build and install instructions Adapt build instructions to the recently added flutter submodule --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3e4e1a5..9df5a4b 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,6 @@ With this app you can conveniently add, manage or simply find documents stored i To get a local copy up and running follow these simple steps. ### Prerequisites - -* Install the Flutter SDK (https://docs.flutter.dev/get-started/install) * Install an IDE of your choice (e.g. VSCode with the Dart/Flutter extensions) ### Install dependencies and generate files @@ -83,11 +81,18 @@ To get a local copy up and running follow these simple steps. ```sh git clone https://github.com/astubenbord/paperless-mobile.git ``` - -You can now either run the `install_dependencies.sh` script at the root of the project, which will automatically install dependencies and generate code for both the app and subpackages, or you can manually run the following steps: +In this project, flutter is pinned at a specific version as a git submodule to ensure all contributors work with the same environment and build with the same flutter version. You can also use your local flutter installation, just make sure that the app also compiles with the same flutter version as pinned in the `flutter` submodule when opening a pull request. + +To download the pinned flutter SDK from the submodule and plan to install the dependencies manually in the next step, simply run +```sh +git submodule update --init +``` + +You can now run the `scripts/install_dependencies.sh` script at the root of the project, which will automatically install dependencies and generate files for both the app and subpackages. Note that the `install_dependencies.sh` script will pull the flutter submodule and use the SDK to execute the flutter commands. + +If you don't want to use submodules, you can also run the following commands using your local flutter installation: #### Inside the `packages/paperless_api/` folder: - 2. Install the dependencies for `paperless_api` ```sh flutter pub get @@ -129,6 +134,8 @@ buildTypes { } } ``` +or use your own signing configuration as described in https://docs.flutter.dev/deployment/android#signing-the-app and leave the `build.gradle` as is. + 2. Build the app with release profile (here for android): ```sh flutter build apk From 42dbaaf85567efa89b472b8477a9ce00140a0c1b Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Tue, 14 Feb 2023 21:02:51 +0100 Subject: [PATCH 4/5] Fix manifest issue, update download function and original file download support --- android/app/src/debug/AndroidManifest.xml | 3 +- lib/core/service/file_service.dart | 18 +++--- .../view/pages/document_details_page.dart | 1 + .../widgets/document_download_button.dart | 56 ++++++++++++++----- .../paperless_documents_api.dart | 2 +- .../paperless_documents_api_impl.dart | 6 +- pubspec.lock | 42 +++++++------- 7 files changed, 79 insertions(+), 49 deletions(-) diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 251e581..0728cde 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -9,7 +9,6 @@ + android:maxSdkVersion="29"/> diff --git a/lib/core/service/file_service.dart b/lib/core/service/file_service.dart index 103db3b..3fe6265 100644 --- a/lib/core/service/file_service.dart +++ b/lib/core/service/file_service.dart @@ -58,14 +58,12 @@ class FileService { static Future get downloadsDirectory async { if (Platform.isAndroid) { - Directory? directory; - directory = Directory('/storage/emulated/0/Download'); - // Try the default global folder, if it exists - if (!await directory.exists()) { - directory = (await getExternalStorageDirectories( + Directory directory = Directory('/storage/emulated/0/Download'); + if (!directory.existsSync()) { + final downloadsDir = await getExternalStorageDirectories( type: StorageDirectory.downloads, - ))! - .first; + ); + directory = downloadsDir!.first; } return directory; } else if (Platform.isIOS) { @@ -77,8 +75,10 @@ class FileService { static Future get scanDirectory async { if (Platform.isAndroid) { - return (await getExternalStorageDirectories(type: StorageDirectory.dcim))! - .first; + final scanDir = await getExternalStorageDirectories( + type: StorageDirectory.dcim, + ); + return scanDir!.first; } else if (Platform.isIOS) { return getApplicationDocumentsDirectory(); } else { diff --git a/lib/features/document_details/view/pages/document_details_page.dart b/lib/features/document_details/view/pages/document_details_page.dart index 4661920..fa68612 100644 --- a/lib/features/document_details/view/pages/document_details_page.dart +++ b/lib/features/document_details/view/pages/document_details_page.dart @@ -231,6 +231,7 @@ class _DocumentDetailsPageState extends State { child: DocumentDownloadButton( document: state.document, enabled: isConnected, + metaData: _metaData, ), ), IconButton( diff --git a/lib/features/document_details/view/widgets/document_download_button.dart b/lib/features/document_details/view/widgets/document_download_button.dart index 498d390..bc68e42 100644 --- a/lib/features/document_details/view/widgets/document_download_button.dart +++ b/lib/features/document_details/view/widgets/document_download_button.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; +import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart'; import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/permission_helpers.dart'; @@ -14,10 +15,12 @@ import 'package:permission_handler/permission_handler.dart'; class DocumentDownloadButton extends StatefulWidget { final DocumentModel? document; final bool enabled; + final Future metaData; const DocumentDownloadButton({ super.key, required this.document, this.enabled = true, + required this.metaData, }); @override @@ -44,24 +47,47 @@ class _DocumentDownloadButtonState extends State { } Future _onDownload(DocumentModel document) async { - // if (!Platform.isAndroid) { - // showSnackBar( - // context, "This feature is currently only supported on Android!"); - // return; - // } - if (Platform.isAndroid && androidInfo!.version.sdkInt! < 30) { - final isGranted = await askForPermission(Permission.storage); - if (!isGranted) { + final api = context.read(); + final meta = await widget.metaData; + try { + final downloadOriginal = await showDialog( + context: context, + builder: (context) => RadioSettingsDialog( + titleText: "Choose filetype", //TODO: INTL + options: [ + RadioOption( + value: true, + label: + "Original (${meta.originalMimeType.split("/").last})", //TODO: INTL + ), + RadioOption( + value: false, + label: "Archived (pdf)", //TODO: INTL + ), + ], + initialValue: false, + ), + ); + if (downloadOriginal == null) { + // Download was cancelled return; } - } - setState(() => _isDownloadPending = true); - final service = context.read(); - try { - final bytes = await service.download(document); - final meta = await service.getMetaData(document); + if (Platform.isAndroid && androidInfo!.version.sdkInt! < 30) { + final isGranted = await askForPermission(Permission.storage); + if (!isGranted) { + return; + } + } + setState(() => _isDownloadPending = true); + final bytes = await api.download( + document, + original: downloadOriginal, + ); final Directory dir = await FileService.downloadsDirectory; - String filePath = "${dir.path}/${meta.mediaFilename}"; + final fileExtension = + downloadOriginal ? meta.mediaFilename.split(".").last : 'pdf'; + String filePath = "${dir.path}/${meta.mediaFilename}".split(".").first; + filePath += ".$fileExtension"; final createdFile = File(filePath); createdFile.createSync(recursive: true); createdFile.writeAsBytesSync(bytes); diff --git a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart index aabd746..ce6f8cf 100644 --- a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart +++ b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart @@ -23,7 +23,7 @@ abstract class PaperlessDocumentsApi { Future> bulkAction(BulkAction action); Future getPreview(int docId); String getThumbnailUrl(int docId); - Future download(DocumentModel document); + Future download(DocumentModel document, {bool original}); Future findSuggestions(DocumentModel document); Future> autocomplete(String query, [int limit = 10]); diff --git a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart index 0536ddb..c5f9fea 100644 --- a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart +++ b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart @@ -196,10 +196,14 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi { } @override - Future download(DocumentModel document) async { + Future download( + DocumentModel document, { + bool original = false, + }) async { try { final response = await client.get( "/api/documents/${document.id}/download/", + queryParameters: original ? {'original': true} : {}, options: Options(responseType: ResponseType.bytes), ); return response.data; diff --git a/pubspec.lock b/pubspec.lock index eb5bbcc..f7cdcaa 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -245,10 +245,10 @@ packages: dependency: "direct main" description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.0" connectivity_plus: dependency: "direct main" description: @@ -897,10 +897,10 @@ packages: dependency: transitive description: name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.6.5" json_annotation: dependency: "direct main" description: @@ -977,10 +977,10 @@ packages: dependency: transitive description: name: matcher - sha256: c94db23593b89766cda57aab9ac311e3616cf87c6fa4e9749df032f66f30dcb8 + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" url: "https://pub.dev" source: hosted - version: "0.12.14" + version: "0.12.13" material_color_utilities: dependency: transitive description: @@ -993,10 +993,10 @@ packages: dependency: transitive description: name: meta - sha256: "12307e7f0605ce3da64cf0db90e5fcab0869f3ca03f76be6bb2991ce0a55e82b" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.8.0" mime: dependency: "direct main" description: @@ -1128,10 +1128,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.8.2" path_drawing: dependency: transitive description: @@ -1621,26 +1621,26 @@ packages: dependency: transitive description: name: test - sha256: b54d427664c00f2013ffb87797a698883c46aee9288e027a50b46eaee7486fa2 + sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d url: "https://pub.dev" source: hosted - version: "1.22.2" + version: "1.22.0" test_api: dependency: transitive description: name: test_api - sha256: "6182294da5abf431177fccc1ee02401f6df30f766bc6130a0852c6b6d7ee6b2d" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 url: "https://pub.dev" source: hosted - version: "0.4.18" + version: "0.4.16" test_core: dependency: transitive description: name: test_core - sha256: "95ecc12692d0dd59080ab2d38d9cf32c7e9844caba23ff6cd285690398ee8ef4" + sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" url: "https://pub.dev" source: hosted - version: "0.4.22" + version: "0.4.20" timezone: dependency: transitive description: @@ -1765,10 +1765,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "2277c73618916ae3c2082b6df67b6ebb64b4c69d9bf23b23700707952ac30e60" + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "9.4.0" watcher: dependency: transitive description: @@ -1789,10 +1789,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" webkit_inspection_protocol: dependency: transitive description: @@ -1834,5 +1834,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.4.0-17.0.pre" From ae31f59aa13c205a2a6af553f8a85f228bff23d9 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Tue, 14 Feb 2023 22:46:26 +0100 Subject: [PATCH 5/5] Improve created app file system structure on ios --- lib/core/service/file_service.dart | 15 ++++++++++++--- .../view/widgets/document_download_button.dart | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/core/service/file_service.dart b/lib/core/service/file_service.dart index 3fe6265..06ba344 100644 --- a/lib/core/service/file_service.dart +++ b/lib/core/service/file_service.dart @@ -50,7 +50,10 @@ class FileService { ))! .first; } else if (Platform.isIOS) { - return getApplicationDocumentsDirectory(); + final appDir = await getApplicationDocumentsDirectory(); + final dir = Directory('${appDir.path}/documents'); + dir.createSync(); + return dir; } else { throw UnsupportedError("Platform not supported."); } @@ -67,7 +70,10 @@ class FileService { } return directory; } else if (Platform.isIOS) { - return getApplicationDocumentsDirectory(); + final appDir = await getApplicationDocumentsDirectory(); + final dir = Directory('${appDir.path}/downloads'); + dir.createSync(); + return dir; } else { throw UnsupportedError("Platform not supported."); } @@ -80,7 +86,10 @@ class FileService { ); return scanDir!.first; } else if (Platform.isIOS) { - return getApplicationDocumentsDirectory(); + final appDir = await getApplicationDocumentsDirectory(); + final dir = Directory('${appDir.path}/scans'); + dir.createSync(); + return dir; } else { throw UnsupportedError("Platform not supported."); } diff --git a/lib/features/document_details/view/widgets/document_download_button.dart b/lib/features/document_details/view/widgets/document_download_button.dart index bc68e42..a024927 100644 --- a/lib/features/document_details/view/widgets/document_download_button.dart +++ b/lib/features/document_details/view/widgets/document_download_button.dart @@ -91,6 +91,7 @@ class _DocumentDownloadButtonState extends State { final createdFile = File(filePath); createdFile.createSync(recursive: true); createdFile.writeAsBytesSync(bytes); + debugPrint("Downloaded file to $filePath"); showSnackBar(context, S.of(context).documentDownloadSuccessMessage); } on PaperlessServerException catch (error, stackTrace) { showErrorMessage(context, error, stackTrace);