mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-23 20:49:26 +01:00
feat: Send multiple images at once
This commit is contained in:
parent
fa0ea99657
commit
0237ada0bc
@ -2850,5 +2850,11 @@
|
||||
"storeInAppleKeyChain": "Store in Apple KeyChain",
|
||||
"@storeInAppleKeyChain": {},
|
||||
"storeSecurlyOnThisDevice": "Store securely on this device",
|
||||
"@storeSecurlyOnThisDevice": {}
|
||||
"@storeSecurlyOnThisDevice": {},
|
||||
"countFiles": "{count} files",
|
||||
"@countFiles": {
|
||||
"placeholders": {
|
||||
"count": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,20 +70,32 @@ class ChatController extends State<Chat> {
|
||||
|
||||
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 = <MatrixFile>[];
|
||||
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<Chat> {
|
||||
}
|
||||
|
||||
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<Chat> {
|
||||
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<Chat> {
|
||||
context: context,
|
||||
useRootNavigator: false,
|
||||
builder: (c) => SendFileDialog(
|
||||
file: MatrixVideoFile(
|
||||
bytes: bytes,
|
||||
name: file.path,
|
||||
),
|
||||
files: [
|
||||
MatrixVideoFile(
|
||||
bytes: bytes,
|
||||
name: file.path,
|
||||
)
|
||||
],
|
||||
room: room!,
|
||||
),
|
||||
);
|
||||
|
@ -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<MatrixFile> 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<SendFileDialog> {
|
||||
static const int minSizeToCompress = 20 * 1024;
|
||||
|
||||
Future<void> _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<double>(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: <Widget>[
|
||||
Flexible(
|
||||
child: Image.memory(
|
||||
widget.file.bytes,
|
||||
widget.files.first.bytes,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
@ -82,14 +92,13 @@ class _SendFileDialogState extends State<SendFileDialog> {
|
||||
),
|
||||
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),
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
@ -45,20 +45,8 @@ extension LocalizedBody on Event {
|
||||
String? get sizeString {
|
||||
if (content['info'] is Map<String, dynamic> &&
|
||||
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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
18
lib/utils/size_string.dart
Normal file
18
lib/utils/size_string.dart
Normal file
@ -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';
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user