Merge pull request #1354 from krille-chan/krille/load-bytes-later-when-send-files

Krille/load bytes later when send files
This commit is contained in:
Krille-chan 2024-09-22 15:57:11 +02:00 committed by GitHub
commit b328e95980
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 257 additions and 223 deletions

View file

@ -2761,5 +2761,6 @@
"discoverHomeservers": "Discover homeservers",
"whatIsAHomeserver": "What is a homeserver?",
"homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org.",
"doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?"
"doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?",
"calculatingFileSize": "Calculating file size..."
}

View file

@ -37,7 +37,6 @@ import 'package:fluffychat/widgets/app_lock.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../../utils/account_bundles.dart';
import '../../utils/localized_exception_extension.dart';
import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart';
import 'send_file_dialog.dart';
import 'send_location_dialog.dart';
@ -123,36 +122,11 @@ class ChatController extends State<ChatPageWithRoom>
void onDragDone(DropDoneDetails details) async {
setState(() => dragging = false);
if (details.files.isEmpty) return;
final result = await showFutureLoadingDialog(
context: context,
future: () async {
final clientConfig = await room.client.getConfig();
final maxUploadSize = clientConfig.mUploadSize ?? 100 * 1024 * 1024;
final matrixFiles = await Future.wait(
details.files.map(
(xfile) async {
final length = await xfile.length();
if (length > maxUploadSize) {
throw FileTooBigMatrixException(length, maxUploadSize);
}
return MatrixFile(
bytes: await xfile.readAsBytes(),
name: xfile.name,
mimeType: xfile.mimeType,
).detectFileType;
},
),
);
return matrixFiles;
},
);
final matrixFiles = result.result;
if (matrixFiles == null || matrixFiles.isEmpty) return;
await showAdaptiveDialog(
context: context,
builder: (c) => SendFileDialog(
files: matrixFiles,
files: details.files,
room: room,
),
);
@ -510,36 +484,24 @@ class ChatController extends State<ChatPageWithRoom>
FilePicker.platform.pickFiles(
compressionQuality: 0,
allowMultiple: false,
withData: true,
),
);
if (result == null || result.files.isEmpty) return;
await showAdaptiveDialog(
context: context,
builder: (c) => SendFileDialog(
files: result.files
.map(
(xfile) => MatrixFile(
bytes: xfile.bytes!,
name: xfile.name,
).detectFileType,
)
.toList(),
files: result.xFiles,
room: room,
),
);
}
void sendImageFromClipBoard(Uint8List? image) async {
if (image == null) return;
await showAdaptiveDialog(
context: context,
builder: (c) => SendFileDialog(
files: [
MatrixFile(
bytes: image!,
name: "image from Clipboard",
).detectFileType,
],
files: [XFile.fromData(image)],
room: room,
),
);
@ -550,7 +512,6 @@ class ChatController extends State<ChatPageWithRoom>
FilePicker.platform.pickFiles(
compressionQuality: 0,
type: FileType.image,
withData: true,
allowMultiple: false,
),
);
@ -559,14 +520,7 @@ class ChatController extends State<ChatPageWithRoom>
await showAdaptiveDialog(
context: context,
builder: (c) => SendFileDialog(
files: result.files
.map(
(xfile) => MatrixFile(
bytes: xfile.bytes!,
name: xfile.name,
).detectFileType,
)
.toList(),
files: result.xFiles,
room: room,
),
);
@ -577,16 +531,11 @@ class ChatController extends State<ChatPageWithRoom>
FocusScope.of(context).requestFocus(FocusNode());
final file = await ImagePicker().pickImage(source: ImageSource.camera);
if (file == null) return;
final bytes = await file.readAsBytes();
await showAdaptiveDialog(
context: context,
builder: (c) => SendFileDialog(
files: [
MatrixImageFile(
bytes: bytes,
name: file.path,
),
],
files: [file],
room: room,
),
);
@ -600,16 +549,11 @@ class ChatController extends State<ChatPageWithRoom>
maxDuration: const Duration(minutes: 1),
);
if (file == null) return;
final bytes = await file.readAsBytes();
await showAdaptiveDialog(
context: context,
builder: (c) => SendFileDialog(
files: [
MatrixVideoFile(
bytes: bytes,
name: file.path,
),
],
files: [file],
room: room,
),
);

View file

@ -60,7 +60,10 @@ class ImageBubble extends StatelessWidget {
if (!tapToView) return;
showDialog(
context: context,
builder: (_) => ImageViewer(event),
builder: (_) => ImageViewer(
event,
outerContext: context,
),
);
}

View file

@ -1,18 +1,25 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
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/matrix_sdk_extensions/matrix_file_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/size_string.dart';
import '../../utils/resize_image.dart';
import '../../utils/resize_video.dart';
class SendFileDialog extends StatefulWidget {
final Room room;
final List<MatrixFile> files;
final List<XFile> files;
const SendFileDialog({
required this.room,
@ -33,17 +40,41 @@ class SendFileDialogState extends State<SendFileDialog> {
Future<void> _send() async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final l10n = L10n.of(context)!;
for (var file in widget.files) {
MatrixImageFile? thumbnail;
if (file is MatrixVideoFile && file.bytes.length > minSizeToCompress) {
await showFutureLoadingDialog(
Navigator.of(context, rootNavigator: false).pop();
showFutureLoadingDialog(
context: context,
future: () async {
file = origImage ? file : await file.resizeVideo();
thumbnail = await file.getVideoThumbnail();
},
);
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);
// 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);
}
widget.room
.sendFileEvent(
file,
@ -58,39 +89,54 @@ class SendFileDialogState extends State<SendFileDialog> {
);
return null;
}
ErrorReporter(context, 'Unable to send file').onErrorCallback(e, s);
ErrorReporter(context, 'Unable to send file')
.onErrorCallback(e, s);
return null;
},
);
}
Navigator.of(context, rootNavigator: false).pop();
},
);
return;
}
Future<String> _calcCombinedFileSize() async {
final lengths =
await Future.wait(widget.files.map((file) => file.length()));
return lengths.fold<double>(0, (p, length) => p + length).sizeString;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
var sendStr = L10n.of(context)!.sendFile;
final allFilesAreImages =
widget.files.every((file) => file is MatrixImageFile);
final sizeString = widget.files
.fold<double>(0, (p, file) => p + file.bytes.length)
.sizeString;
final uniqueMimeType = widget.files
.map((file) => file.mimeType ?? lookupMimeType(file.path))
.toSet()
.singleOrNull;
final fileName = widget.files.length == 1
? widget.files.single.name
: L10n.of(context)!.countFiles(widget.files.length.toString());
if (allFilesAreImages) {
if (uniqueMimeType?.startsWith('image') ?? false) {
sendStr = L10n.of(context)!.sendImage;
} else if (widget.files.every((file) => file is MatrixAudioFile)) {
} else if (uniqueMimeType?.startsWith('audio') ?? false) {
sendStr = L10n.of(context)!.sendAudio;
} else if (widget.files.every((file) => file is MatrixVideoFile)) {
} else if (uniqueMimeType?.startsWith('video') ?? false) {
sendStr = L10n.of(context)!.sendVideo;
}
return FutureBuilder<String>(
future: _calcCombinedFileSize(),
builder: (context, snapshot) {
final sizeString =
snapshot.data ?? L10n.of(context)!.calculatingFileSize;
Widget contentWidget;
if (allFilesAreImages) {
if (uniqueMimeType?.startsWith('image') ?? false) {
contentWidget = Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
@ -100,8 +146,14 @@ class SendFileDialogState extends State<SendFileDialog> {
elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
shadowColor: theme.appBarTheme.shadowColor,
clipBehavior: Clip.hardEdge,
child: Image.memory(
widget.files.first.bytes,
child: kIsWeb
? Image.network(
widget.files.first.path,
fit: BoxFit.contain,
height: 256,
)
: Image.file(
File(widget.files.first.path),
fit: BoxFit.contain,
height: 256,
),
@ -134,13 +186,41 @@ class SendFileDialogState extends State<SendFileDialog> {
),
],
);
} else if (widget.files.every((file) => file is MatrixVideoFile)) {
contentWidget = Column(
} else {
final fileNameParts = fileName.split('.');
contentWidget = SizedBox(
width: 256,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(fileName),
const SizedBox(height: 16),
children: [
Row(
children: [
Icon(
uniqueMimeType == null
? Icons.description_outlined
: uniqueMimeType.startsWith('video')
? Icons.video_file_outlined
: uniqueMimeType.startsWith('audio')
? Icons.audio_file_outlined
: Icons.description_outlined,
),
const SizedBox(width: 8),
Expanded(
child: Text(
fileNameParts.first,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (fileNameParts.length > 1)
Text('.${fileNameParts.last}'),
Text(' ($sizeString)'),
],
),
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
if (uniqueMimeType != null &&
uniqueMimeType.startsWith('video') &&
PlatformInfos.isMobile)
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
@ -156,7 +236,8 @@ class SendFileDialogState extends State<SendFileDialog> {
children: [
Text(
L10n.of(context)!.sendOriginal,
style: const TextStyle(fontWeight: FontWeight.bold),
style:
const TextStyle(fontWeight: FontWeight.bold),
),
Text(sizeString),
],
@ -165,9 +246,8 @@ class SendFileDialogState extends State<SendFileDialog> {
],
),
],
),
);
} else {
contentWidget = Text('$fileName ($sizeString)');
}
return AlertDialog.adaptive(
title: Text(sendStr),
@ -186,5 +266,7 @@ class SendFileDialogState extends State<SendFileDialog> {
),
],
);
},
);
}
}

View file

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:cross_file/cross_file.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:flutter_shortcuts/flutter_shortcuts.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
@ -205,7 +206,13 @@ class ChatListController extends State<ChatList>
context: context,
useRootNavigator: false,
builder: (c) => SendFileDialog(
files: [shareFile],
files: [
XFile.fromData(
shareFile.bytes,
name: shareFile.name,
mimeType: shareFile.mimeType,
),
],
room: room,
),
);

View file

@ -10,8 +10,9 @@ import '../../utils/matrix_sdk_extensions/event_extension.dart';
class ImageViewer extends StatefulWidget {
final Event event;
final BuildContext outerContext;
const ImageViewer(this.event, {super.key});
const ImageViewer(this.event, {required this.outerContext, super.key});
@override
ImageViewerController createState() => ImageViewerController();
@ -20,8 +21,9 @@ class ImageViewer extends StatefulWidget {
class ImageViewerController extends State<ImageViewer> {
/// Forward this image to another room.
void forwardAction() {
Matrix.of(context).shareContent = widget.event.content;
context.go('/rooms');
Matrix.of(widget.outerContext).shareContent = widget.event.content;
Navigator.of(context).pop();
widget.outerContext.go('/rooms');
}
/// Save this file with a system call.

View file

@ -1,28 +1,25 @@
import 'dart:io';
import 'package:cross_file/cross_file.dart';
import 'package:matrix/matrix.dart';
import 'package:path_provider/path_provider.dart';
import 'package:video_compress/video_compress.dart';
import 'package:fluffychat/utils/platform_infos.dart';
extension ResizeImage on MatrixFile {
extension ResizeImage on XFile {
static const int max = 1200;
static const int quality = 40;
Future<MatrixVideoFile> resizeVideo() async {
final tmpDir = await getTemporaryDirectory();
final tmpFile = File('${tmpDir.path}/$name');
MediaInfo? mediaInfo;
await tmpFile.writeAsBytes(bytes);
try {
if (PlatformInfos.isMobile) {
// will throw an error e.g. on Android SDK < 18
mediaInfo = await VideoCompress.compressVideo(tmpFile.path);
mediaInfo = await VideoCompress.compressVideo(path);
}
} catch (e, s) {
Logs().w('Error while compressing video', e, s);
}
return MatrixVideoFile(
bytes: (await mediaInfo?.file?.readAsBytes()) ?? bytes,
bytes: (await mediaInfo?.file?.readAsBytes()) ?? await readAsBytes(),
name: name,
mimeType: mimeType,
width: mediaInfo?.width,
@ -33,13 +30,9 @@ extension ResizeImage on MatrixFile {
Future<MatrixImageFile?> getVideoThumbnail() async {
if (!PlatformInfos.isMobile) return null;
final tmpDir = await getTemporaryDirectory();
final tmpFile = File('${tmpDir.path}/$name');
if (await tmpFile.exists() == false) {
await tmpFile.writeAsBytes(bytes);
}
try {
final bytes = await VideoCompress.getByteThumbnail(tmpFile.path);
final bytes = await VideoCompress.getByteThumbnail(path);
if (bytes == null) return null;
return MatrixImageFile(
bytes: bytes,

View file

@ -231,7 +231,7 @@ packages:
source: hosted
version: "1.9.2"
cross_file:
dependency: transitive
dependency: "direct main"
description:
name: cross_file
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
@ -1228,7 +1228,7 @@ packages:
source: hosted
version: "2.0.0"
mime:
dependency: transitive
dependency: "direct main"
description:
name: mime
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"

View file

@ -17,6 +17,7 @@ dependencies:
callkeep: ^0.3.2
chewie: ^1.8.1
collection: ^1.18.0
cross_file: ^0.3.4+2
cupertino_icons: any
desktop_drop: ^0.4.4
desktop_notifications: ^0.6.3
@ -65,6 +66,7 @@ dependencies:
latlong2: ^0.9.1
linkify: ^5.0.0
matrix: ^0.33.0
mime: ^1.0.6
native_imaging: ^0.1.1
opus_caf_converter_dart: ^1.0.1
package_info_plus: ^6.0.0