feat: Sending multiple files at once
This commit is contained in:
parent
b328e95980
commit
b83503585f
6 changed files with 165 additions and 58 deletions
|
|
@ -128,6 +128,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
builder: (c) => SendFileDialog(
|
||||
files: details.files,
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -483,7 +484,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
final result = await AppLock.of(context).pauseWhile(
|
||||
FilePicker.platform.pickFiles(
|
||||
compressionQuality: 0,
|
||||
allowMultiple: false,
|
||||
allowMultiple: true,
|
||||
),
|
||||
);
|
||||
if (result == null || result.files.isEmpty) return;
|
||||
|
|
@ -492,6 +493,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
builder: (c) => SendFileDialog(
|
||||
files: result.xFiles,
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -503,6 +505,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
builder: (c) => SendFileDialog(
|
||||
files: [XFile.fromData(image)],
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -512,7 +515,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
FilePicker.platform.pickFiles(
|
||||
compressionQuality: 0,
|
||||
type: FileType.image,
|
||||
allowMultiple: false,
|
||||
allowMultiple: true,
|
||||
),
|
||||
);
|
||||
if (result == null || result.files.isEmpty) return;
|
||||
|
|
@ -522,6 +525,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
builder: (c) => SendFileDialog(
|
||||
files: result.xFiles,
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -537,6 +541,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
builder: (c) => SendFileDialog(
|
||||
files: [file],
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -555,6 +560,7 @@ class ChatController extends State<ChatPageWithRoom>
|
|||
builder: (c) => SendFileDialog(
|
||||
files: [file],
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@ import 'package:flutter/material.dart';
|
|||
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
|
||||
import 'package:fluffychat/config/app_config.dart';
|
||||
import 'package:fluffychat/utils/error_reporter.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
||||
import 'package:fluffychat/utils/platform_infos.dart';
|
||||
import 'package:fluffychat/utils/size_string.dart';
|
||||
|
|
@ -20,10 +19,12 @@ import '../../utils/resize_video.dart';
|
|||
class SendFileDialog extends StatefulWidget {
|
||||
final Room room;
|
||||
final List<XFile> files;
|
||||
final BuildContext outerContext;
|
||||
|
||||
const SendFileDialog({
|
||||
required this.room,
|
||||
required this.files,
|
||||
required this.outerContext,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -38,65 +39,98 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
static const int minSizeToCompress = 20 * 1024;
|
||||
|
||||
Future<void> _send() async {
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||
final scaffoldMessenger = ScaffoldMessenger.of(widget.outerContext);
|
||||
final l10n = L10n.of(context)!;
|
||||
|
||||
Navigator.of(context, rootNavigator: false).pop();
|
||||
try {
|
||||
scaffoldMessenger.showLoadingSnackBar(l10n.prepareSendingAttachment);
|
||||
Navigator.of(context, rootNavigator: false).pop();
|
||||
final clientConfig = await widget.room.client.getConfig();
|
||||
final maxUploadSize = clientConfig.mUploadSize ?? 100 * 1024 * 1024;
|
||||
|
||||
showFutureLoadingDialog(
|
||||
context: context,
|
||||
future: () async {
|
||||
final clientConfig = await widget.room.client.getConfig();
|
||||
final maxUploadSize = clientConfig.mUploadSize ?? 100 * 1024 * 1024;
|
||||
for (final xfile in widget.files) {
|
||||
final MatrixFile file;
|
||||
MatrixImageFile? thumbnail;
|
||||
final length = await xfile.length();
|
||||
final mimeType = xfile.mimeType ?? lookupMimeType(xfile.path);
|
||||
|
||||
for (final xfile in widget.files) {
|
||||
final MatrixFile file;
|
||||
MatrixImageFile? thumbnail;
|
||||
final length = await xfile.length();
|
||||
final mimeType = xfile.mimeType ?? lookupMimeType(xfile.path);
|
||||
// If file is a video, shrink it!
|
||||
if (PlatformInfos.isMobile &&
|
||||
mimeType != null &&
|
||||
mimeType.startsWith('video') &&
|
||||
length > minSizeToCompress &&
|
||||
!origImage) {
|
||||
scaffoldMessenger.showLoadingSnackBar(l10n.compressVideo);
|
||||
file = await xfile.resizeVideo();
|
||||
scaffoldMessenger.showLoadingSnackBar(l10n.generatingVideoThumbnail);
|
||||
thumbnail = await xfile.getVideoThumbnail();
|
||||
} else {
|
||||
// Else we just create a MatrixFile
|
||||
file = MatrixFile(
|
||||
bytes: await xfile.readAsBytes(),
|
||||
name: xfile.name,
|
||||
mimeType: xfile.mimeType,
|
||||
).detectFileType;
|
||||
}
|
||||
|
||||
// If file is a video, shrink it!
|
||||
if (mimeType != null &&
|
||||
mimeType.startsWith('video') &&
|
||||
length > minSizeToCompress &&
|
||||
!origImage) {
|
||||
file = await xfile.resizeVideo();
|
||||
thumbnail = await xfile.getVideoThumbnail();
|
||||
} else {
|
||||
// Else we just create a MatrixFile
|
||||
file = MatrixFile(
|
||||
bytes: await xfile.readAsBytes(),
|
||||
name: xfile.name,
|
||||
mimeType: xfile.mimeType,
|
||||
).detectFileType;
|
||||
}
|
||||
if (file.bytes.length > maxUploadSize) {
|
||||
throw FileTooBigMatrixException(length, maxUploadSize);
|
||||
}
|
||||
|
||||
if (file.bytes.length > maxUploadSize) {
|
||||
throw FileTooBigMatrixException(length, maxUploadSize);
|
||||
}
|
||||
if (widget.files.length > 1) {
|
||||
scaffoldMessenger.showLoadingSnackBar(
|
||||
l10n.sendingAttachmentCountOfCount(
|
||||
widget.files.indexOf(xfile) + 1,
|
||||
widget.files.length,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
scaffoldMessenger.showLoadingSnackBar(l10n.sendingAttachment);
|
||||
}
|
||||
|
||||
widget.room
|
||||
.sendFileEvent(
|
||||
try {
|
||||
await widget.room.sendFileEvent(
|
||||
file,
|
||||
thumbnail: thumbnail,
|
||||
shrinkImageMaxDimension: origImage ? null : 1600,
|
||||
);
|
||||
} on MatrixException catch (e) {
|
||||
final retryAfterMs = e.retryAfterMs;
|
||||
if (e.error != MatrixError.M_LIMIT_EXCEEDED || retryAfterMs == null) {
|
||||
rethrow;
|
||||
}
|
||||
final retryAfterDuration =
|
||||
Duration(milliseconds: retryAfterMs + 1000);
|
||||
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
l10n.serverLimitReached(retryAfterDuration.inSeconds),
|
||||
),
|
||||
),
|
||||
);
|
||||
await Future.delayed(retryAfterDuration);
|
||||
|
||||
scaffoldMessenger.showLoadingSnackBar(l10n.sendingAttachment);
|
||||
|
||||
await widget.room.sendFileEvent(
|
||||
file,
|
||||
thumbnail: thumbnail,
|
||||
shrinkImageMaxDimension: origImage ? null : 1600,
|
||||
)
|
||||
.catchError(
|
||||
(e, s) {
|
||||
if (e is FileTooBigMatrixException) {
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(content: Text(l10n.fileIsTooBigForServer)),
|
||||
);
|
||||
return null;
|
||||
}
|
||||
ErrorReporter(context, 'Unable to send file')
|
||||
.onErrorCallback(e, s);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
scaffoldMessenger.clearSnackBars();
|
||||
scaffoldMessenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(e.toLocalizedString(widget.outerContext)),
|
||||
duration: const Duration(seconds: 30),
|
||||
showCloseIcon: true,
|
||||
),
|
||||
);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -270,3 +304,30 @@ class SendFileDialogState extends State<SendFileDialog> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension on ScaffoldMessengerState {
|
||||
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showLoadingSnackBar(
|
||||
String title,
|
||||
) {
|
||||
clearSnackBars();
|
||||
return showSnackBar(
|
||||
SnackBar(
|
||||
duration: const Duration(minutes: 5),
|
||||
dismissDirection: DismissDirection.none,
|
||||
content: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator.adaptive(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(title),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ class ChatListController extends State<ChatList>
|
|||
),
|
||||
],
|
||||
room: room,
|
||||
outerContext: context,
|
||||
),
|
||||
);
|
||||
Matrix.of(context).shareContent = null;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
|
@ -10,10 +11,29 @@ import 'package:matrix/matrix.dart';
|
|||
import 'uia_request_manager.dart';
|
||||
|
||||
extension LocalizedExceptionExtension on Object {
|
||||
static String _formatFileSize(int size) {
|
||||
if (size < 1024) return '$size B';
|
||||
final i = (log(size) / log(1024)).floor();
|
||||
final num = (size / pow(1024, i));
|
||||
final round = num.round();
|
||||
final numString = round < 10
|
||||
? num.toStringAsFixed(2)
|
||||
: round < 100
|
||||
? num.toStringAsFixed(1)
|
||||
: round.toString();
|
||||
return '$numString ${'kMGTPEZY'[i - 1]}B';
|
||||
}
|
||||
|
||||
String toLocalizedString(
|
||||
BuildContext context, [
|
||||
ExceptionContext? exceptionContext,
|
||||
]) {
|
||||
if (this is FileTooBigMatrixException) {
|
||||
final exception = this as FileTooBigMatrixException;
|
||||
return L10n.of(context)!.fileIsTooBigForServer(
|
||||
_formatFileSize(exception.maxFileSize),
|
||||
);
|
||||
}
|
||||
if (this is MatrixException) {
|
||||
switch ((this as MatrixException).error) {
|
||||
case MatrixError.M_FORBIDDEN:
|
||||
|
|
@ -30,9 +50,6 @@ extension LocalizedExceptionExtension on Object {
|
|||
if (this is InvalidPassphraseException) {
|
||||
return L10n.of(context)!.wrongRecoveryKey;
|
||||
}
|
||||
if (this is FileTooBigMatrixException) {
|
||||
return L10n.of(context)!.fileIsTooBigForServer;
|
||||
}
|
||||
if (this is BadServerVersionsException) {
|
||||
final serverVersions = (this as BadServerVersionsException)
|
||||
.serverVersions
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue