diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 4f134df5..6f0f9187 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2471,5 +2471,22 @@ "@yourOwnUsername": { "type": "text", "placeholders": {} + }, + "saveFile": "Save file", + "@saveFile": { + "type": "text", + "placeholders": {} + }, + "saveFileToFolder": "Save file to this foler", + "@saveFileToFolder": { + "type": "text", + "placeholders": {} + }, + "savedFileAs": "Saved file as {filename}", + "@savedFileAs": { + "type": "text", + "placeholders": { + "filename": {} + } } } diff --git a/lib/pages/image_viewer.dart b/lib/pages/image_viewer.dart index 9c9e5a3e..df77d783 100644 --- a/lib/pages/image_viewer.dart +++ b/lib/pages/image_viewer.dart @@ -24,8 +24,8 @@ class ImageViewerController extends State { VRouter.of(context).to('/rooms'); } - /// Open this file with a system call. - void openFileAction() => widget.event.openFile(context); + /// Save this file with a system call. + void saveFileAction() => widget.event.saveFile(context); /// Go back if user swiped it away void onInteractionEnds(ScaleEndDetails endDetails) { diff --git a/lib/pages/views/image_viewer_view.dart b/lib/pages/views/image_viewer_view.dart index 6bd5c30b..eeaad16b 100644 --- a/lib/pages/views/image_viewer_view.dart +++ b/lib/pages/views/image_viewer_view.dart @@ -31,7 +31,7 @@ class ImageViewerView extends StatelessWidget { ), IconButton( icon: Icon(Icons.download_outlined), - onPressed: controller.openFileAction, + onPressed: controller.saveFileAction, color: Colors.white, tooltip: L10n.of(context).downloadFile, ), diff --git a/lib/utils/matrix_sdk_extensions.dart/event_extension.dart b/lib/utils/matrix_sdk_extensions.dart/event_extension.dart index 407d140a..171c974d 100644 --- a/lib/utils/matrix_sdk_extensions.dart/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions.dart/event_extension.dart @@ -6,12 +6,13 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'matrix_file_extension.dart'; extension LocalizedBody on Event { - void openFile(BuildContext context) async { + void saveFile(BuildContext context) async { final matrixFile = await showFutureLoadingDialog( context: context, future: () => downloadAndDecryptAttachmentCached(), ); - matrixFile.result?.open(); + + matrixFile.result?.save(context); } IconData get statusIcon { diff --git a/lib/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart b/lib/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart index 4a359093..be2f3471 100644 --- a/lib/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart @@ -1,45 +1,53 @@ import 'dart:io'; -import 'package:android_path_provider/android_path_provider.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/utils/platform_infos.dart'; -import 'package:flutter/foundation.dart'; -import 'package:open_file/open_file.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:universal_html/html.dart' as html; -import 'package:mime_type/mime_type.dart'; +import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:file_picker_cross/file_picker_cross.dart'; +import 'package:filesystem_picker/filesystem_picker.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; extension MatrixFileExtension on MatrixFile { - void open() async { - if (kIsWeb) { - final fileName = name.split('/').last; - final mimeType = mime(fileName); - final element = html.document.createElement('a'); - element.setAttribute( - 'href', html.Url.createObjectUrlFromBlob(html.Blob([bytes]))); - element.setAttribute('target', '_blank'); - element.setAttribute('rel', 'noopener'); - element.setAttribute('download', fileName); - element.setAttribute('type', mimeType); - element.style.display = 'none'; - html.document.body.append(element); - element.click(); - element.remove(); + void save(BuildContext context) async { + if (PlatformInfos.isMobile && + !(await Permission.storage.request()).isGranted) return; + final fileName = name.split('/').last; + if (PlatformInfos.isAndroid) { + final path = await FilesystemPicker.open( + title: L10n.of(context).saveFile, + context: context, + rootDirectory: Directory('/sdcard/'), + fsType: FilesystemType.folder, + pickText: L10n.of(context).saveFileToFolder, + folderIconColor: Theme.of(context).primaryColor, + requestPermission: () async => + await Permission.storage.request().isGranted, + ); + if (path != null) { + // determine a unique filename + // somefile-number.extension, e.g. helloworld-1.txt + var file = File('$path/$fileName'); + var i = 0; + var extension = ''; + if (fileName.contains('.')) { + extension = fileName.substring(fileName.lastIndexOf('.')); + } + final fileNameWithoutExtension = + fileName.substring(0, fileName.lastIndexOf('.')); + while (await file.exists()) { + i++; + file = File('$path/$fileNameWithoutExtension-$i$extension'); + } + await file.writeAsBytes(bytes); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: + Text(L10n.of(context).savedFileAs(file.path.split('/').last)))); + } } else { - if (PlatformInfos.isMobile && - !(await Permission.storage.request()).isGranted) return; - final downloadsDir = PlatformInfos.isDesktop - ? (await getDownloadsDirectory()).path - : Platform.isAndroid - ? (await AndroidPathProvider.downloadsPath) - : (await getApplicationDocumentsDirectory()).path; - - final file = File(downloadsDir + '/' + name.split('/').last); - file.writeAsBytesSync(bytes); - await OpenFile.open(file.path); + final file = FilePickerCross(bytes); + await file.exportToStorage(fileName: fileName); } - return; } MatrixFile get detectFileType { diff --git a/lib/widgets/event_content/message_download_content.dart b/lib/widgets/event_content/message_download_content.dart index 41aa4053..abe64029 100644 --- a/lib/widgets/event_content/message_download_content.dart +++ b/lib/widgets/event_content/message_download_content.dart @@ -24,7 +24,7 @@ class MessageDownloadContent extends StatelessWidget { primary: Theme.of(context).scaffoldBackgroundColor, onPrimary: Theme.of(context).textTheme.bodyText1.color, ), - onPressed: () => event.openFile(context), + onPressed: () => event.saveFile(context), child: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/pubspec.lock b/pubspec.lock index 784533df..203d0076 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,13 +29,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.0" - android_path_provider: - dependency: "direct main" - description: - name: android_path_provider - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.1" animations: dependency: transitive description: @@ -297,6 +290,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.2" + filesystem_picker: + dependency: "direct main" + description: + name: filesystem_picker + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" firebase_core: dependency: transitive description: @@ -646,13 +646,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" - mime_type: - dependency: "direct main" - description: - name: mime_type - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" moor: dependency: transitive description: @@ -704,13 +697,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.0" - open_file: - dependency: "direct main" - description: - name: open_file - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.1" open_noti_settings: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 8554b236..2da66dee 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,7 +9,6 @@ environment: dependencies: adaptive_dialog: ^1.0.1 adaptive_theme: ^2.2.0 - android_path_provider: ^0.2.1 audioplayers: ^0.19.1 cached_network_image: ^3.0.0 cupertino_icons: any @@ -21,6 +20,7 @@ dependencies: url: https://gitlab.com/famedly/libraries/fcm_shared_isolate.git ref: main file_picker_cross: ^4.4.2 + filesystem_picker: ^1.0.4 flutter: sdk: flutter flutter_app_lock: ^1.5.0 @@ -42,12 +42,10 @@ dependencies: intl: any localstorage: ^4.0.0+1 matrix: ^0.1.7 - mime_type: ^1.0.0 native_imaging: git: url: https://gitlab.com/famedly/libraries/native_imaging.git ref: master - open_file: ^3.2.1 open_noti_settings: ^0.2.0 package_info_plus: ^1.0.3 path_provider: ^2.0.2