diff --git a/assets/i18n/en-US.json b/assets/i18n/en-US.json index 43c89616..f4c6917b 100644 --- a/assets/i18n/en-US.json +++ b/assets/i18n/en-US.json @@ -1021,5 +1021,6 @@ "currentEmbed": "Current Embed", "noEmbed": "No embed yet", "save": "Save", - "webView": "Web View" + "webView": "Web View", + "messageActions": "Message Actions" } diff --git a/lib/widgets/chat/message_content.dart b/lib/widgets/chat/message_content.dart index 1d07fa58..4bc8ad25 100644 --- a/lib/widgets/chat/message_content.dart +++ b/lib/widgets/chat/message_content.dart @@ -18,6 +18,32 @@ class MessageContent extends StatelessWidget { @override Widget build(BuildContext context) { + if (item.type == 'messages.delete' || item.deletedAt != null) { + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Symbols.delete, + size: 14, + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.6), + ), + const Gap(4), + Text( + item.content ?? 'Deleted a message', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of( + context, + ).colorScheme.onSurfaceVariant.withOpacity(0.6), + fontStyle: FontStyle.italic, + ), + ), + ], + ); + } + switch (item.type) { case 'call.start': case 'call.ended': @@ -71,30 +97,6 @@ class MessageContent extends StatelessWidget { ), ], ); - case 'messages.delete': - return Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Icon( - Symbols.delete, - size: 14, - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant.withOpacity(0.6), - ), - const Gap(4), - Text( - item.content ?? 'Deleted a message', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant.withOpacity(0.6), - fontStyle: FontStyle.italic, - ), - ), - ], - ); case 'text': default: return Column( diff --git a/lib/widgets/chat/message_item.dart b/lib/widgets/chat/message_item.dart index 5f7ea180..db473cf1 100644 --- a/lib/widgets/chat/message_item.dart +++ b/lib/widgets/chat/message_item.dart @@ -15,16 +15,20 @@ import 'package:island/pods/messages_notifier.dart'; import 'package:island/pods/translate.dart'; import 'package:island/screens/chat/room.dart'; import 'package:island/utils/mapping.dart'; +import 'package:island/widgets/account/account_pfc.dart'; import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/chat/message_content.dart'; import 'package:island/widgets/chat/message_indicators.dart'; import 'package:island/widgets/chat/message_sender_info.dart'; import 'package:island/widgets/content/alert.native.dart'; import 'package:island/widgets/content/cloud_file_collection.dart'; +import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/embed/link.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:styled_widget/styled_widget.dart'; -import 'package:super_context_menu/super_context_menu.dart'; +import 'package:island/widgets/content/sheet.dart'; + +const kChatMessageStyle = 'discord'; class MessageItemAction { static const String edit = "edit"; @@ -51,6 +55,188 @@ class MessageItem extends HookConsumerWidget { required this.onJump, }); + @override + Widget build(BuildContext context, WidgetRef ref) { + final remoteMessage = message.toRemoteMessage(); + + final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS); + + final messageLanguage = + remoteMessage.content != null + ? ref.watch(detectStringLanguageProvider(remoteMessage.content!)) + : null; + + final currentLanguage = context.locale.toString(); + final translatableLanguage = + messageLanguage != null + ? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2) + : false; + + final translating = useState(false); + final translatedText = useState(null); + + Future translate() async { + if (translatedText.value != null) { + translatedText.value = null; + return; + } + + if (translating.value) return; + if (remoteMessage.content == null) return; + translating.value = true; + try { + final text = await ref.watch( + translateStringProvider( + TranslateQuery( + text: remoteMessage.content!, + lang: currentLanguage.substring(0, 2), + ), + ).future, + ); + translatedText.value = text; + } catch (err) { + showErrorAlert(err); + } finally { + translating.value = false; + } + } + + void showActionMenu() { + if (onAction == null) return; + showModalBottomSheet( + context: context, + builder: + (context) => SheetScaffold( + titleText: 'messageActions'.tr(), + child: SingleChildScrollView( + child: Column( + children: [ + if (isCurrentUser) + ListTile( + leading: Icon(Symbols.edit), + title: Text('edit'.tr()), + onTap: () { + onAction!.call(MessageItemAction.edit); + Navigator.pop(context); + }, + ), + if (isCurrentUser) + ListTile( + leading: Icon(Symbols.delete), + title: Text('delete'.tr()), + onTap: () { + onAction!.call(MessageItemAction.delete); + Navigator.pop(context); + }, + ), + if (isCurrentUser) Divider(), + ListTile( + leading: Icon(Symbols.reply), + title: Text('reply'.tr()), + onTap: () { + onAction!.call(MessageItemAction.reply); + Navigator.pop(context); + }, + ), + ListTile( + leading: Icon(Symbols.forward), + title: Text('forward'.tr()), + onTap: () { + onAction!.call(MessageItemAction.forward); + Navigator.pop(context); + }, + ), + if (translatableLanguage) Divider(), + if (translatableLanguage) + ListTile( + leading: Icon(Symbols.translate), + title: Text( + translatedText.value == null + ? 'translate'.tr() + : translating.value + ? 'translating'.tr() + : 'translated'.tr(), + ), + onTap: () { + translate(); + Navigator.pop(context); + }, + ), + if (isMobile) Divider(), + if (isMobile) + ListTile( + leading: Icon(Symbols.copy_all), + title: Text('copyMessage'.tr()), + onTap: () { + Clipboard.setData( + ClipboardData(text: remoteMessage.content ?? ''), + ); + Navigator.pop(context); + }, + ), + ], + ), + ), + ), + ); + } + + return GestureDetector( + onLongPress: showActionMenu, + onSecondaryTap: showActionMenu, + child: switch (kChatMessageStyle) { + 'irc' => MessageItemDisplayIRC( + message: message, + isCurrentUser: isCurrentUser, + progress: progress, + showAvatar: showAvatar, + onJump: onJump, + translatedText: translatedText.value, + translating: translating.value, + ), + 'discord' => MessageItemDisplayDiscord( + message: message, + isCurrentUser: isCurrentUser, + progress: progress, + showAvatar: showAvatar, + onJump: onJump, + translatedText: translatedText.value, + translating: translating.value, + ), + _ => MessageItemDisplayBubble( + message: message, + isCurrentUser: isCurrentUser, + progress: progress, + showAvatar: showAvatar, + onJump: onJump, + translatedText: translatedText.value, + translating: translating.value, + ), + }, + ); + } +} + +class MessageItemDisplayBubble extends HookConsumerWidget { + final LocalChatMessage message; + final bool isCurrentUser; + final Map? progress; + final bool showAvatar; + final Function(String messageId) onJump; + final String? translatedText; + final bool translating; + + const MessageItemDisplayBubble({ + super.key, + required this.message, + required this.isCurrentUser, + required this.progress, + required this.showAvatar, + required this.onJump, + required this.translatedText, + required this.translating, + }); + @override Widget build(BuildContext context, WidgetRef ref) { final textColor = @@ -108,261 +294,455 @@ class MessageItem extends HookConsumerWidget { final remoteMessage = message.toRemoteMessage(); final sender = remoteMessage.sender; - final isMobile = !kIsWeb && (Platform.isAndroid || Platform.isIOS); - - final messageLanguage = - remoteMessage.content != null - ? ref.watch(detectStringLanguageProvider(remoteMessage.content!)) - : null; - - final currentLanguage = context.locale.toString(); - final translatableLanguage = - messageLanguage != null - ? messageLanguage.substring(0, 2) != currentLanguage.substring(0, 2) - : false; - - final translating = useState(false); - final translatedText = useState(null); - - Future translate() async { - if (translatedText.value != null) { - translatedText.value = null; - return; - } - - if (translating.value) return; - if (remoteMessage.content == null) return; - translating.value = true; - try { - final text = await ref.watch( - translateStringProvider( - TranslateQuery( - text: remoteMessage.content!, - lang: currentLanguage.substring(0, 2), - ), - ).future, - ); - translatedText.value = text; - } catch (err) { - showErrorAlert(err); - } finally { - translating.value = false; - } - } - - return ContextMenuWidget( - menuProvider: (_) { - if (onAction == null) return Menu(children: []); - return Menu( + return Material( + color: + hasBackground + ? Colors.transparent + : Theme.of(context).colorScheme.surface, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - if (isCurrentUser) - MenuAction( - title: 'edit'.tr(), - image: MenuImage.icon(Symbols.edit), - callback: () { - onAction!.call(MessageItemAction.edit); - }, + if (showAvatar) ...[ + const Gap(8), + MessageSenderInfo( + sender: sender, + createdAt: message.createdAt, + textColor: textColor, ), - if (isCurrentUser) - MenuAction( - title: 'delete'.tr(), - image: MenuImage.icon(Symbols.delete), - callback: () { - onAction!.call(MessageItemAction.delete); - }, - ), - if (isCurrentUser) MenuSeparator(), - MenuAction( - title: 'reply'.tr(), - image: MenuImage.icon(Symbols.reply), - callback: () { - onAction!.call(MessageItemAction.reply); - }, - ), - MenuAction( - title: 'forward'.tr(), - image: MenuImage.icon(Symbols.forward), - callback: () { - onAction!.call(MessageItemAction.forward); - }, - ), - if (translatableLanguage) MenuSeparator(), - if (translatableLanguage) - MenuAction( - title: - translatedText.value == null - ? 'translate'.tr() - : translating.value - ? 'translating'.tr() - : 'translated'.tr(), - image: MenuImage.icon(Symbols.translate), - callback: translate, - ), - if (isMobile) MenuSeparator(), - if (isMobile) - MenuAction( - title: 'copyMessage'.tr(), - image: MenuImage.icon(Symbols.copy_all), - callback: () { - Clipboard.setData( - ClipboardData(text: remoteMessage.content ?? ''), - ); - }, - ), - ], - ); - }, - child: Material( - color: - hasBackground - ? Colors.transparent - : Theme.of(context).colorScheme.surface, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (showAvatar) ...[ - const Gap(8), - MessageSenderInfo( - sender: sender, - createdAt: message.createdAt, + const Gap(4), + ], + const Gap(2), + Row( + spacing: 4, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Flexible( + child: AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: flashColor, + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (remoteMessage.repliedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: true, + ).padding(vertical: 4), + if (remoteMessage.forwardedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: false, + ).padding(vertical: 4), + if (MessageContent.hasContent(remoteMessage)) + MessageContent( + item: remoteMessage, + translatedText: translatedText, + ), + if (remoteMessage.attachments.isNotEmpty) + LayoutBuilder( + builder: (context, constraints) { + return CloudFileList( + files: remoteMessage.attachments, + maxWidth: constraints.maxWidth, + padding: EdgeInsets.symmetric(vertical: 4), + ); + }, + ), + if (remoteMessage.meta['embeds'] != null) + ...((remoteMessage.meta['embeds'] as List) + .map((embed) => convertMapKeysToSnakeCase(embed)) + .where((embed) => embed['type'] == 'link') + .map((embed) => SnScrappedLink.fromJson(embed)) + .map( + (link) => LayoutBuilder( + builder: (context, constraints) { + return EmbedLinkWidget( + link: link, + maxWidth: math.min( + constraints.maxWidth, + 480, + ), + margin: const EdgeInsets.symmetric( + vertical: 4, + ), + ); + }, + ), + ) + .toList()), + if (progress != null && progress!.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 8, + children: [ + if ((remoteMessage.content?.isNotEmpty ?? false)) + const Gap(0), + for (var entry in progress!.entries) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'fileUploadingProgress'.tr( + args: [ + (entry.key + 1).toString(), + entry.value.toStringAsFixed(1), + ], + ), + style: TextStyle( + fontSize: 12, + color: textColor.withOpacity(0.8), + ), + ), + const Gap(4), + LinearProgressIndicator( + value: entry.value / 100, + backgroundColor: + Theme.of( + context, + ).colorScheme.surfaceVariant, + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const Gap(0), + ], + ), + ], + ), + ), + ), + MessageIndicators( + editedAt: remoteMessage.editedAt, + status: message.status, + isCurrentUser: isCurrentUser, textColor: textColor, ), - const Gap(4), ], - const Gap(2), - Row( - spacing: 4, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, + ), + ], + ), + ), + ); + } +} + +class MessageItemDisplayIRC extends HookConsumerWidget { + final LocalChatMessage message; + final bool isCurrentUser; + final Map? progress; + final bool showAvatar; + final Function(String messageId) onJump; + final String? translatedText; + final bool translating; + + const MessageItemDisplayIRC({ + super.key, + required this.message, + required this.isCurrentUser, + required this.progress, + required this.showAvatar, + required this.onJump, + required this.translatedText, + required this.translating, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final remoteMessage = message.toRemoteMessage(); + final sender = remoteMessage.sender; + final textColor = Theme.of(context).colorScheme.onSurfaceVariant; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2), + child: Row( + children: [ + Text( + DateFormat('HH:mm').format(message.createdAt), + style: TextStyle(color: textColor.withOpacity(0.7), fontSize: 12), + ), + const SizedBox(width: 8), + Text( + '<${sender.account.nick}>', + style: TextStyle(color: Colors.blue), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + translatedText ?? remoteMessage.content ?? '', + style: TextStyle(color: textColor), + ), + ), + ], + ), + ); + } +} + +class MessageItemDisplayDiscord extends HookConsumerWidget { + final LocalChatMessage message; + final bool isCurrentUser; + final Map? progress; + final bool showAvatar; + final Function(String messageId) onJump; + final String? translatedText; + final bool translating; + + const MessageItemDisplayDiscord({ + super.key, + required this.message, + required this.isCurrentUser, + required this.progress, + required this.showAvatar, + required this.onJump, + required this.translatedText, + required this.translating, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textColor = Theme.of(context).colorScheme.onSurfaceVariant; + final remoteMessage = message.toRemoteMessage(); + final sender = remoteMessage.sender; + + const kAvatarRadius = 12.0; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + child: + showAvatar + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - decoration: BoxDecoration( - color: flashColor, - borderRadius: BorderRadius.circular(16), + Row( + spacing: 8, + children: [ + AccountPfcGestureDetector( + uname: sender.account.name, + child: ProfilePictureWidget( + file: sender.account.profile.picture, + radius: kAvatarRadius, + ), ), - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 6, + MessageSenderInfo( + sender: sender, + createdAt: message.createdAt, + textColor: textColor, + showAvatar: false, + isCompact: true, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (remoteMessage.repliedMessageId != null) - MessageQuoteWidget( - message: message, - textColor: textColor, - isReply: true, - ).padding(vertical: 4), - if (remoteMessage.forwardedMessageId != null) - MessageQuoteWidget( - message: message, - textColor: textColor, - isReply: false, - ).padding(vertical: 4), - if (MessageContent.hasContent(remoteMessage)) - MessageContent( - item: remoteMessage, - translatedText: translatedText.value, - ), - if (remoteMessage.attachments.isNotEmpty) - LayoutBuilder( + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (remoteMessage.repliedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: true, + ).padding(vertical: 4), + if (remoteMessage.forwardedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: false, + ).padding(vertical: 4), + if (MessageContent.hasContent(remoteMessage)) + MessageContent( + item: remoteMessage, + translatedText: translatedText, + ), + if (remoteMessage.attachments.isNotEmpty) + LayoutBuilder( + builder: (context, constraints) { + return CloudFileList( + files: remoteMessage.attachments, + maxWidth: constraints.maxWidth, + padding: EdgeInsets.symmetric(vertical: 4), + ); + }, + ), + if (remoteMessage.meta['embeds'] != null) + ...((remoteMessage.meta['embeds'] as List) + .map((embed) => convertMapKeysToSnakeCase(embed)) + .where((embed) => embed['type'] == 'link') + .map((embed) => SnScrappedLink.fromJson(embed)) + .map( + (link) => LayoutBuilder( + builder: (context, constraints) { + return EmbedLinkWidget( + link: link, + maxWidth: math.min( + constraints.maxWidth, + 480, + ), + margin: const EdgeInsets.symmetric( + vertical: 4, + ), + ); + }, + ), + ) + .toList()), + if (progress != null && progress!.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 8, + children: [ + if ((remoteMessage.content?.isNotEmpty ?? false)) + const SizedBox.shrink(), + for (var entry in progress!.entries) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'fileUploadingProgress'.tr( + args: [ + (entry.key + 1).toString(), + entry.value.toStringAsFixed(1), + ], + ), + style: TextStyle( + fontSize: 12, + color: textColor.withOpacity(0.8), + ), + ), + const Gap(4), + LinearProgressIndicator( + value: entry.value / 100, + backgroundColor: + Theme.of( + context, + ).colorScheme.surfaceVariant, + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), + ), + ], + ), + const Gap(0), + ], + ), + ], + ).padding(left: kAvatarRadius * 2 + 8), + ], + ) + : Padding( + padding: EdgeInsets.only(left: kAvatarRadius * 2 + 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showAvatar) + MessageSenderInfo( + sender: sender, + createdAt: message.createdAt, + textColor: textColor, + showAvatar: false, + isCompact: true, + ), + if (remoteMessage.repliedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: true, + ).padding(vertical: 4), + if (remoteMessage.forwardedMessageId != null) + MessageQuoteWidget( + message: message, + textColor: textColor, + isReply: false, + ).padding(vertical: 4), + if (MessageContent.hasContent(remoteMessage)) + MessageContent( + item: remoteMessage, + translatedText: translatedText, + ), + if (remoteMessage.attachments.isNotEmpty) + LayoutBuilder( + builder: (context, constraints) { + return CloudFileList( + files: remoteMessage.attachments, + maxWidth: constraints.maxWidth, + padding: EdgeInsets.symmetric(vertical: 4), + ); + }, + ), + if (remoteMessage.meta['embeds'] != null) + ...((remoteMessage.meta['embeds'] as List) + .map((embed) => convertMapKeysToSnakeCase(embed)) + .where((embed) => embed['type'] == 'link') + .map((embed) => SnScrappedLink.fromJson(embed)) + .map( + (link) => LayoutBuilder( builder: (context, constraints) { - return CloudFileList( - files: remoteMessage.attachments, - maxWidth: constraints.maxWidth, - padding: EdgeInsets.symmetric(vertical: 4), + return EmbedLinkWidget( + link: link, + maxWidth: math.min(constraints.maxWidth, 480), + margin: const EdgeInsets.symmetric( + vertical: 4, + ), ); }, ), - if (remoteMessage.meta['embeds'] != null) - ...((remoteMessage.meta['embeds'] as List) - .map( - (embed) => convertMapKeysToSnakeCase(embed), - ) - .where((embed) => embed['type'] == 'link') - .map((embed) => SnScrappedLink.fromJson(embed)) - .map( - (link) => LayoutBuilder( - builder: (context, constraints) { - return EmbedLinkWidget( - link: link, - maxWidth: math.min( - constraints.maxWidth, - 480, - ), - margin: const EdgeInsets.symmetric( - vertical: 4, - ), - ); - }, - ), - ) - .toList()), - if (progress != null && progress!.isNotEmpty) + ) + .toList()), + if (progress != null && progress!.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + spacing: 8, + children: [ + if ((remoteMessage.content?.isNotEmpty ?? false)) + const Gap(0), + for (var entry in progress!.entries) Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - spacing: 8, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - if ((remoteMessage.content?.isNotEmpty ?? - false)) - const Gap(0), - for (var entry in progress!.entries) - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - 'fileUploadingProgress'.tr( - args: [ - (entry.key + 1).toString(), - entry.value.toStringAsFixed(1), - ], - ), - style: TextStyle( - fontSize: 12, - color: textColor.withOpacity(0.8), - ), - ), - const Gap(4), - LinearProgressIndicator( - value: entry.value / 100, - backgroundColor: - Theme.of( - context, - ).colorScheme.surfaceVariant, - valueColor: - AlwaysStoppedAnimation( - Theme.of( - context, - ).colorScheme.primary, - ), - ), + Text( + 'fileUploadingProgress'.tr( + args: [ + (entry.key + 1).toString(), + entry.value.toStringAsFixed(1), ], ), - const Gap(0), + style: TextStyle( + fontSize: 12, + color: textColor.withOpacity(0.8), + ), + ), + const Gap(4), + LinearProgressIndicator( + value: entry.value / 100, + backgroundColor: + Theme.of( + context, + ).colorScheme.surfaceVariant, + valueColor: AlwaysStoppedAnimation( + Theme.of(context).colorScheme.primary, + ), + ), ], ), + const Gap(0), ], ), - ), - ), - MessageIndicators( - editedAt: remoteMessage.editedAt, - status: message.status, - isCurrentUser: isCurrentUser, - textColor: textColor, - ), - ], + ], + ), ), - ], - ), - ), - ), ); } } diff --git a/lib/widgets/chat/message_list_tile.dart b/lib/widgets/chat/message_list_tile.dart index 9c104699..e7bd7f41 100644 --- a/lib/widgets/chat/message_list_tile.dart +++ b/lib/widgets/chat/message_list_tile.dart @@ -41,7 +41,7 @@ class MessageListTile extends StatelessWidget { sender: sender, createdAt: message.createdAt, textColor: Theme.of(context).colorScheme.onSurfaceVariant, - compact: true, + showAvatar: false, ), const SizedBox(height: 4), MessageContent(item: remoteMessage), diff --git a/lib/widgets/chat/message_sender_info.dart b/lib/widgets/chat/message_sender_info.dart index 6bbee3f1..4456a6f0 100644 --- a/lib/widgets/chat/message_sender_info.dart +++ b/lib/widgets/chat/message_sender_info.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:gap/gap.dart'; import 'package:island/models/chat.dart'; import 'package:island/widgets/account/account_name.dart'; import 'package:island/widgets/account/account_pfc.dart'; @@ -9,14 +10,16 @@ class MessageSenderInfo extends StatelessWidget { final SnChatMember sender; final DateTime createdAt; final Color textColor; - final bool compact; + final bool showAvatar; + final bool isCompact; const MessageSenderInfo({ super.key, required this.sender, required this.createdAt, required this.textColor, - this.compact = false, + this.showAvatar = true, + this.isCompact = false, }); @override @@ -28,11 +31,42 @@ class MessageSenderInfo extends StatelessWidget { ? DateFormat('MM/dd HH:mm').format(createdAt.toLocal()) : DateFormat('HH:mm').format(createdAt.toLocal()); - if (compact) { + if (isCompact) { + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + if (showAvatar) + AccountPfcGestureDetector( + uname: sender.account.name, + child: ProfilePictureWidget( + fileId: sender.account.profile.picture?.id, + radius: 14, + ), + ), + if (showAvatar) const Gap(4), + AccountName( + account: sender.account, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: textColor, + fontWeight: FontWeight.w500, + ), + ), + const Gap(6), + Text( + timestamp, + style: TextStyle(fontSize: 10, color: textColor.withOpacity(0.7)), + ), + ], + ); + } + + if (showAvatar) { return Row( spacing: 8, children: [ - if (!compact) + if (!showAvatar) AccountPfcGestureDetector( uname: sender.account.name, child: ProfilePictureWidget( @@ -84,13 +118,14 @@ class MessageSenderInfo extends StatelessWidget { spacing: 8, mainAxisSize: MainAxisSize.min, children: [ - AccountPfcGestureDetector( - uname: sender.account.name, - child: ProfilePictureWidget( - fileId: sender.account.profile.picture?.id, - radius: 16, + if (showAvatar) + AccountPfcGestureDetector( + uname: sender.account.name, + child: ProfilePictureWidget( + fileId: sender.account.profile.picture?.id, + radius: 16, + ), ), - ), Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 2,