diff --git a/android/app/build.gradle b/android/app/build.gradle index a339628..474c2f0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/android/build.gradle b/android/build.gradle index d77ed1c..48a5fe9 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.0' repositories { google() mavenCentral() diff --git a/lib/features/documents/view/pages/document_details_page.dart b/lib/features/documents/view/pages/document_details_page.dart index 424b289..9223188 100644 --- a/lib/features/documents/view/pages/document_details_page.dart +++ b/lib/features/documents/view/pages/document_details_page.dart @@ -1,6 +1,8 @@ +import 'dart:developer' as dev; import 'dart:io'; import 'dart:math'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_paperless_mobile/core/bloc/label_bloc_provider.dart'; @@ -26,6 +28,7 @@ import 'package:flutter_paperless_mobile/generated/l10n.dart'; import 'package:flutter_paperless_mobile/util.dart'; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:share_plus/share_plus.dart'; class DocumentDetailsPage extends StatefulWidget { final int documentId; @@ -73,12 +76,16 @@ class _DocumentDetailsPageState extends State { ).padded(const EdgeInsets.symmetric(horizontal: 8.0)), IconButton( icon: const Icon(Icons.download), - onPressed: null, //() => _onDownload(document), //TODO: FIX + onPressed: Platform.isAndroid ? () => _onDownload(document) : null, ), IconButton( icon: const Icon(Icons.open_in_new), onPressed: () => _onOpen(document), ).padded(const EdgeInsets.symmetric(horizontal: 8.0)), + IconButton( + icon: const Icon(Icons.share), + onPressed: () => _onShare(document), + ), ], ), ), @@ -313,31 +320,43 @@ class _DocumentDetailsPageState extends State { } Future _onDownload(DocumentModel document) async { - setState(() { - _isDownloadPending = true; - }); + if (!Platform.isAndroid) { + showSnackBar(context, "This feature is currently only supported on Android!"); + return; + } + setState(() => _isDownloadPending = true); getIt().download(document).then((bytes) async { - //FIXME: logic currently flawed, some error somewhere but cannot look into directory... - final dir = await getApplicationDocumentsDirectory(); - final dirPath = dir.path + "/files/"; - var filePath = dirPath + document.originalFileName; - - if (File(filePath).existsSync()) { - final count = dir - .listSync() - .where((entity) => (entity.path.contains(document.originalFileName))) - .fold(0, (previous, element) => previous + 1); - final extSeperationIdx = filePath.lastIndexOf("."); - filePath = - filePath.replaceRange(extSeperationIdx, extSeperationIdx + 1, " (${count + 1})."); - } - Directory(dirPath).createSync(); + final Directory dir = + (await getExternalStorageDirectories(type: StorageDirectory.downloads))!.first; + String filePath = "${dir.path}/${document.originalFileName}"; + //TODO: Add replacement mechanism here (ask user if file should be replaced if exists) await File(filePath).writeAsBytes(bytes); - _isDownloadPending = false; - showSnackBar(context, "Document successfully downloaded to $filePath"); //TODO: INTL + setState(() => _isDownloadPending = false); + dev.log("File downloaded to $filePath"); }); } + /// + /// Downloads file to temporary directory, from which it can then be shared. + /// + Future _onShare(DocumentModel document) async { + Uint8List documentBytes = await getIt().download(document); + final dir = await getTemporaryDirectory(); + final String path = "${dir.path}/${document.originalFileName}"; + await File(path).writeAsBytes(documentBytes); + Share.shareXFiles( + [ + XFile( + path, + name: document.originalFileName, + mimeType: "application/pdf", + lastModified: document.modified, + ) + ], + subject: document.title, + ); + } + Future _onDelete(DocumentModel document) async { showDialog( context: context, diff --git a/lib/main.dart b/lib/main.dart index 6118d55..5adc6e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,6 +20,7 @@ import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:intl/intl.dart'; import 'package:intl/intl_standalone.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:path_provider/path_provider.dart'; void main() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); @@ -30,6 +31,8 @@ void main() async { HttpOverrides.global = X509HttpOverrides(); configureDependencies(); + // Remove temporarily downloaded files. + (await getTemporaryDirectory()).deleteSync(recursive: true); kPackageInfo = await PackageInfo.fromPlatform(); // Load application settings and stored authentication data diff --git a/pubspec.lock b/pubspec.lock index 02cb707..1e817b5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -246,6 +246,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.0" + cross_file: + dependency: transitive + description: + name: cross_file + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3+2" crypto: dependency: transitive description: @@ -898,7 +905,7 @@ packages: name: package_info_plus_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" path: dependency: transitive description: @@ -926,7 +933,7 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.10" + version: "2.0.11" path_provider_android: dependency: transitive description: @@ -968,7 +975,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" pdf: dependency: "direct main" description: @@ -1109,6 +1116,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.27.4" + share_plus: + dependency: "direct main" + description: + name: share_plus + url: "https://pub.dartlang.org" + source: hosted + version: "6.2.0" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" shared_preferences: dependency: transitive description: @@ -1407,14 +1428,14 @@ packages: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.1" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.13" url_launcher_windows: dependency: transitive description: @@ -1477,7 +1498,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.7.0" + version: "3.0.1" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 399ee4b..1c48cdf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.0.1 +version: 1.0.2 environment: sdk: ">=2.17.0 <3.0.0" @@ -72,6 +72,7 @@ dependencies: connectivity_plus: ^2.3.9 flutter_native_splash: ^2.2.11 + share_plus: ^6.2.0 dev_dependencies: integration_test: