From 0237ada0bc84a3a88e7b379d98a1fe0ee4a061f9 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Sun, 10 Jul 2022 09:26:16 +0200 Subject: [PATCH] feat: Send multiple images at once --- assets/l10n/intl_en.arb | 8 +- lib/pages/chat/chat.dart | 91 ++++++++++++------- lib/pages/chat/send_file_dialog.dart | 75 ++++++++------- lib/pages/chat_list/chat_list_item.dart | 2 +- .../event_extension.dart | 16 +--- .../matrix_file_extension.dart | 18 +--- lib/utils/size_string.dart | 18 ++++ 7 files changed, 129 insertions(+), 99 deletions(-) create mode 100644 lib/utils/size_string.dart diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 8474b178..a843aab2 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -2850,5 +2850,11 @@ "storeInAppleKeyChain": "Store in Apple KeyChain", "@storeInAppleKeyChain": {}, "storeSecurlyOnThisDevice": "Store securely on this device", - "@storeSecurlyOnThisDevice": {} + "@storeSecurlyOnThisDevice": {}, + "countFiles": "{count} files", + "@countFiles": { + "placeholders": { + "count": {} + } + } } diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 4d9640be..9b6eb31c 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -70,20 +70,32 @@ class ChatController extends State { void onDragDone(DropDoneDetails details) async { setState(() => dragging = false); - for (final xfile in details.files) { - final bytes = await xfile.readAsBytes(); - await showDialog( - context: context, - useRootNavigator: false, - builder: (c) => SendFileDialog( - file: MatrixFile( - bytes: bytes, - name: xfile.name, - ).detectFileType, - room: room!, + final bytesList = await showFutureLoadingDialog( + context: context, + future: () => Future.wait( + details.files.map( + (xfile) => xfile.readAsBytes(), ), - ); + ), + ); + if (bytesList.error != null) return; + + final matrixFiles = []; + for (var i = 0; i < bytesList.result!.length; i++) { + matrixFiles.add(MatrixFile( + bytes: bytesList.result![i], + name: details.files[i].name, + ).detectFileType); } + + await showDialog( + context: context, + useRootNavigator: false, + builder: (c) => SendFileDialog( + files: matrixFiles, + room: room!, + ), + ); } bool get canSaveSelectedEvent => @@ -308,34 +320,41 @@ class ChatController extends State { } void sendFileAction() async { - final result = - await FilePickerCross.importFromStorage(type: FileTypeCross.any); - if (result.fileName == null) return; + final result = await FilePickerCross.importMultipleFromStorage( + type: FileTypeCross.any, + ); + if (result.isEmpty) return; await showDialog( context: context, useRootNavigator: false, builder: (c) => SendFileDialog( - file: MatrixFile( - bytes: result.toUint8List(), - name: result.fileName!, - ).detectFileType, + files: result + .map((xfile) => MatrixFile( + bytes: xfile.toUint8List(), + name: xfile.fileName!, + ).detectFileType) + .toList(), room: room!, ), ); } void sendImageAction() async { - final result = - await FilePickerCross.importFromStorage(type: FileTypeCross.image); - if (result.fileName == null) return; + final result = await FilePickerCross.importMultipleFromStorage( + type: FileTypeCross.image, + ); + if (result.isEmpty) return; + await showDialog( context: context, useRootNavigator: false, builder: (c) => SendFileDialog( - file: MatrixImageFile( - bytes: result.toUint8List(), - name: result.fileName!, - ), + files: result + .map((xfile) => MatrixFile( + bytes: xfile.toUint8List(), + name: xfile.fileName!, + ).detectFileType) + .toList(), room: room!, ), ); @@ -351,10 +370,12 @@ class ChatController extends State { context: context, useRootNavigator: false, builder: (c) => SendFileDialog( - file: MatrixImageFile( - bytes: bytes, - name: file.path, - ), + files: [ + MatrixImageFile( + bytes: bytes, + name: file.path, + ) + ], room: room!, ), ); @@ -370,10 +391,12 @@ class ChatController extends State { context: context, useRootNavigator: false, builder: (c) => SendFileDialog( - file: MatrixVideoFile( - bytes: bytes, - name: file.path, - ), + files: [ + MatrixVideoFile( + bytes: bytes, + name: file.path, + ) + ], room: room!, ), ); diff --git a/lib/pages/chat/send_file_dialog.dart b/lib/pages/chat/send_file_dialog.dart index 3383c001..d88978c4 100644 --- a/lib/pages/chat/send_file_dialog.dart +++ b/lib/pages/chat/send_file_dialog.dart @@ -4,16 +4,16 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:future_loading_dialog/future_loading_dialog.dart'; import 'package:matrix/matrix.dart'; -import '../../utils/matrix_sdk_extensions.dart/matrix_file_extension.dart'; +import 'package:fluffychat/utils/size_string.dart'; import '../../utils/resize_image.dart'; class SendFileDialog extends StatefulWidget { final Room room; - final MatrixFile file; + final List files; const SendFileDialog({ required this.room, - required this.file, + required this.files, Key? key, }) : super(key: key); @@ -28,49 +28,59 @@ class _SendFileDialogState extends State { static const int minSizeToCompress = 20 * 1024; Future _send() async { - var file = widget.file; - MatrixImageFile? thumbnail; - if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) { - await showFutureLoadingDialog( - context: context, - future: () async { - file = await file.resizeVideo(); - thumbnail = await file.getVideoThumbnail(); - }); + for (var file in widget.files) { + MatrixImageFile? thumbnail; + if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) { + await showFutureLoadingDialog( + context: context, + future: () async { + file = await file.resizeVideo(); + thumbnail = await file.getVideoThumbnail(); + }); + } + final scaffoldMessenger = ScaffoldMessenger.of(context); + widget.room + .sendFileEvent( + file, + thumbnail: thumbnail, + shrinkImageMaxDimension: origImage ? null : 1600, + ) + .catchError((e) { + scaffoldMessenger.showSnackBar( + SnackBar(content: Text(e.toLocalizedString())), + ); + }); } - final scaffoldMessenger = ScaffoldMessenger.of(context); - widget.room - .sendFileEvent( - file, - thumbnail: thumbnail, - shrinkImageMaxDimension: origImage ? null : 1600, - ) - .catchError((e) { - scaffoldMessenger.showSnackBar( - SnackBar(content: Text(e.toLocalizedString())), - ); - }); - Navigator.of(context, rootNavigator: false).pop(); + return; } @override Widget build(BuildContext context) { var sendStr = L10n.of(context)!.sendFile; - if (widget.file is MatrixImageFile) { + final bool allFilesAreImages = + widget.files.every((file) => file is MatrixImageFile); + final sizeString = widget.files + .fold(0, (p, file) => p + file.bytes.length) + .sizeString; + final fileName = widget.files.length == 1 + ? widget.files.single.name + : L10n.of(context)!.countFiles(widget.files.length.toString()); + + if (allFilesAreImages) { sendStr = L10n.of(context)!.sendImage; - } else if (widget.file is MatrixAudioFile) { + } else if (widget.files.every((file) => file is MatrixAudioFile)) { sendStr = L10n.of(context)!.sendAudio; - } else if (widget.file is MatrixVideoFile) { + } else if (widget.files.every((file) => file is MatrixVideoFile)) { sendStr = L10n.of(context)!.sendVideo; } Widget contentWidget; - if (widget.file is MatrixImageFile) { + if (allFilesAreImages) { contentWidget = Column(mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Image.memory( - widget.file.bytes, + widget.files.first.bytes, fit: BoxFit.contain, ), ), @@ -82,14 +92,13 @@ class _SendFileDialogState extends State { ), InkWell( onTap: () => setState(() => origImage = !origImage), - child: Text(L10n.of(context)!.sendOriginal + - ' (${widget.file.sizeString})'), + child: Text(L10n.of(context)!.sendOriginal + ' ($sizeString)'), ), ], ) ]); } else { - contentWidget = Text('${widget.file.name} (${widget.file.sizeString})'); + contentWidget = Text('$fileName ($sizeString)'); } return AlertDialog( title: Text(sendStr), diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 65114fa1..52a9bab6 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -103,7 +103,7 @@ class ChatListItem extends StatelessWidget { context: context, useRootNavigator: false, builder: (c) => SendFileDialog( - file: Matrix.of(context).shareContent!['file'], + files: [Matrix.of(context).shareContent!['file']], room: room, ), ); diff --git a/lib/utils/matrix_sdk_extensions.dart/event_extension.dart b/lib/utils/matrix_sdk_extensions.dart/event_extension.dart index b6796a40..b1d3231d 100644 --- a/lib/utils/matrix_sdk_extensions.dart/event_extension.dart +++ b/lib/utils/matrix_sdk_extensions.dart/event_extension.dart @@ -45,20 +45,8 @@ extension LocalizedBody on Event { String? get sizeString { if (content['info'] is Map && content['info'].containsKey('size')) { - num size = content['info']['size']; - if (size < 1000000) { - size = size / 1000; - size = (size * 10).round() / 10; - return '${size.toString()} KB'; - } else if (size < 1000000000) { - size = size / 1000000; - size = (size * 10).round() / 10; - return '${size.toString()} MB'; - } else { - size = size / 1000000000; - size = (size * 10).round() / 10; - return '${size.toString()} GB'; - } + final size = content['info']['size']; + return size.sizeString; } else { return null; } 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 17ebd339..64343d8b 100644 --- a/lib/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart +++ b/lib/utils/matrix_sdk_extensions.dart/matrix_file_extension.dart @@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:fluffychat/utils/platform_infos.dart'; +import 'package:fluffychat/utils/size_string.dart'; extension MatrixFileExtension on MatrixFile { void save(BuildContext context) async { @@ -46,20 +47,5 @@ extension MatrixFileExtension on MatrixFile { return this; } - String get sizeString { - var size = this.size.toDouble(); - if (size < 1000000) { - size = size / 1000; - size = (size * 10).round() / 10; - return '${size.toString()} KB'; - } else if (size < 1000000000) { - size = size / 1000000; - size = (size * 10).round() / 10; - return '${size.toString()} MB'; - } else { - size = size / 1000000000; - size = (size * 10).round() / 10; - return '${size.toString()} GB'; - } - } + String get sizeString => size.sizeString; } diff --git a/lib/utils/size_string.dart b/lib/utils/size_string.dart new file mode 100644 index 00000000..930a97f9 --- /dev/null +++ b/lib/utils/size_string.dart @@ -0,0 +1,18 @@ +extension SizeString on num { + String get sizeString { + var size = toDouble(); + if (size < 1000000) { + size = size / 1000; + size = (size * 10).round() / 10; + return '${size.toString()} KB'; + } else if (size < 1000000000) { + size = size / 1000000; + size = (size * 10).round() / 10; + return '${size.toString()} MB'; + } else { + size = size / 1000000000; + size = (size * 10).round() / 10; + return '${size.toString()} GB'; + } + } +}