diff --git a/lib/pages/chat/events/audio_player.dart b/lib/pages/chat/events/audio_player.dart index 1fb65dc24..87aa1a765 100644 --- a/lib/pages/chat/events/audio_player.dart +++ b/lib/pages/chat/events/audio_player.dart @@ -4,17 +4,16 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:just_audio/just_audio.dart'; import 'package:matrix/matrix.dart'; import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat/events/html_message.dart'; import 'package:fluffychat/utils/error_reporter.dart'; +import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; -import 'package:fluffychat/utils/url_launcher.dart'; import '../../../utils/matrix_sdk_extensions/event_extension.dart'; class AudioPlayerWidget extends StatefulWidget { @@ -240,17 +239,11 @@ class AudioPlayerState extends State { final statusText = this.statusText ??= _durationString ?? '00:00'; final audioPlayer = this.audioPlayer; - final body = widget.event.content.tryGet('body') ?? - widget.event.content.tryGet('filename'); - final displayBody = body != null && - body.isNotEmpty && - widget.event.content['org.matrix.msc1767.audio'] == null; + final fileDescription = widget.event.fileDescription; final wavePosition = (currentPosition / maxPosition) * AudioPlayerWidget.wavesCount; - final fontSize = 12 * AppConfig.fontSizeFactor; - return Padding( padding: const EdgeInsets.all(12.0), child: Column( @@ -380,22 +373,12 @@ class AudioPlayerState extends State { ], ), ), - if (displayBody) ...[ + if (fileDescription != null) ...[ const SizedBox(height: 8), - Linkify( - text: body, - style: TextStyle( - color: widget.color, - fontSize: fontSize, - ), - options: const LinkifyOptions(humanize: false), - linkStyle: TextStyle( - color: widget.color.withAlpha(150), - fontSize: fontSize, - decoration: TextDecoration.underline, - decorationColor: widget.color.withAlpha(150), - ), - onOpen: (url) => UrlLauncher(context, url.url).launchUrl(), + HtmlMessage( + html: fileDescription, + textColor: widget.color, + room: widget.event.room, ), ], ], diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart index e175f5e4c..605561ae2 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/image_bubble.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pages/chat/events/html_message.dart'; import 'package:fluffychat/pages/image_viewer/image_viewer.dart'; +import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/widgets/mxc_image.dart'; import '../../../widgets/blur_hash.dart'; @@ -13,6 +15,7 @@ class ImageBubble extends StatelessWidget { final BoxFit fit; final bool maxSize; final Color? backgroundColor; + final Color? textColor; final bool thumbnailOnly; final bool animated; final double width; @@ -34,6 +37,7 @@ class ImageBubble extends StatelessWidget { this.onTap, this.borderRadius, this.timeline, + this.textColor, super.key, }); @@ -76,35 +80,54 @@ class ImageBubble extends StatelessWidget { final borderRadius = this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius); - return Material( - color: Colors.transparent, - clipBehavior: Clip.hardEdge, - shape: RoundedRectangleBorder( - borderRadius: borderRadius, - side: BorderSide( - color: event.messageType == MessageTypes.Sticker - ? Colors.transparent - : theme.dividerColor, - ), - ), - child: InkWell( - onTap: () => _onTap(context), - borderRadius: borderRadius, - child: Hero( - tag: event.eventId, - child: MxcImage( - event: event, - width: width, - height: height, - fit: fit, - animated: animated, - isThumbnail: thumbnailOnly, - placeholder: event.messageType == MessageTypes.Sticker - ? null - : _buildPlaceholder, + + final fileDescription = event.fileDescription; + final textColor = this.textColor; + + return Column( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + Material( + color: Colors.transparent, + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: borderRadius, + side: BorderSide( + color: event.messageType == MessageTypes.Sticker + ? Colors.transparent + : theme.dividerColor, + ), + ), + child: InkWell( + onTap: () => _onTap(context), + borderRadius: borderRadius, + child: Hero( + tag: event.eventId, + child: MxcImage( + event: event, + width: width, + height: height, + fit: fit, + animated: animated, + isThumbnail: thumbnailOnly, + placeholder: event.messageType == MessageTypes.Sticker + ? null + : _buildPlaceholder, + ), + ), ), ), - ), + if (fileDescription != null && textColor != null) + SizedBox( + width: width, + child: HtmlMessage( + html: fileDescription, + textColor: textColor, + room: event.room, + ), + ), + ], ); } } diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index f661519ba..eab969b57 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -8,6 +8,7 @@ import 'package:swipe_to_action/swipe_to_action.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; +import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/utils/string_color.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -130,6 +131,7 @@ class Message extends StatelessWidget { MessageTypes.Image, MessageTypes.Sticker, }.contains(event.messageType) && + event.fileDescription == null && !event.redacted) || (event.messageType == MessageTypes.Text && event.relationshipType == null && diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 3879e0bd9..87c1809c0 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -140,6 +140,7 @@ class MessageContent extends StatelessWidget { fit: fit, borderRadius: borderRadius, timeline: timeline, + textColor: textColor, ); case CuteEventContent.eventType: return CuteContent(event); @@ -159,7 +160,7 @@ class MessageContent extends StatelessWidget { } return MessageDownloadContent(event, textColor); case MessageTypes.Video: - return EventVideoPlayer(event); + return EventVideoPlayer(event, textColor: textColor); case MessageTypes.File: return MessageDownloadContent(event, textColor); diff --git a/lib/pages/chat/events/message_download_content.dart b/lib/pages/chat/events/message_download_content.dart index 767ea8e7b..b40325089 100644 --- a/lib/pages/chat/events/message_download_content.dart +++ b/lib/pages/chat/events/message_download_content.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/pages/chat/events/html_message.dart'; +import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; class MessageDownloadContent extends StatelessWidget { @@ -21,59 +23,73 @@ class MessageDownloadContent extends StatelessWidget { ?.toUpperCase() ?? 'UNKNOWN'); final sizeString = event.sizeString; - return InkWell( - onTap: () => event.saveFile(context), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - Icon( - Icons.file_download_outlined, - color: textColor, - ), - const SizedBox(width: 16), - Flexible( - child: Text( - filename, - maxLines: 1, - style: TextStyle( + final fileDescription = event.fileDescription; + return Column( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + InkWell( + onTap: () => event.saveFile(context), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Icon( + Icons.file_download_outlined, color: textColor, - fontWeight: FontWeight.bold, ), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ), - const Divider(height: 1), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), - child: Row( - children: [ - Text( - filetype, - style: TextStyle( - color: textColor.withAlpha(150), - ), - ), - const Spacer(), - if (sizeString != null) - Text( - sizeString, - style: TextStyle( - color: textColor.withAlpha(150), + const SizedBox(width: 16), + Flexible( + child: Text( + filename, + maxLines: 1, + style: TextStyle( + color: textColor, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), ), - ), - ], - ), + ], + ), + ), + const Divider(height: 1), + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), + child: Row( + children: [ + Text( + filetype, + style: TextStyle( + color: textColor.withAlpha(150), + ), + ), + const Spacer(), + if (sizeString != null) + Text( + sizeString, + style: TextStyle( + color: textColor.withAlpha(150), + ), + ), + ], + ), + ), + ], ), - ], - ), + ), + if (fileDescription != null) + HtmlMessage( + html: fileDescription, + textColor: textColor, + room: event.room, + ), + ], ); } } diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart index 3f2b3d0cc..0ea894eed 100644 --- a/lib/pages/chat/events/video_player.dart +++ b/lib/pages/chat/events/video_player.dart @@ -11,7 +11,9 @@ import 'package:universal_html/html.dart' as html; import 'package:video_player/video_player.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pages/chat/events/html_message.dart'; import 'package:fluffychat/pages/chat/events/image_bubble.dart'; +import 'package:fluffychat/utils/file_description.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/platform_infos.dart'; @@ -20,7 +22,8 @@ import '../../../utils/error_reporter.dart'; class EventVideoPlayer extends StatefulWidget { final Event event; - const EventVideoPlayer(this.event, {super.key}); + final Color? textColor; + const EventVideoPlayer(this.event, {this.textColor, super.key}); @override EventVideoPlayerState createState() => EventVideoPlayerState(); @@ -102,51 +105,75 @@ class EventVideoPlayerState extends State { final blurHash = (widget.event.infoMap as Map) .tryGet('xyz.amorgan.blurhash') ?? fallbackBlurHash; + final fileDescription = widget.event.fileDescription; + final textColor = widget.textColor; + + const width = 300.0; final chewieManager = _chewieManager; - return Material( - color: Colors.black, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), - child: SizedBox( - height: 300, - child: chewieManager != null - ? Center(child: Chewie(controller: chewieManager)) - : Stack( - children: [ - if (hasThumbnail) - Center( - child: ImageBubble( - widget.event, - tapToView: false, + return Column( + mainAxisSize: MainAxisSize.min, + spacing: 8, + children: [ + Material( + color: Colors.black, + borderRadius: BorderRadius.circular(AppConfig.borderRadius), + child: SizedBox( + height: width, + child: chewieManager != null + ? Center(child: Chewie(controller: chewieManager)) + : Stack( + children: [ + if (hasThumbnail) + Center( + child: ImageBubble( + widget.event, + tapToView: false, + textColor: widget.textColor, + ), + ) + else + BlurHash( + blurhash: blurHash, + width: width, + height: width, + ), + Center( + child: IconButton( + style: IconButton.styleFrom( + backgroundColor: theme.colorScheme.surface, + ), + icon: _isDownloading + ? const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator.adaptive( + strokeWidth: 2, + ), + ) + : const Icon(Icons.play_circle_outlined), + tooltip: _isDownloading + ? L10n.of(context).loadingPleaseWait + : L10n.of(context).videoWithSize( + widget.event.sizeString ?? '?MB', + ), + onPressed: _isDownloading ? null : _downloadAction, + ), ), - ) - else - BlurHash(blurhash: blurHash, width: 300, height: 300), - Center( - child: IconButton( - style: IconButton.styleFrom( - backgroundColor: theme.colorScheme.surface, - ), - icon: _isDownloading - ? const SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ) - : const Icon(Icons.play_circle_outlined), - tooltip: _isDownloading - ? L10n.of(context).loadingPleaseWait - : L10n.of(context).videoWithSize( - widget.event.sizeString ?? '?MB', - ), - onPressed: _isDownloading ? null : _downloadAction, - ), + ], ), - ], - ), - ), + ), + ), + if (fileDescription != null && textColor != null) + SizedBox( + width: width, + child: HtmlMessage( + html: fileDescription, + textColor: textColor, + room: widget.event.room, + ), + ), + ], ); } } diff --git a/lib/utils/file_description.dart b/lib/utils/file_description.dart new file mode 100644 index 000000000..5a259a02a --- /dev/null +++ b/lib/utils/file_description.dart @@ -0,0 +1,19 @@ +import 'package:matrix/matrix.dart'; + +extension FileDescriptionExtension on Event { + String? get fileDescription { + if (!{ + MessageTypes.File, + MessageTypes.Image, + }.contains(messageType)) { + return null; + } + final formattedBody = content.tryGet('formatted_body'); + if (formattedBody != null) return formattedBody; + + final filename = content.tryGet('filename'); + final body = content.tryGet('body'); + if (filename != body && body != null) return body; + return null; + } +}