Skip to content

TW-1806: fix image weird display on web #1866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions lib/data/network/extensions/file_info_extension.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import 'package:flutter/foundation.dart';
import 'package:fluffychat/utils/mime_type_uitls.dart';
import 'package:matrix/matrix.dart';
import 'package:mime/mime.dart';

extension FileInfoExtension on FileInfo {
String get fileExtension => fileName.split('.').last;

String get mimeType =>
lookupMimeType(kIsWeb ? fileName : filePath) ??
'application/octet-stream';
String get mimeType => MimeTypeUitls.instance.getTwakeMimeType(filePath);

Map<String, dynamic> get metadata => ({
'mimetype': mimeType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:file_picker/file_picker.dart';
import 'package:fluffychat/utils/mime_type_uitls.dart';
import 'package:matrix/matrix.dart';

extension PlatformFileListExtension on PlatformFile {
Expand All @@ -9,6 +10,7 @@ extension PlatformFileListExtension on PlatformFile {
bytes: bytes,
name: name,
filePath: path ?? '$temporaryDirectoryPath/$name',
mimeType: MimeTypeUitls.instance.getTwakeMimeType(name),
readStream: readStream,
sizeInBytes: size,
);
Expand All @@ -21,6 +23,7 @@ extension PlatformFileListExtension on PlatformFile {
filePath: '',
readStream: readStream,
sizeInBytes: size,
mimeType: MimeTypeUitls.instance.getTwakeMimeType(name),
);
}

Expand Down
25 changes: 25 additions & 0 deletions lib/domain/model/preview_file/supported_preview_file_types.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:fluffychat/resource/image_paths.dart';
import 'package:fluffychat/utils/platform_infos.dart';

enum SupportedIconFileTypesEnum {
image,
Expand Down Expand Up @@ -40,14 +41,38 @@ class SupportedPreviewFileTypes {
static const imageMimeTypes = [
'image/bmp',
'image/jpeg',
'image/jpg',
'image/gif',
'image/png',
];

static const imageMimeTypesAndroid = [
...imageMimeTypes,
'image/webp',
];

static const imageMimeTypesIOS = [
...imageMimeTypes,
'image/heic',
'image/heif',
];

static List<String> get crossPlatformImageMimeTypes {
if (PlatformInfos.isAndroid) {
return imageMimeTypesAndroid;
} else if (PlatformInfos.isIOS) {
return imageMimeTypesIOS;
} else {
return imageMimeTypes;
}
}

static const videoMimeTypes = [
'video/mp4',
'video/3gpp',
'video/quicktime',
'video/mov',
'video/mpeg',
];

static const audioMimeTypes = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:fluffychat/pages/chat/events/images_builder/image_placeholder.dart';
import 'package:fluffychat/pages/chat/events/message_content_style.dart';
import 'package:fluffychat/utils/extension/mime_type_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_blurhash/flutter_blurhash.dart';
import 'package:matrix/matrix.dart';
import 'package:flutter_avif/flutter_avif.dart';

class UnencryptedImageWidget extends StatelessWidget {
const UnencryptedImageWidget({
Expand All @@ -23,6 +25,17 @@ class UnencryptedImageWidget extends StatelessWidget {

@override
Widget build(BuildContext context) {
if (event.mimeType == TwakeMimeTypeExtension.avifMimeType) {
return AvifImage.network(
event
.attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)!
.getDownloadLink(event.room.client)
.toString(),
height: height,
width: width,
fit: BoxFit.cover,
);
}
return Image.network(
event
.attachmentOrThumbnailMxcUrl(getThumbnail: isThumbnail)!
Expand Down Expand Up @@ -51,8 +64,22 @@ class UnencryptedImageWidget extends StatelessWidget {
cacheHeight: (height * MediaQuery.devicePixelRatioOf(context)).round(),
filterQuality: FilterQuality.medium,
errorBuilder: (context, error, stackTrace) {
return BlurHash(
hash: event.blurHash ?? MessageContentStyle.defaultBlurHash,
return Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: width,
height: height,
child: BlurHash(
hash: event.blurHash ?? MessageContentStyle.defaultBlurHash,
),
),
Icon(
Icons.error,
size: MessageContentStyle.iconErrorSize,
color: Theme.of(context).colorScheme.onError,
),
],
);
},
loadingBuilder: (context, child, loadingProgress) {
Expand Down
2 changes: 2 additions & 0 deletions lib/pages/chat/events/message_content_style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,6 @@ class MessageContentStyle {
);

static const blurhashSize = 32;

static const iconErrorSize = 36.0;
}
28 changes: 28 additions & 0 deletions lib/pages/image_viewer/image_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class ImageViewerController extends State<ImageViewer> {

String? filePath;

String? thumbnailFilePath;

final downloadMediaFileInteractor = getIt.get<DownloadMediaFileInteractor>();

StreamSubscription? streamSubcription;
Expand All @@ -51,6 +53,7 @@ class ImageViewerController extends State<ImageViewer> {
super.initState();
if (!PlatformInfos.isWeb && widget.event != null) {
handleDownloadFile(widget.event!);
handleDownloadThumbnailFile(widget.event!);
}
}

Expand Down Expand Up @@ -78,6 +81,31 @@ class ImageViewerController extends State<ImageViewer> {
}
}

Future<void> handleDownloadThumbnailFile(Event event) async {
try {
streamSubcription = downloadMediaFileInteractor
.execute(event: event, getThumbnail: true)
.listen((state) {
state.fold(
(failure) {
if (failure is DownloadMediaFileFailure) {
Logs().e('Error downloading file', failure.exception);
}
},
(success) {
if (success is DownloadMediaFileSuccess) {
setState(() {
thumbnailFilePath = success.filePath;
});
}
},
);
});
} catch (e) {
Logs().e('Error downloading file', e);
}
}

@override
void dispose() {
streamSubcription?.cancel();
Expand Down
29 changes: 29 additions & 0 deletions lib/pages/image_viewer/image_viewer_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ import 'dart:typed_data';

import 'package:fluffychat/pages/image_viewer/image_viewer_style.dart';
import 'package:fluffychat/pages/image_viewer/media_viewer_app_bar.dart';
import 'package:fluffychat/utils/extension/mime_type_extension.dart';
import 'package:fluffychat/utils/extension/value_notifier_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_avif/flutter_avif.dart';
import 'package:matrix/matrix.dart';

import 'image_viewer.dart';
Expand Down Expand Up @@ -117,6 +120,17 @@ class _ImageWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (PlatformInfos.isWeb) {
if (event.mimeType == TwakeMimeTypeExtension.avifMimeType) {
return AvifImage.network(
event
.attachmentOrThumbnailMxcUrl()!
.getDownloadLink(event.room.client)
.toString(),
height: height,
width: width,
fit: BoxFit.cover,
);
}
return FutureBuilder(
future: event.downloadAndDecryptAttachment(
getThumbnail: true,
Expand All @@ -135,10 +149,25 @@ class _ImageWidget extends StatelessWidget {
);
} else {
if (controller.filePath != null) {
if (event.mimeType == TwakeMimeTypeExtension.avifMimeType) {
return AvifImage.file(
File(controller.filePath!),
height: height,
width: width,
fit: BoxFit.cover,
);
}
return Image.file(
File(controller.filePath!),
fit: BoxFit.contain,
filterQuality: FilterQuality.none,
errorBuilder: (context, error, stackTrace) {
return Image.file(
File(controller.thumbnailFilePath!),
fit: BoxFit.contain,
filterQuality: FilterQuality.none,
);
},
);
} else {
return const CupertinoActivityIndicator(
Expand Down
23 changes: 22 additions & 1 deletion lib/presentation/extensions/send_file_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:fluffychat/presentation/extensions/image_extension.dart';
import 'package:fluffychat/presentation/fake_sending_file_info.dart';
import 'package:fluffychat/presentation/model/file/file_asset_entity.dart';
import 'package:fluffychat/utils/date_time_extension.dart';
import 'package:fluffychat/utils/extension/mime_type_extension.dart';
import 'package:fluffychat/utils/manager/storage_directory_manager.dart';
import 'package:flutter/widgets.dart';
import 'package:image/image.dart' as img;
Expand Down Expand Up @@ -70,8 +71,25 @@ extension SendFileExtension on Room {
rethrow;
}

final encryptedService = EncryptedService();
final tempDir = await getTemporaryDirectory();

if (TwakeMimeTypeExtension.heicMimeTypes.contains(fileInfo.mimeType) &&
fileInfo is ImageFileInfo) {
final formattedDateTime = DateTime.now().getFormattedCurrentDateTime();
final targetPath =
await File('${tempDir.path}/$formattedDateTime${fileInfo.fileName}')
.create();
await _generateThumbnail(fileInfo, targetPath: targetPath.path);
fileInfo = ImageFileInfo(
fileInfo.fileName,
targetPath.path,
await targetPath.length(),
width: fileInfo.width,
height: fileInfo.height,
);
}

final encryptedService = EncryptedService();
final formattedDateTime = DateTime.now().getFormattedCurrentDateTime();
final tempEncryptedFile =
await File('${tempDir.path}/$formattedDateTime${fileInfo.fileName}')
Expand Down Expand Up @@ -358,6 +376,9 @@ extension SendFileExtension on Room {
'msgtype': messageType,
'body': fileInfo.fileName,
'filename': fileInfo.fileName,
'info': {
...fileInfo.metadata,
},
},
type: EventTypes.Message,
eventId: txid,
Expand Down
8 changes: 5 additions & 3 deletions lib/presentation/mixins/paste_image_mixin.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:fluffychat/pages/chat/send_file_dialog/send_file_dialog.dart';
import 'package:fluffychat/presentation/enum/chat/send_media_with_caption_status_enum.dart';
import 'package:fluffychat/utils/clipboard.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_file_extension.dart';
import 'package:fluffychat/utils/mime_type_uitls.dart';
import 'package:fluffychat/utils/platform_infos.dart';
import 'package:fluffychat/utils/twake_snackbar.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -35,11 +37,11 @@ mixin PasteImageMixin {
(matrixFile) => matrixFile != null,
)
.map(
(matrixFile) => MatrixImageFile(
(matrixFile) => MatrixFile(
name: matrixFile!.name,
mimeType: matrixFile.mimeType,
mimeType: MimeTypeUitls.instance.getTwakeMimeType(matrixFile.name),
bytes: matrixFile.bytes,
),
).detectFileType,
)
.cast<MatrixImageFile>()
.toList();
Expand Down
15 changes: 6 additions & 9 deletions lib/presentation/mixins/send_files_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,12 @@ mixin SendFilesMixin {
allowMultiple: true,
);
final temporaryDirectory = await getTemporaryDirectory();
fileInfos ??= result?.files
.map(
(xFile) => FileInfo.fromMatrixFile(
xFile.toMatrixFileOnMobile(
temporaryDirectoryPath: temporaryDirectory.path,
),
),
)
.toList();
fileInfos ??= result?.files.map((xFile) {
final matrixFile = xFile.toMatrixFileOnMobile(
temporaryDirectoryPath: temporaryDirectory.path,
);
return FileInfo.fromMatrixFile(matrixFile);
}).toList();

if (fileInfos == null || fileInfos.isEmpty == true) return;
onSendFileCallback?.call();
Expand Down
20 changes: 18 additions & 2 deletions lib/utils/extension/mime_type_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ import 'package:matrix/matrix.dart';
typedef TwakeMimeType = String?;

extension TwakeMimeTypeExtension on TwakeMimeType {
static const String defaultUnsupportedImageMimeType = 'file/image';

static const String defaultUnsupportedVideoMimeType = 'file/video';

bool isAndroidSupportedPreview() =>
SupportedPreviewFileTypes.androidSupportedTypes.contains(this);

bool isIOSSupportedPreview() =>
SupportedPreviewFileTypes.iOSSupportedTypes.containsKey(this);

bool isImageFile() => SupportedPreviewFileTypes.imageMimeTypes.contains(this);
bool isImageFile() =>
SupportedPreviewFileTypes.imageMimeTypes.contains(this) ||
defaultUnsupportedImageMimeType == this;

bool isDocFile({String? fileType}) =>
SupportedPreviewFileTypes.docMimeTypes.contains(this) ||
Expand All @@ -39,7 +45,10 @@ extension TwakeMimeTypeExtension on TwakeMimeType {
SupportedPreviewFileTypes.zipFileTypes
.contains(fileType.toLowerCase());

bool isVideoFile() => SupportedPreviewFileTypes.videoMimeTypes.contains(this);
bool isVideoFile() {
return SupportedPreviewFileTypes.videoMimeTypes.contains(this) ||
defaultUnsupportedVideoMimeType == this;
}

bool isPdfFile({String? fileType}) =>
SupportedPreviewFileTypes.pdfMimeTypes.contains(this) ||
Expand Down Expand Up @@ -96,4 +105,11 @@ extension TwakeMimeTypeExtension on TwakeMimeType {
return L10n.of(context)!.file.toUpperCase();
}
}

static const String avifMimeType = 'image/avif';

static const List<String> heicMimeTypes = [
'image/heic',
'image/heif',
];
}
Loading