mirror of
https://gitlab.com/famedly/fluffychat.git
synced 2024-11-27 14:59:29 +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": "Store in Apple KeyChain",
|
||||||
"@storeInAppleKeyChain": {},
|
"@storeInAppleKeyChain": {},
|
||||||
"storeSecurlyOnThisDevice": "Store securely on this device",
|
"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 {
|
void onDragDone(DropDoneDetails details) async {
|
||||||
setState(() => dragging = false);
|
setState(() => dragging = false);
|
||||||
for (final xfile in details.files) {
|
final bytesList = await showFutureLoadingDialog(
|
||||||
final bytes = await xfile.readAsBytes();
|
context: context,
|
||||||
await showDialog(
|
future: () => Future.wait(
|
||||||
context: context,
|
details.files.map(
|
||||||
useRootNavigator: false,
|
(xfile) => xfile.readAsBytes(),
|
||||||
builder: (c) => SendFileDialog(
|
|
||||||
file: MatrixFile(
|
|
||||||
bytes: bytes,
|
|
||||||
name: xfile.name,
|
|
||||||
).detectFileType,
|
|
||||||
room: room!,
|
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
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 =>
|
bool get canSaveSelectedEvent =>
|
||||||
@ -308,34 +320,41 @@ class ChatController extends State<Chat> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void sendFileAction() async {
|
void sendFileAction() async {
|
||||||
final result =
|
final result = await FilePickerCross.importMultipleFromStorage(
|
||||||
await FilePickerCross.importFromStorage(type: FileTypeCross.any);
|
type: FileTypeCross.any,
|
||||||
if (result.fileName == null) return;
|
);
|
||||||
|
if (result.isEmpty) return;
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
file: MatrixFile(
|
files: result
|
||||||
bytes: result.toUint8List(),
|
.map((xfile) => MatrixFile(
|
||||||
name: result.fileName!,
|
bytes: xfile.toUint8List(),
|
||||||
).detectFileType,
|
name: xfile.fileName!,
|
||||||
|
).detectFileType)
|
||||||
|
.toList(),
|
||||||
room: room!,
|
room: room!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendImageAction() async {
|
void sendImageAction() async {
|
||||||
final result =
|
final result = await FilePickerCross.importMultipleFromStorage(
|
||||||
await FilePickerCross.importFromStorage(type: FileTypeCross.image);
|
type: FileTypeCross.image,
|
||||||
if (result.fileName == null) return;
|
);
|
||||||
|
if (result.isEmpty) return;
|
||||||
|
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
file: MatrixImageFile(
|
files: result
|
||||||
bytes: result.toUint8List(),
|
.map((xfile) => MatrixFile(
|
||||||
name: result.fileName!,
|
bytes: xfile.toUint8List(),
|
||||||
),
|
name: xfile.fileName!,
|
||||||
|
).detectFileType)
|
||||||
|
.toList(),
|
||||||
room: room!,
|
room: room!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -351,10 +370,12 @@ class ChatController extends State<Chat> {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
file: MatrixImageFile(
|
files: [
|
||||||
bytes: bytes,
|
MatrixImageFile(
|
||||||
name: file.path,
|
bytes: bytes,
|
||||||
),
|
name: file.path,
|
||||||
|
)
|
||||||
|
],
|
||||||
room: room!,
|
room: room!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -370,10 +391,12 @@ class ChatController extends State<Chat> {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
file: MatrixVideoFile(
|
files: [
|
||||||
bytes: bytes,
|
MatrixVideoFile(
|
||||||
name: file.path,
|
bytes: bytes,
|
||||||
),
|
name: file.path,
|
||||||
|
)
|
||||||
|
],
|
||||||
room: room!,
|
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:future_loading_dialog/future_loading_dialog.dart';
|
||||||
import 'package:matrix/matrix.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';
|
import '../../utils/resize_image.dart';
|
||||||
|
|
||||||
class SendFileDialog extends StatefulWidget {
|
class SendFileDialog extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
final MatrixFile file;
|
final List<MatrixFile> files;
|
||||||
|
|
||||||
const SendFileDialog({
|
const SendFileDialog({
|
||||||
required this.room,
|
required this.room,
|
||||||
required this.file,
|
required this.files,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -28,49 +28,59 @@ class _SendFileDialogState extends State<SendFileDialog> {
|
|||||||
static const int minSizeToCompress = 20 * 1024;
|
static const int minSizeToCompress = 20 * 1024;
|
||||||
|
|
||||||
Future<void> _send() async {
|
Future<void> _send() async {
|
||||||
var file = widget.file;
|
for (var file in widget.files) {
|
||||||
MatrixImageFile? thumbnail;
|
MatrixImageFile? thumbnail;
|
||||||
if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) {
|
if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) {
|
||||||
await showFutureLoadingDialog(
|
await showFutureLoadingDialog(
|
||||||
context: context,
|
context: context,
|
||||||
future: () async {
|
future: () async {
|
||||||
file = await file.resizeVideo();
|
file = await file.resizeVideo();
|
||||||
thumbnail = await file.getVideoThumbnail();
|
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();
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var sendStr = L10n.of(context)!.sendFile;
|
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;
|
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;
|
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;
|
sendStr = L10n.of(context)!.sendVideo;
|
||||||
}
|
}
|
||||||
Widget contentWidget;
|
Widget contentWidget;
|
||||||
if (widget.file is MatrixImageFile) {
|
if (allFilesAreImages) {
|
||||||
contentWidget = Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
contentWidget = Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Image.memory(
|
child: Image.memory(
|
||||||
widget.file.bytes,
|
widget.files.first.bytes,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -82,14 +92,13 @@ class _SendFileDialogState extends State<SendFileDialog> {
|
|||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () => setState(() => origImage = !origImage),
|
onTap: () => setState(() => origImage = !origImage),
|
||||||
child: Text(L10n.of(context)!.sendOriginal +
|
child: Text(L10n.of(context)!.sendOriginal + ' ($sizeString)'),
|
||||||
' (${widget.file.sizeString})'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
contentWidget = Text('${widget.file.name} (${widget.file.sizeString})');
|
contentWidget = Text('$fileName ($sizeString)');
|
||||||
}
|
}
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(sendStr),
|
title: Text(sendStr),
|
||||||
|
@ -103,7 +103,7 @@ class ChatListItem extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
file: Matrix.of(context).shareContent!['file'],
|
files: [Matrix.of(context).shareContent!['file']],
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -45,20 +45,8 @@ extension LocalizedBody on Event {
|
|||||||
String? get sizeString {
|
String? get sizeString {
|
||||||
if (content['info'] is Map<String, dynamic> &&
|
if (content['info'] is Map<String, dynamic> &&
|
||||||
content['info'].containsKey('size')) {
|
content['info'].containsKey('size')) {
|
||||||
num size = content['info']['size'];
|
final size = content['info']['size'];
|
||||||
if (size < 1000000) {
|
return size.sizeString;
|
||||||
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';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
|
import 'package:fluffychat/utils/size_string.dart';
|
||||||
|
|
||||||
extension MatrixFileExtension on MatrixFile {
|
extension MatrixFileExtension on MatrixFile {
|
||||||
void save(BuildContext context) async {
|
void save(BuildContext context) async {
|
||||||
@ -46,20 +47,5 @@ extension MatrixFileExtension on MatrixFile {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
String get sizeString {
|
String get sizeString => size.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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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