From 8da19c569da41836b57688ae3e15557053b21faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=88=D0=B5=20=D0=98=D0=BC=D1=8F?= Date: Sat, 11 Apr 2026 00:09:38 +0700 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=81=D0=BB=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=84=D0=BE=D1=82=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/frontend/widgets/message_bubble.dart | 171 +++++++++++++++++++---- 1 file changed, 143 insertions(+), 28 deletions(-) diff --git a/lib/frontend/widgets/message_bubble.dart b/lib/frontend/widgets/message_bubble.dart index 100a1fd..352435d 100644 --- a/lib/frontend/widgets/message_bubble.dart +++ b/lib/frontend/widgets/message_bubble.dart @@ -78,7 +78,13 @@ class MessageBubble extends StatelessWidget { MessageType get contentType { if (message.attachments != null && message.attachments!.isNotEmpty) { final first = message.attachments!.first; - if (first is ForwardedMessageAttachment) return MessageType.text; + if (first is ForwardedMessageAttachment) { + final fwd = first; + final hasPhoto = + fwd.originalAttachments != null && + fwd.originalAttachments!.any((a) => a is PhotoAttachment); + return hasPhoto ? MessageType.attachment : MessageType.text; + } if (first is UnknownAttachment) return MessageType.text; if (first.type == AttachmentType.audio) return MessageType.voice; return MessageType.attachment; @@ -319,26 +325,19 @@ class MessageBubble extends StatelessWidget { : (isDark ? cs.onSurface : const Color(0xFF1C1C1E)); final forwarded = _getForwardedAttachment(); + final isForwarded = forwarded != null; return Row( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (forwarded != null) ...[ - _buildForwardedHeader(context, forwarded), - const SizedBox(height: 4), - ], - Text( - message.text ?? '', - style: TextStyle(color: textColor, fontSize: 16, height: 1.3), - ), - ], - ), + child: isForwarded + ? _buildForwardedInlineText(context, forwarded, textColor) + : Text( + message.text ?? '', + style: TextStyle(color: textColor, fontSize: 16, height: 1.3), + ), ), const SizedBox(width: 8), Padding( @@ -356,6 +355,58 @@ class MessageBubble extends StatelessWidget { ); } + Widget _buildForwardedInlineText( + BuildContext context, + ForwardedMessageAttachment forwarded, + Color textColor, + ) { + final headerColor = isMe + ? Colors.white.withValues(alpha: 0.7) + : Theme.of(context).colorScheme.onSurfaceVariant; + + final senderName = forwarded.originalSenderName; + final displaySender = senderName ?? forwarded.originalSenderId.toString(); + final origText = forwarded.originalText; + final hasOrigText = origText != null && origText.isNotEmpty; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Symbols.forward, size: 14, color: headerColor), + const SizedBox(width: 4), + Text( + displaySender, + style: TextStyle( + color: headerColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + if (hasOrigText) ...[ + const SizedBox(height: 2), + Text( + origText, + style: TextStyle(color: textColor, fontSize: 14), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ] else ...[ + const SizedBox(height: 2), + Text( + message.text ?? '', + style: TextStyle(color: textColor, fontSize: 16, height: 1.3), + ), + ], + ], + ); + } + ForwardedMessageAttachment? _getForwardedAttachment() { if (message.attachments == null || message.attachments!.isEmpty) return null; @@ -387,20 +438,23 @@ class MessageBubble extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Symbols.forward, size: 14, color: headerColor), - const SizedBox(width: 4), - Text( - displaySender, - style: TextStyle( - color: headerColor, - fontSize: 12, - fontWeight: FontWeight.w500, + Padding( + padding: const EdgeInsets.only(left: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Symbols.forward, size: 14, color: headerColor), + const SizedBox(width: 4), + Text( + displaySender, + style: TextStyle( + color: headerColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), ), - ), - ], + ], + ), ), if (hasOrigText) ...[ const SizedBox(height: 2), @@ -423,6 +477,13 @@ class MessageBubble extends StatelessWidget { final first = attachments.first; if (first is ForwardedMessageAttachment) { + final fwd = first; + final photos = fwd.originalAttachments + ?.whereType() + .toList(); + if (photos != null && photos.isNotEmpty) { + return _buildForwardedPhotoContent(context, fwd, photos); + } return _buildTextContent(context); } @@ -514,6 +575,60 @@ class MessageBubble extends StatelessWidget { ); } + Widget _buildForwardedPhotoContent( + BuildContext context, + ForwardedMessageAttachment forwarded, + List photos, + ) { + final headerColor = isMe + ? Colors.white.withValues(alpha: 0.7) + : Theme.of(context).colorScheme.onSurfaceVariant; + final senderName = forwarded.originalSenderName; + final displaySender = senderName ?? forwarded.originalSenderId.toString(); + final hasCaption = message.text != null && message.text!.isNotEmpty; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only(left: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Symbols.forward, size: 14, color: headerColor), + const SizedBox(width: 4), + Text( + displaySender, + style: TextStyle( + color: headerColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + const SizedBox(height: 4), + if (hasCaption) ...[ + Padding( + padding: const EdgeInsets.only(left: 8), + child: Text( + message.text ?? '', + style: TextStyle( + color: isMe ? Colors.white : const Color(0xFF1C1C1E), + fontSize: 16, + height: 1.3, + ), + ), + ), + const SizedBox(height: 6), + ], + _buildPhotoContent(context, photos), + ], + ); + } + Widget _buildSinglePhoto(BuildContext ctx, PhotoAttachment photo) { final imageUrl = photo.baseUrl ?? ''; final width = photo.width?.toDouble() ?? 200;