diff --git a/lib/app/layouts/conversation_view/widgets/message/attachment/other_file.dart b/lib/app/layouts/conversation_view/widgets/message/attachment/other_file.dart index 12b3f2c295..f49e21f25c 100644 --- a/lib/app/layouts/conversation_view/widgets/message/attachment/other_file.dart +++ b/lib/app/layouts/conversation_view/widgets/message/attachment/other_file.dart @@ -7,6 +7,7 @@ import 'package:bluebubbles/helpers/helpers.dart'; import 'package:bluebubbles/database/models.dart'; import 'package:bluebubbles/services/services.dart'; import 'package:bluebubbles/utils/share.dart'; +import 'package:bluebubbles/utils/attachment_mime_utils.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -59,7 +60,12 @@ class OtherFile extends StatelessWidget { launchUrl(Uri.file(_file.path)); } else { try { - final res = await OpenFilex.open("${fs.appDocDir.path}/attachments/${attachment.guid!}/${basename(file.path!)}"); + final res = await OpenFilex.open( + file.path!, + type: attachment.mimeType ?? + resolveAttachmentMimeType(file.name, file.path) ?? + "application/octet-stream", + ); if (res.type == ResultType.noAppToOpen) { showSnackbar('Error', "No handler for this file type! Using share menu instead."); await Future.delayed(const Duration(seconds: 1)); diff --git a/lib/utils/attachment_mime_utils.dart b/lib/utils/attachment_mime_utils.dart new file mode 100644 index 0000000000..c1276ecdea --- /dev/null +++ b/lib/utils/attachment_mime_utils.dart @@ -0,0 +1,5 @@ +import 'package:mime_type/mime_type.dart'; + +String? resolveAttachmentMimeType(String name, String? path) { + return mime(name) ?? (path == null ? null : mime(path)); +} diff --git a/test/utils/attachment_mime_utils_test.dart b/test/utils/attachment_mime_utils_test.dart new file mode 100644 index 0000000000..6b4cec6704 --- /dev/null +++ b/test/utils/attachment_mime_utils_test.dart @@ -0,0 +1,46 @@ +import 'package:bluebubbles/utils/attachment_mime_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('resolveAttachmentMimeType', () { + test('resolves PDF filenames', () { + expect(resolveAttachmentMimeType('report.pdf', null), 'application/pdf'); + }); + + test('resolves DOCX filenames', () { + expect( + resolveAttachmentMimeType('report.docx', null), + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ); + }); + + test('resolves XLSX filenames', () { + expect( + resolveAttachmentMimeType('report.xlsx', null), + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + ); + }); + + test('resolves PPTX filenames', () { + expect( + resolveAttachmentMimeType('report.pptx', null), + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ); + }); + + test('resolves plain text filenames', () { + expect(resolveAttachmentMimeType('notes.txt', null), 'text/plain'); + }); + + test('leaves unknown binary filenames unresolved', () { + expect(resolveAttachmentMimeType('payload.unknown', null), isNull); + }); + + test('falls back to a path extension when the filename has none', () { + expect( + resolveAttachmentMimeType('document', '/tmp/document.pdf'), + 'application/pdf', + ); + }); + }); +}