From 4448f183ac3b31f3a9991638bf7db2e56f4b0f77 Mon Sep 17 00:00:00 2001 From: Henry-Hiles Date: Wed, 11 Nov 2026 15:40:01 -0500 Subject: [PATCH] preview attachments where possible Fixes #166 --- lib/widgets/composer/composer.dart | 50 +++++++++++++++---- lib/widgets/renderers/message.dart | 62 ++---------------------- lib/widgets/renderers/message_media.dart | 55 +++++++++++++++++++++ 3 files changed, 99 insertions(+), 68 deletions(-) create mode 100644 lib/widgets/renderers/message_media.dart diff --git a/lib/widgets/composer/composer.dart b/lib/widgets/composer/composer.dart index 4c3caf6..0b0a7db 100644 --- a/lib/widgets/composer/composer.dart +++ b/lib/widgets/composer/composer.dart @@ -33,6 +33,8 @@ import "package:nexus/controllers/power_level.dart"; import "package:nexus/controllers/image_packs.dart"; import "package:nexus/models/content/image_pack.dart"; import "package:nexus/models/content/message.dart"; +import "package:nexus/widgets/renderers/message_media.dart"; +import "package:nexus/helpers/extensions/is_media.dart"; import "package:nexus/models/content/poll_start.dart"; import "package:nexus/models/image_pack.dart"; import "package:nexus/widgets/emoji/mxc_emoji.dart"; @@ -482,19 +484,47 @@ class const Composer( if (attachment != null) Card( margin: .only(bottom: 8), - child: ListTile( - leading: attachment.$2 == null - ? SizedBox( + child: Column( + children: [ + ListTile( + leading: switch (attachment.$2) { + null => SizedBox( width: 16, height: 16, child: CircularProgressIndicator(), - ) - : Icon(Icons.file_copy), - title: Text(attachment.$1), - trailing: IconButton( - onPressed: () => ref.invalidate(attachmentProvider), - icon: Icon(Icons.close), - ), + ), + ImageMessageContent() => Icon(Icons.image), + VideoMessageContent() => Icon(Icons.movie), + AudioMessageContent() => Icon(Icons.audiotrack), + _ => Icon(Icons.file_copy), + }, + title: Text(attachment.$1), + trailing: IconButton( + onPressed: () => ref.invalidate(attachmentProvider), + icon: Icon(Icons.close), + ), + ), + AnimatedSize( + duration: Durations.short4, + alignment: .topCenter, + child: switch (attachment.$2) { + final content? + when content is! FileMessageContent && + content.isMedia => + Padding( + padding: EdgeInsets.all(12).copyWith(top: 0), + child: Align( + alignment: .centerLeft, + child: ConstrainedBox( + constraints: .loose(.square(300)), + child: MessageMedia(content), + ), + ), + ), + _ => SizedBox(width: .infinity), + }, + ), + ], ), ), AnimatedSize( diff --git a/lib/widgets/renderers/message.dart b/lib/widgets/renderers/message.dart index 40ee5e7..5b25a35 100644 --- a/lib/widgets/renderers/message.dart +++ b/lib/widgets/renderers/message.dart @@ -12,7 +12,6 @@ import "package:nexus/models/location.dart"; import "package:nexus/models/content/poll_start.dart"; import "package:nexus/models/content/sticker.dart"; import "package:nexus/models/event.dart"; -import "package:nexus/widgets/renderers/file_card.dart"; import "package:nexus/widgets/html/html.dart"; import "package:nexus/widgets/lazy_loading/message_avatar.dart"; import "package:nexus/widgets/lazy_loading/message_displayname.dart"; @@ -24,8 +23,8 @@ import "package:nexus/widgets/reactions/reaction_row.dart"; import "package:nexus/widgets/common/timestamp.dart"; import "package:nexus/widgets/renderers/url_preview.dart"; import "package:nexus/widgets/renderers/event_preview.dart"; -import "package:nexus/widgets/players/video.dart"; -import "package:nexus/widgets/players/audio.dart"; +import "package:nexus/widgets/renderers/message_media.dart"; +import "package:nexus/helpers/extensions/is_media.dart"; import "package:nexus/widgets/renderers/poll.dart"; import "package:nexus/widgets/threads/thread_summary.dart"; @@ -125,63 +124,10 @@ class const MessageRenderer( ), if (!textOnly) ...[ - if (event.content - case ImageMessageContent(:final url) || - FileMessageContent(:final url) || - VideoMessageContent(:final url) || - AudioMessageContent(:final url)) + if (content.isMedia) ConstrainedBox( constraints: .loose(.square(500)), - child: switch (event.content) { - VideoMessageContent( - :final info, - :final file, - :final filename, - :final body, - ) => - VideoPlayer( - url, - info, - encrypted: file != null, - filename: filename ?? body, - ), - AudioMessageContent( - :final info, - :final file, - :final filename, - :final body, - ) => - AudioPlayer( - url, - info, - encrypted: file != null, - filename: filename ?? body, - ), - FileMessageContent( - :final info, - :final filename, - :final file, - ) => - FileCard( - url, - info, - filename: filename, - encrypted: file != null, - ), - ImageMessageContent( - :final info, - :final file, - :final filename, - :final body, - ) => - MessageImage( - url, - info: info, - encrypted: file != null, - filename: filename ?? body, - ), - _ => SizedBox.shrink(), - }, + child: MessageMedia(content), ), if (event.lastEditRowId != 0) diff --git a/lib/widgets/renderers/message_media.dart b/lib/widgets/renderers/message_media.dart new file mode 100644 index 0000000..1f7e6b9 --- /dev/null +++ b/lib/widgets/renderers/message_media.dart @@ -0,0 +1,55 @@ +import "package:material_ui/material_ui.dart"; +import "package:nexus/models/content/message.dart"; +import "package:nexus/widgets/players/audio.dart"; +import "package:nexus/widgets/players/video.dart"; +import "package:nexus/widgets/renderers/file_card.dart"; +import "package:nexus/widgets/renderers/message_image.dart"; + +final class const MessageMedia(final MessageContent content, {super.key}) + extends StatelessWidget { + @override + Widget build(BuildContext context) => switch (content) { + VideoMessageContent( + :final url, + :final info, + :final file, + :final filename, + :final body, + ) => + VideoPlayer( + url, + info, + encrypted: file != null, + filename: filename ?? body, + ), + AudioMessageContent( + :final url, + :final info, + :final file, + :final filename, + :final body, + ) => + AudioPlayer( + url, + info, + encrypted: file != null, + filename: filename ?? body, + ), + FileMessageContent(:final url, :final info, :final filename, :final file) => + FileCard(url, info, filename: filename, encrypted: file != null), + ImageMessageContent( + :final url, + :final info, + :final file, + :final filename, + :final body, + ) => + MessageImage( + url, + info: info, + encrypted: file != null, + filename: filename ?? body, + ), + _ => SizedBox.shrink(), + }; +}