feat: Display file description on all file events
This commit is contained in:
parent
a5dc6db4e8
commit
f68a9de6bf
7 changed files with 215 additions and 144 deletions
|
|
@ -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<AudioPlayerWidget> {
|
|||
final statusText = this.statusText ??= _durationString ?? '00:00';
|
||||
final audioPlayer = this.audioPlayer;
|
||||
|
||||
final body = widget.event.content.tryGet<String>('body') ??
|
||||
widget.event.content.tryGet<String>('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<AudioPlayerWidget> {
|
|||
],
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 &&
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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: <Widget>[
|
||||
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: <Widget>[
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<EventVideoPlayer> {
|
|||
final blurHash = (widget.event.infoMap as Map<String, dynamic>)
|
||||
.tryGet<String>('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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
lib/utils/file_description.dart
Normal file
19
lib/utils/file_description.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue