feat: Display file description on all file events

This commit is contained in:
Krille 2025-01-10 15:19:48 +01:00
commit f68a9de6bf
No known key found for this signature in database
GPG key ID: E067ECD60F1A0652
7 changed files with 213 additions and 142 deletions

View file

@ -4,17 +4,16 @@ import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart'; import 'package:opus_caf_converter_dart/opus_caf_converter_dart.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/themes.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/error_reporter.dart';
import 'package:fluffychat/utils/file_description.dart';
import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import '../../../utils/matrix_sdk_extensions/event_extension.dart'; import '../../../utils/matrix_sdk_extensions/event_extension.dart';
class AudioPlayerWidget extends StatefulWidget { class AudioPlayerWidget extends StatefulWidget {
@ -240,17 +239,11 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
final statusText = this.statusText ??= _durationString ?? '00:00'; final statusText = this.statusText ??= _durationString ?? '00:00';
final audioPlayer = this.audioPlayer; final audioPlayer = this.audioPlayer;
final body = widget.event.content.tryGet<String>('body') ?? final fileDescription = widget.event.fileDescription;
widget.event.content.tryGet<String>('filename');
final displayBody = body != null &&
body.isNotEmpty &&
widget.event.content['org.matrix.msc1767.audio'] == null;
final wavePosition = final wavePosition =
(currentPosition / maxPosition) * AudioPlayerWidget.wavesCount; (currentPosition / maxPosition) * AudioPlayerWidget.wavesCount;
final fontSize = 12 * AppConfig.fontSizeFactor;
return Padding( return Padding(
padding: const EdgeInsets.all(12.0), padding: const EdgeInsets.all(12.0),
child: Column( child: Column(
@ -380,22 +373,12 @@ class AudioPlayerState extends State<AudioPlayerWidget> {
], ],
), ),
), ),
if (displayBody) ...[ if (fileDescription != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Linkify( HtmlMessage(
text: body, html: fileDescription,
style: TextStyle( textColor: widget.color,
color: widget.color, room: widget.event.room,
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(),
), ),
], ],
], ],

View file

@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart'; import 'package:matrix/matrix.dart';
import 'package:fluffychat/config/app_config.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/pages/image_viewer/image_viewer.dart';
import 'package:fluffychat/utils/file_description.dart';
import 'package:fluffychat/widgets/mxc_image.dart'; import 'package:fluffychat/widgets/mxc_image.dart';
import '../../../widgets/blur_hash.dart'; import '../../../widgets/blur_hash.dart';
@ -13,6 +15,7 @@ class ImageBubble extends StatelessWidget {
final BoxFit fit; final BoxFit fit;
final bool maxSize; final bool maxSize;
final Color? backgroundColor; final Color? backgroundColor;
final Color? textColor;
final bool thumbnailOnly; final bool thumbnailOnly;
final bool animated; final bool animated;
final double width; final double width;
@ -34,6 +37,7 @@ class ImageBubble extends StatelessWidget {
this.onTap, this.onTap,
this.borderRadius, this.borderRadius,
this.timeline, this.timeline,
this.textColor,
super.key, super.key,
}); });
@ -76,35 +80,54 @@ class ImageBubble extends StatelessWidget {
final borderRadius = final borderRadius =
this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius); this.borderRadius ?? BorderRadius.circular(AppConfig.borderRadius);
return Material(
color: Colors.transparent, final fileDescription = event.fileDescription;
clipBehavior: Clip.hardEdge, final textColor = this.textColor;
shape: RoundedRectangleBorder(
borderRadius: borderRadius, return Column(
side: BorderSide( mainAxisSize: MainAxisSize.min,
color: event.messageType == MessageTypes.Sticker spacing: 8,
? Colors.transparent children: [
: theme.dividerColor, Material(
), color: Colors.transparent,
), clipBehavior: Clip.hardEdge,
child: InkWell( shape: RoundedRectangleBorder(
onTap: () => _onTap(context), borderRadius: borderRadius,
borderRadius: borderRadius, side: BorderSide(
child: Hero( color: event.messageType == MessageTypes.Sticker
tag: event.eventId, ? Colors.transparent
child: MxcImage( : theme.dividerColor,
event: event, ),
width: width, ),
height: height, child: InkWell(
fit: fit, onTap: () => _onTap(context),
animated: animated, borderRadius: borderRadius,
isThumbnail: thumbnailOnly, child: Hero(
placeholder: event.messageType == MessageTypes.Sticker tag: event.eventId,
? null child: MxcImage(
: _buildPlaceholder, 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,
),
),
],
); );
} }
} }

View file

@ -8,6 +8,7 @@ import 'package:swipe_to_action/swipe_to_action.dart';
import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/config/themes.dart';
import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart'; import 'package:fluffychat/pages/chat/events/room_creation_state_event.dart';
import 'package:fluffychat/utils/date_time_extension.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/utils/string_color.dart';
import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/matrix.dart';
@ -130,6 +131,7 @@ class Message extends StatelessWidget {
MessageTypes.Image, MessageTypes.Image,
MessageTypes.Sticker, MessageTypes.Sticker,
}.contains(event.messageType) && }.contains(event.messageType) &&
event.fileDescription == null &&
!event.redacted) || !event.redacted) ||
(event.messageType == MessageTypes.Text && (event.messageType == MessageTypes.Text &&
event.relationshipType == null && event.relationshipType == null &&

View file

@ -140,6 +140,7 @@ class MessageContent extends StatelessWidget {
fit: fit, fit: fit,
borderRadius: borderRadius, borderRadius: borderRadius,
timeline: timeline, timeline: timeline,
textColor: textColor,
); );
case CuteEventContent.eventType: case CuteEventContent.eventType:
return CuteContent(event); return CuteContent(event);
@ -159,7 +160,7 @@ class MessageContent extends StatelessWidget {
} }
return MessageDownloadContent(event, textColor); return MessageDownloadContent(event, textColor);
case MessageTypes.Video: case MessageTypes.Video:
return EventVideoPlayer(event); return EventVideoPlayer(event, textColor: textColor);
case MessageTypes.File: case MessageTypes.File:
return MessageDownloadContent(event, textColor); return MessageDownloadContent(event, textColor);

View file

@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:matrix/matrix.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'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
class MessageDownloadContent extends StatelessWidget { class MessageDownloadContent extends StatelessWidget {
@ -21,59 +23,73 @@ class MessageDownloadContent extends StatelessWidget {
?.toUpperCase() ?? ?.toUpperCase() ??
'UNKNOWN'); 'UNKNOWN');
final sizeString = event.sizeString; final sizeString = event.sizeString;
return InkWell( final fileDescription = event.fileDescription;
onTap: () => event.saveFile(context), return Column(
child: Column( mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, spacing: 8,
mainAxisSize: MainAxisSize.min, children: [
children: <Widget>[ InkWell(
Padding( onTap: () => event.saveFile(context),
padding: const EdgeInsets.all(16.0), child: Column(
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
children: [ mainAxisSize: MainAxisSize.min,
Icon( children: <Widget>[
Icons.file_download_outlined, Padding(
color: textColor, padding: const EdgeInsets.all(16.0),
), child: Row(
const SizedBox(width: 16), children: [
Flexible( Icon(
child: Text( Icons.file_download_outlined,
filename,
maxLines: 1,
style: TextStyle(
color: textColor, color: textColor,
fontWeight: FontWeight.bold,
), ),
overflow: TextOverflow.ellipsis, const SizedBox(width: 16),
), Flexible(
), child: Text(
], filename,
), maxLines: 1,
), style: TextStyle(
const Divider(height: 1), color: textColor,
Padding( fontWeight: FontWeight.bold,
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), ),
child: Row( overflow: TextOverflow.ellipsis,
children: [ ),
Text(
filetype,
style: TextStyle(
color: textColor.withAlpha(150),
),
),
const Spacer(),
if (sizeString != null)
Text(
sizeString,
style: TextStyle(
color: textColor.withAlpha(150),
), ),
), ],
], ),
), ),
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,
),
],
); );
} }
} }

View file

@ -11,7 +11,9 @@ import 'package:universal_html/html.dart' as html;
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:fluffychat/config/app_config.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/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/localized_exception_extension.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart';
import 'package:fluffychat/utils/platform_infos.dart'; import 'package:fluffychat/utils/platform_infos.dart';
@ -20,7 +22,8 @@ import '../../../utils/error_reporter.dart';
class EventVideoPlayer extends StatefulWidget { class EventVideoPlayer extends StatefulWidget {
final Event event; final Event event;
const EventVideoPlayer(this.event, {super.key}); final Color? textColor;
const EventVideoPlayer(this.event, {this.textColor, super.key});
@override @override
EventVideoPlayerState createState() => EventVideoPlayerState(); EventVideoPlayerState createState() => EventVideoPlayerState();
@ -102,51 +105,75 @@ class EventVideoPlayerState extends State<EventVideoPlayer> {
final blurHash = (widget.event.infoMap as Map<String, dynamic>) final blurHash = (widget.event.infoMap as Map<String, dynamic>)
.tryGet<String>('xyz.amorgan.blurhash') ?? .tryGet<String>('xyz.amorgan.blurhash') ??
fallbackBlurHash; fallbackBlurHash;
final fileDescription = widget.event.fileDescription;
final textColor = widget.textColor;
const width = 300.0;
final chewieManager = _chewieManager; final chewieManager = _chewieManager;
return Material( return Column(
color: Colors.black, mainAxisSize: MainAxisSize.min,
borderRadius: BorderRadius.circular(AppConfig.borderRadius), spacing: 8,
child: SizedBox( children: [
height: 300, Material(
child: chewieManager != null color: Colors.black,
? Center(child: Chewie(controller: chewieManager)) borderRadius: BorderRadius.circular(AppConfig.borderRadius),
: Stack( child: SizedBox(
children: [ height: width,
if (hasThumbnail) child: chewieManager != null
Center( ? Center(child: Chewie(controller: chewieManager))
child: ImageBubble( : Stack(
widget.event, children: [
tapToView: false, 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,
),
),
],
); );
} }
} }

View file

@ -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<String>('formatted_body');
if (formattedBody != null) return formattedBody;
final filename = content.tryGet<String>('filename');
final body = content.tryGet<String>('body');
if (filename != body && body != null) return body;
return null;
}
}