diff --git a/lib/redux/document/document_actions.dart b/lib/redux/document/document_actions.dart index 9ae19f112..70e3ca54c 100644 --- a/lib/redux/document/document_actions.dart +++ b/lib/redux/document/document_actions.dart @@ -34,9 +34,9 @@ import 'package:invoiceninja_flutter/redux/vendor/vendor_actions.dart'; import 'package:invoiceninja_flutter/ui/app/entities/entity_actions_dialog.dart'; import 'package:invoiceninja_flutter/utils/completers.dart'; import 'package:invoiceninja_flutter/utils/dialogs.dart'; +import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:pinch_zoom/pinch_zoom.dart'; import 'package:printing/printing.dart'; @@ -321,7 +321,7 @@ void handleDocumentAction( } final store = StoreProvider.of(context!); - final localization = AppLocalization.of(context); + final localization = AppLocalization.of(context)!; final documentIds = documents.map((document) => document.id).toList(); final document = store.state.documentState.map[documentIds.first]!; @@ -331,19 +331,19 @@ void handleDocumentAction( break; case EntityAction.restore: final message = documentIds.length > 1 - ? localization!.restoredDocuments + ? localization.restoredDocuments .replaceFirst(':value', ':count') .replaceFirst(':count', documentIds.length.toString()) - : localization!.restoredDocument; + : localization.restoredDocument; store.dispatch(RestoreDocumentRequest( snackBarCompleter(message), documentIds)); break; case EntityAction.archive: final message = documentIds.length > 1 - ? localization!.archivedDocuments + ? localization.archivedDocuments .replaceFirst(':value', ':count') .replaceFirst(':count', documentIds.length.toString()) - : localization!.archivedDocument; + : localization.archivedDocument; store.dispatch(ArchiveDocumentRequest( snackBarCompleter(message), documentIds)); break; @@ -386,7 +386,7 @@ void handleDocumentAction( DownloadDocumentsRequest( documentIds: documentIds, completer: snackBarCompleter( - localization!.exportedData, + localization.exportedData, ), ), ); @@ -402,7 +402,7 @@ void handleDocumentAction( actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), - child: Text(localization!.close.toUpperCase())), + child: Text(localization.close.toUpperCase())), ], content: document.isImage ? PinchZoom( @@ -440,27 +440,26 @@ void handleDocumentAction( WebUtils.downloadBinaryFile(document!.name, document.data!); } } else { - final directory = await (isDesktopOS() - ? getDownloadsDirectory() as FutureOr - : getApplicationDocumentsDirectory()); + final directory = await getAppDownloadDirectory(); + if (directory != null) { + String filePath = + '$directory/${file.Platform.pathSeparator}${document!.name}'; - String filePath = - '${directory.path}${file.Platform.pathSeparator}${document!.name}'; + if (file.File(filePath).existsSync()) { + final extension = document.name.split('.').last; + final timestamp = DateTime.now().millisecondsSinceEpoch; + filePath = filePath.replaceFirst( + '.$extension', '_$timestamp.$extension'); + } - if (file.File(filePath).existsSync()) { - final extension = document.name.split('.').last; - final timestamp = DateTime.now().millisecondsSinceEpoch; - filePath = - filePath.replaceFirst('.$extension', '_$timestamp.$extension'); - } + await File(filePath).writeAsBytes(document.data!); - await File(filePath).writeAsBytes(document.data!); - - if (isDesktopOS()) { - showToast(localization!.fileSavedInPath - .replaceFirst(':path', directory.path)); - } else { - await Share.shareXFiles([XFile(filePath)]); + if (isDesktopOS()) { + showToast(localization.fileSavedInPath + .replaceFirst(':path', directory)); + } else { + await Share.shareXFiles([XFile(filePath)]); + } } } } diff --git a/lib/ui/client/client_pdf.dart b/lib/ui/client/client_pdf.dart index 4893dec82..01971eda9 100644 --- a/lib/ui/client/client_pdf.dart +++ b/lib/ui/client/client_pdf.dart @@ -21,8 +21,8 @@ import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart'; import 'package:invoiceninja_flutter/ui/app/forms/date_picker.dart'; import 'package:invoiceninja_flutter/ui/app/multiselect.dart'; +import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:invoiceninja_flutter/utils/formatting.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:printing/printing.dart'; // Project imports: @@ -352,16 +352,14 @@ class _ClientPdfViewState extends State { WebUtils.downloadBinaryFile( fileName, _response!.bodyBytes); } else { - final directory = await (isDesktopOS() - ? getDownloadsDirectory() - : getApplicationDocumentsDirectory()); + final directory = await getAppDownloadDirectory(); if (directory == null) { return; } String filePath = - '${directory.path}${file.Platform.pathSeparator}$fileName'; + '$directory${file.Platform.pathSeparator}$fileName'; if (file.File(filePath).existsSync()) { final timestamp = @@ -375,7 +373,7 @@ class _ClientPdfViewState extends State { if (isDesktopOS()) { showToast(localization.fileSavedInPath - .replaceFirst(':path', directory.path)); + .replaceFirst(':path', directory)); } else { await Share.shareXFiles([XFile(filePath)]); } diff --git a/lib/ui/invoice/invoice_pdf.dart b/lib/ui/invoice/invoice_pdf.dart index 5125ef460..89c3ff9b0 100644 --- a/lib/ui/invoice/invoice_pdf.dart +++ b/lib/ui/invoice/invoice_pdf.dart @@ -14,7 +14,7 @@ import 'package:http/http.dart' as http; import 'package:http/http.dart'; import 'package:invoiceninja_flutter/main_app.dart'; import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart'; -import 'package:path_provider/path_provider.dart'; +import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:printing/printing.dart'; import 'package:share_plus/share_plus.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -269,16 +269,15 @@ class _InvoicePdfViewState extends State { WebUtils.downloadBinaryFile( fileName, _response!.bodyBytes); } else { - final directory = await (isDesktopOS() - ? getDownloadsDirectory() - : getApplicationDocumentsDirectory()); + final directory = + await getAppDownloadDirectory(); if (directory == null) { return; } String filePath = - '${directory.path}${file.Platform.pathSeparator}$fileName'; + '$directory${file.Platform.pathSeparator}$fileName'; if (file.File(filePath).existsSync()) { final timestamp = @@ -293,7 +292,7 @@ class _InvoicePdfViewState extends State { if (isDesktopOS()) { showToast(localization.fileSavedInPath - .replaceFirst(':path', directory.path)); + .replaceFirst(':path', directory)); } else { await Share.shareXFiles([XFile(filePath)]); } diff --git a/lib/ui/reports/reports_screen_vm.dart b/lib/ui/reports/reports_screen_vm.dart index 6e5ee22c0..8df0d9bef 100644 --- a/lib/ui/reports/reports_screen_vm.dart +++ b/lib/ui/reports/reports_screen_vm.dart @@ -18,9 +18,9 @@ import 'package:invoiceninja_flutter/ui/reports/recurring_expense_report.dart'; import 'package:invoiceninja_flutter/ui/reports/recurring_invoice_report.dart'; import 'package:invoiceninja_flutter/ui/reports/transaction_report.dart'; import 'package:invoiceninja_flutter/ui/reports/vendor_report.dart'; +import 'package:invoiceninja_flutter/utils/files.dart'; import 'package:invoiceninja_flutter/utils/platforms.dart'; import 'package:memoize/memoize.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:redux/redux.dart'; // Project imports: @@ -511,22 +511,19 @@ class ReportsScreenVM { if (kIsWeb) { WebUtils.downloadTextFile(filename, csvData); } else { - final directory = await (isDesktopOS() - ? getDownloadsDirectory() - : getApplicationDocumentsDirectory()); + final directory = await getAppDownloadDirectory(); if (directory == null) { return; } - final filePath = - directory.path + file.Platform.pathSeparator + filename; + final filePath = directory + file.Platform.pathSeparator + filename; final csvFile = file.File(filePath); await csvFile.writeAsString(csvData); if (isDesktopOS()) { showToast(localization!.fileSavedInPath - .replaceFirst(':path', directory.path)); + .replaceFirst(':path', directory)); } else { await Share.shareXFiles([XFile(filePath)]); } diff --git a/lib/utils/files.dart b/lib/utils/files.dart index 95571142c..0e8927df6 100644 --- a/lib/utils/files.dart +++ b/lib/utils/files.dart @@ -7,6 +7,10 @@ import 'package:flutter/foundation.dart'; // Package imports: import 'package:file_picker/file_picker.dart'; import 'package:http/http.dart'; +import 'package:invoiceninja_flutter/main_app.dart'; +import 'package:invoiceninja_flutter/utils/dialogs.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; // Project imports: @@ -77,3 +81,24 @@ Future?> _pickFiles({ return null; } + +Future getAppDownloadDirectory() async { + final directory = await (isDesktopOS() + ? getDownloadsDirectory() + : getApplicationDocumentsDirectory()); + + if (directory == null) { + return null; + } + + if (!Directory(directory.path).existsSync()) { + showErrorDialog( + message: AppLocalization.of(navigatorKey.currentContext!)! + .directoryDoesNotExist + .replaceFirst(':value', directory.path)); + + return null; + } + + return directory.path; +} diff --git a/lib/utils/i18n.dart b/lib/utils/i18n.dart index 18f19690d..cc045554a 100644 --- a/lib/utils/i18n.dart +++ b/lib/utils/i18n.dart @@ -18,6 +18,8 @@ mixin LocalizationsProvider on LocaleCodeAware { static final Map> _localizedValues = { 'en': { // STARTER: lang key - do not remove comment + 'directory_does_not_exist': + 'The download directory does not exist :value', 'user_logged_in_notification': 'User Logged in Notification', 'user_logged_in_notification_help': 'Send an email when logging in from a new location', @@ -109898,6 +109900,10 @@ mixin LocalizationsProvider on LocaleCodeAware { _localizedValues[localeCode]!['user_logged_in_notification_help'] ?? _localizedValues['en']!['user_logged_in_notification_help']!; + String get directoryDoesNotExist => + _localizedValues[localeCode]!['directory_does_not_exist'] ?? + _localizedValues['en']!['directory_does_not_exist']!; + // STARTER: lang field - do not remove comment String lookup(String? key) {