mirror of
https://github.com/krille-chan/fluffychat
synced 2024-10-10 03:03:38 +00:00
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:
commit
b328e95980
9 changed files with 257 additions and 223 deletions
|
@ -2761,5 +2761,6 @@
|
||||||
"discoverHomeservers": "Discover homeservers",
|
"discoverHomeservers": "Discover homeservers",
|
||||||
"whatIsAHomeserver": "What is a homeserver?",
|
"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.",
|
"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..."
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import 'package:fluffychat/widgets/app_lock.dart';
|
||||||
import 'package:fluffychat/widgets/matrix.dart';
|
import 'package:fluffychat/widgets/matrix.dart';
|
||||||
import '../../utils/account_bundles.dart';
|
import '../../utils/account_bundles.dart';
|
||||||
import '../../utils/localized_exception_extension.dart';
|
import '../../utils/localized_exception_extension.dart';
|
||||||
import '../../utils/matrix_sdk_extensions/matrix_file_extension.dart';
|
|
||||||
import 'send_file_dialog.dart';
|
import 'send_file_dialog.dart';
|
||||||
import 'send_location_dialog.dart';
|
import 'send_location_dialog.dart';
|
||||||
|
|
||||||
|
@ -123,36 +122,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
void onDragDone(DropDoneDetails details) async {
|
void onDragDone(DropDoneDetails details) async {
|
||||||
setState(() => dragging = false);
|
setState(() => dragging = false);
|
||||||
if (details.files.isEmpty) return;
|
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(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: matrixFiles,
|
files: details.files,
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -510,36 +484,24 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
FilePicker.platform.pickFiles(
|
FilePicker.platform.pickFiles(
|
||||||
compressionQuality: 0,
|
compressionQuality: 0,
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
withData: true,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (result == null || result.files.isEmpty) return;
|
if (result == null || result.files.isEmpty) return;
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: result.files
|
files: result.xFiles,
|
||||||
.map(
|
|
||||||
(xfile) => MatrixFile(
|
|
||||||
bytes: xfile.bytes!,
|
|
||||||
name: xfile.name,
|
|
||||||
).detectFileType,
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendImageFromClipBoard(Uint8List? image) async {
|
void sendImageFromClipBoard(Uint8List? image) async {
|
||||||
|
if (image == null) return;
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [
|
files: [XFile.fromData(image)],
|
||||||
MatrixFile(
|
|
||||||
bytes: image!,
|
|
||||||
name: "image from Clipboard",
|
|
||||||
).detectFileType,
|
|
||||||
],
|
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -550,7 +512,6 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
FilePicker.platform.pickFiles(
|
FilePicker.platform.pickFiles(
|
||||||
compressionQuality: 0,
|
compressionQuality: 0,
|
||||||
type: FileType.image,
|
type: FileType.image,
|
||||||
withData: true,
|
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -559,14 +520,7 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: result.files
|
files: result.xFiles,
|
||||||
.map(
|
|
||||||
(xfile) => MatrixFile(
|
|
||||||
bytes: xfile.bytes!,
|
|
||||||
name: xfile.name,
|
|
||||||
).detectFileType,
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -577,16 +531,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
FocusScope.of(context).requestFocus(FocusNode());
|
FocusScope.of(context).requestFocus(FocusNode());
|
||||||
final file = await ImagePicker().pickImage(source: ImageSource.camera);
|
final file = await ImagePicker().pickImage(source: ImageSource.camera);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
final bytes = await file.readAsBytes();
|
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [
|
files: [file],
|
||||||
MatrixImageFile(
|
|
||||||
bytes: bytes,
|
|
||||||
name: file.path,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -600,16 +549,11 @@ class ChatController extends State<ChatPageWithRoom>
|
||||||
maxDuration: const Duration(minutes: 1),
|
maxDuration: const Duration(minutes: 1),
|
||||||
);
|
);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
final bytes = await file.readAsBytes();
|
|
||||||
await showAdaptiveDialog(
|
await showAdaptiveDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [
|
files: [file],
|
||||||
MatrixVideoFile(
|
|
||||||
bytes: bytes,
|
|
||||||
name: file.path,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -60,7 +60,10 @@ class ImageBubble extends StatelessWidget {
|
||||||
if (!tapToView) return;
|
if (!tapToView) return;
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => ImageViewer(event),
|
builder: (_) => ImageViewer(
|
||||||
|
event,
|
||||||
|
outerContext: context,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,25 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:cross_file/cross_file.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
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 'package:mime/mime.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/utils/error_reporter.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 'package:fluffychat/utils/size_string.dart';
|
||||||
import '../../utils/resize_image.dart';
|
import '../../utils/resize_video.dart';
|
||||||
|
|
||||||
class SendFileDialog extends StatefulWidget {
|
class SendFileDialog extends StatefulWidget {
|
||||||
final Room room;
|
final Room room;
|
||||||
final List<MatrixFile> files;
|
final List<XFile> files;
|
||||||
|
|
||||||
const SendFileDialog({
|
const SendFileDialog({
|
||||||
required this.room,
|
required this.room,
|
||||||
|
@ -33,158 +40,233 @@ class SendFileDialogState extends State<SendFileDialog> {
|
||||||
Future<void> _send() async {
|
Future<void> _send() async {
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
final scaffoldMessenger = ScaffoldMessenger.of(context);
|
||||||
final l10n = L10n.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(
|
|
||||||
context: context,
|
|
||||||
future: () async {
|
|
||||||
file = origImage ? file : await file.resizeVideo();
|
|
||||||
thumbnail = await file.getVideoThumbnail();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Navigator.of(context, rootNavigator: false).pop();
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
var sendStr = L10n.of(context)!.sendFile;
|
var sendStr = L10n.of(context)!.sendFile;
|
||||||
final allFilesAreImages =
|
final uniqueMimeType = widget.files
|
||||||
widget.files.every((file) => file is MatrixImageFile);
|
.map((file) => file.mimeType ?? lookupMimeType(file.path))
|
||||||
final sizeString = widget.files
|
.toSet()
|
||||||
.fold<double>(0, (p, file) => p + file.bytes.length)
|
.singleOrNull;
|
||||||
.sizeString;
|
|
||||||
final fileName = widget.files.length == 1
|
final fileName = widget.files.length == 1
|
||||||
? widget.files.single.name
|
? widget.files.single.name
|
||||||
: L10n.of(context)!.countFiles(widget.files.length.toString());
|
: L10n.of(context)!.countFiles(widget.files.length.toString());
|
||||||
|
|
||||||
if (allFilesAreImages) {
|
if (uniqueMimeType?.startsWith('image') ?? false) {
|
||||||
sendStr = L10n.of(context)!.sendImage;
|
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;
|
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;
|
sendStr = L10n.of(context)!.sendVideo;
|
||||||
}
|
}
|
||||||
Widget contentWidget;
|
|
||||||
if (allFilesAreImages) {
|
return FutureBuilder<String>(
|
||||||
contentWidget = Column(
|
future: _calcCombinedFileSize(),
|
||||||
mainAxisSize: MainAxisSize.min,
|
builder: (context, snapshot) {
|
||||||
children: <Widget>[
|
final sizeString =
|
||||||
Flexible(
|
snapshot.data ?? L10n.of(context)!.calculatingFileSize;
|
||||||
child: Material(
|
|
||||||
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
Widget contentWidget;
|
||||||
elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
|
if (uniqueMimeType?.startsWith('image') ?? false) {
|
||||||
shadowColor: theme.appBarTheme.shadowColor,
|
contentWidget = Column(
|
||||||
clipBehavior: Clip.hardEdge,
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Image.memory(
|
children: <Widget>[
|
||||||
widget.files.first.bytes,
|
Flexible(
|
||||||
fit: BoxFit.contain,
|
child: Material(
|
||||||
height: 256,
|
borderRadius: BorderRadius.circular(AppConfig.borderRadius),
|
||||||
|
elevation: theme.appBarTheme.scrolledUnderElevation ?? 4,
|
||||||
|
shadowColor: theme.appBarTheme.shadowColor,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CupertinoSwitch(
|
||||||
|
value: origImage,
|
||||||
|
onChanged: (v) => setState(() => origImage = v),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
L10n.of(context)!.sendOriginal,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(sizeString),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
final fileNameParts = fileName.split('.');
|
||||||
|
contentWidget = SizedBox(
|
||||||
|
width: 256,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
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: [
|
||||||
|
CupertinoSwitch(
|
||||||
|
value: origImage,
|
||||||
|
onChanged: (v) => setState(() => origImage = v),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
L10n.of(context)!.sendOriginal,
|
||||||
|
style:
|
||||||
|
const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
Text(sizeString),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
const SizedBox(height: 16),
|
}
|
||||||
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
|
return AlertDialog.adaptive(
|
||||||
Row(
|
title: Text(sendStr),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
content: contentWidget,
|
||||||
children: [
|
actions: <Widget>[
|
||||||
CupertinoSwitch(
|
TextButton(
|
||||||
value: origImage,
|
onPressed: () {
|
||||||
onChanged: (v) => setState(() => origImage = v),
|
// just close the dialog
|
||||||
),
|
Navigator.of(context, rootNavigator: false).pop();
|
||||||
const SizedBox(width: 16),
|
},
|
||||||
Expanded(
|
child: Text(L10n.of(context)!.cancel),
|
||||||
child: Column(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
TextButton(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
onPressed: _send,
|
||||||
children: [
|
child: Text(L10n.of(context)!.send),
|
||||||
Text(
|
),
|
||||||
L10n.of(context)!.sendOriginal,
|
],
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
);
|
||||||
),
|
},
|
||||||
Text(sizeString),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else if (widget.files.every((file) => file is MatrixVideoFile)) {
|
|
||||||
contentWidget = Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: <Widget>[
|
|
||||||
Text(fileName),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
// Workaround for SwitchListTile.adaptive crashes in CupertinoDialog
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
CupertinoSwitch(
|
|
||||||
value: origImage,
|
|
||||||
onChanged: (v) => setState(() => origImage = v),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
L10n.of(context)!.sendOriginal,
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
Text(sizeString),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
contentWidget = Text('$fileName ($sizeString)');
|
|
||||||
}
|
|
||||||
return AlertDialog.adaptive(
|
|
||||||
title: Text(sendStr),
|
|
||||||
content: contentWidget,
|
|
||||||
actions: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
// just close the dialog
|
|
||||||
Navigator.of(context, rootNavigator: false).pop();
|
|
||||||
},
|
|
||||||
child: Text(L10n.of(context)!.cancel),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: _send,
|
|
||||||
child: Text(L10n.of(context)!.send),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:adaptive_dialog/adaptive_dialog.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_gen/gen_l10n/l10n.dart';
|
||||||
import 'package:flutter_shortcuts/flutter_shortcuts.dart';
|
import 'package:flutter_shortcuts/flutter_shortcuts.dart';
|
||||||
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
import 'package:future_loading_dialog/future_loading_dialog.dart';
|
||||||
|
@ -205,7 +206,13 @@ class ChatListController extends State<ChatList>
|
||||||
context: context,
|
context: context,
|
||||||
useRootNavigator: false,
|
useRootNavigator: false,
|
||||||
builder: (c) => SendFileDialog(
|
builder: (c) => SendFileDialog(
|
||||||
files: [shareFile],
|
files: [
|
||||||
|
XFile.fromData(
|
||||||
|
shareFile.bytes,
|
||||||
|
name: shareFile.name,
|
||||||
|
mimeType: shareFile.mimeType,
|
||||||
|
),
|
||||||
|
],
|
||||||
room: room,
|
room: room,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,8 +10,9 @@ import '../../utils/matrix_sdk_extensions/event_extension.dart';
|
||||||
|
|
||||||
class ImageViewer extends StatefulWidget {
|
class ImageViewer extends StatefulWidget {
|
||||||
final Event event;
|
final Event event;
|
||||||
|
final BuildContext outerContext;
|
||||||
|
|
||||||
const ImageViewer(this.event, {super.key});
|
const ImageViewer(this.event, {required this.outerContext, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ImageViewerController createState() => ImageViewerController();
|
ImageViewerController createState() => ImageViewerController();
|
||||||
|
@ -20,8 +21,9 @@ class ImageViewer extends StatefulWidget {
|
||||||
class ImageViewerController extends State<ImageViewer> {
|
class ImageViewerController extends State<ImageViewer> {
|
||||||
/// Forward this image to another room.
|
/// Forward this image to another room.
|
||||||
void forwardAction() {
|
void forwardAction() {
|
||||||
Matrix.of(context).shareContent = widget.event.content;
|
Matrix.of(widget.outerContext).shareContent = widget.event.content;
|
||||||
context.go('/rooms');
|
Navigator.of(context).pop();
|
||||||
|
widget.outerContext.go('/rooms');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save this file with a system call.
|
/// Save this file with a system call.
|
||||||
|
|
|
@ -1,28 +1,25 @@
|
||||||
import 'dart:io';
|
import 'package:cross_file/cross_file.dart';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:video_compress/video_compress.dart';
|
import 'package:video_compress/video_compress.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
|
|
||||||
extension ResizeImage on MatrixFile {
|
extension ResizeImage on XFile {
|
||||||
static const int max = 1200;
|
static const int max = 1200;
|
||||||
static const int quality = 40;
|
static const int quality = 40;
|
||||||
|
|
||||||
Future<MatrixVideoFile> resizeVideo() async {
|
Future<MatrixVideoFile> resizeVideo() async {
|
||||||
final tmpDir = await getTemporaryDirectory();
|
|
||||||
final tmpFile = File('${tmpDir.path}/$name');
|
|
||||||
MediaInfo? mediaInfo;
|
MediaInfo? mediaInfo;
|
||||||
await tmpFile.writeAsBytes(bytes);
|
|
||||||
try {
|
try {
|
||||||
// will throw an error e.g. on Android SDK < 18
|
if (PlatformInfos.isMobile) {
|
||||||
mediaInfo = await VideoCompress.compressVideo(tmpFile.path);
|
// will throw an error e.g. on Android SDK < 18
|
||||||
|
mediaInfo = await VideoCompress.compressVideo(path);
|
||||||
|
}
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Logs().w('Error while compressing video', e, s);
|
Logs().w('Error while compressing video', e, s);
|
||||||
}
|
}
|
||||||
return MatrixVideoFile(
|
return MatrixVideoFile(
|
||||||
bytes: (await mediaInfo?.file?.readAsBytes()) ?? bytes,
|
bytes: (await mediaInfo?.file?.readAsBytes()) ?? await readAsBytes(),
|
||||||
name: name,
|
name: name,
|
||||||
mimeType: mimeType,
|
mimeType: mimeType,
|
||||||
width: mediaInfo?.width,
|
width: mediaInfo?.width,
|
||||||
|
@ -33,13 +30,9 @@ extension ResizeImage on MatrixFile {
|
||||||
|
|
||||||
Future<MatrixImageFile?> getVideoThumbnail() async {
|
Future<MatrixImageFile?> getVideoThumbnail() async {
|
||||||
if (!PlatformInfos.isMobile) return null;
|
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 {
|
try {
|
||||||
final bytes = await VideoCompress.getByteThumbnail(tmpFile.path);
|
final bytes = await VideoCompress.getByteThumbnail(path);
|
||||||
if (bytes == null) return null;
|
if (bytes == null) return null;
|
||||||
return MatrixImageFile(
|
return MatrixImageFile(
|
||||||
bytes: bytes,
|
bytes: bytes,
|
|
@ -231,7 +231,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.2"
|
version: "1.9.2"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||||
|
@ -1228,7 +1228,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.0"
|
version: "2.0.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||||
|
|
|
@ -17,6 +17,7 @@ dependencies:
|
||||||
callkeep: ^0.3.2
|
callkeep: ^0.3.2
|
||||||
chewie: ^1.8.1
|
chewie: ^1.8.1
|
||||||
collection: ^1.18.0
|
collection: ^1.18.0
|
||||||
|
cross_file: ^0.3.4+2
|
||||||
cupertino_icons: any
|
cupertino_icons: any
|
||||||
desktop_drop: ^0.4.4
|
desktop_drop: ^0.4.4
|
||||||
desktop_notifications: ^0.6.3
|
desktop_notifications: ^0.6.3
|
||||||
|
@ -65,6 +66,7 @@ dependencies:
|
||||||
latlong2: ^0.9.1
|
latlong2: ^0.9.1
|
||||||
linkify: ^5.0.0
|
linkify: ^5.0.0
|
||||||
matrix: ^0.33.0
|
matrix: ^0.33.0
|
||||||
|
mime: ^1.0.6
|
||||||
native_imaging: ^0.1.1
|
native_imaging: ^0.1.1
|
||||||
opus_caf_converter_dart: ^1.0.1
|
opus_caf_converter_dart: ^1.0.1
|
||||||
package_info_plus: ^6.0.0
|
package_info_plus: ^6.0.0
|
||||||
|
|
Loading…
Reference in a new issue