diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 6b2c1348..49403f0c 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -14,6 +14,7 @@ import 'attachment_widget.dart'; class FileAttachment extends AttachmentWidget { final Widget title; final Widget trailing; + final VoidCallback onAttachmentTap; const FileAttachment({ Key key, @@ -22,6 +23,7 @@ class FileAttachment extends AttachmentWidget { Size size, this.title, this.trailing, + this.onAttachmentTap, }) : super(key: key, message: message, attachment: attachment, size: size); bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video'; @@ -31,45 +33,48 @@ class FileAttachment extends AttachmentWidget { @override Widget build(BuildContext context) { return Material( - child: Container( - width: size?.width ?? 100, - height: 56.0, - decoration: BoxDecoration( - color: StreamChatTheme.of(context).colorTheme.white, - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, + child: GestureDetector( + onTap: onAttachmentTap, + child: Container( + width: size?.width ?? 100, + height: 56.0, + decoration: BoxDecoration( + color: StreamChatTheme.of(context).colorTheme.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ), ), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: 40.0, - width: 33.33, - margin: EdgeInsets.all(8.0), - child: _getFileTypeImage(context), - ), - SizedBox(width: 8.0), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - attachment?.title ?? 'File', - style: StreamChatTheme.of(context).textTheme.bodyBold, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 3.0), - _buildSubtitle(context), - ], + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 40.0, + width: 33.33, + margin: EdgeInsets.all(8.0), + child: _getFileTypeImage(context), ), - ), - SizedBox(width: 8.0), - _buildTrailing(context), - ], + SizedBox(width: 8.0), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + attachment?.title ?? 'File', + style: StreamChatTheme.of(context).textTheme.bodyBold, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 3.0), + _buildSubtitle(context), + ], + ), + ), + SizedBox(width: 8.0), + _buildTrailing(context), + ], + ), ), ), ); diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index 2426991f..4ded5ca7 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -15,6 +15,7 @@ class GiphyAttachment extends AttachmentWidget { final MessageTheme messageTheme; final ShowMessageCallback onShowMessage; final ValueChanged onReturnAction; + final VoidCallback onAttachmentTap; const GiphyAttachment({ Key key, @@ -24,6 +25,7 @@ class GiphyAttachment extends AttachmentWidget { this.messageTheme, this.onShowMessage, this.onReturnAction, + this.onAttachmentTap, }) : super(key: key, message: message, attachment: attachment, size: size); @override @@ -65,7 +67,7 @@ class GiphyAttachment extends AttachmentWidget { Padding( padding: const EdgeInsets.all(8.0), child: GestureDetector( - onTap: () => _onImageTap(context), + onTap: () => onAttachmentTap ?? _onImageTap(context), child: ClipRRect( borderRadius: BorderRadius.only( topLeft: Radius.circular(8), diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 7f2c83eb..67e38c75 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -20,7 +20,11 @@ import 'extension.dart'; import 'image_group.dart'; import 'message_text.dart'; -typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); +typedef AttachmentBuilder = Widget Function( + BuildContext, + Message, + List, +); typedef OnQuotedMessageTap = void Function(String); /// The display behaviour of a widget @@ -205,63 +209,142 @@ class MessageWidget extends StatefulWidget { this.customActions = const [], this.onAttachmentTap, }) : attachmentBuilders = { - 'image': (context, message, attachment) { - return ImageAttachment( - attachment: attachment, - message: message, - messageTheme: messageTheme, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + 'image': (context, message, attachments) { + var border = RoundedRectangleBorder( + side: BorderSide.none, + borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, + ); + + if (attachments.length > 1) { + return Padding( + padding: attachmentPadding, + child: wrapAttachmentWidget( + context, + Material( + color: messageTheme.messageBackgroundColor, + child: ImageGroup( + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + images: attachments, + message: message, + messageTheme: messageTheme, + onShowMessage: onShowMessage, + ), + ), + border, + reverse, + ), + ); + } + + return wrapAttachmentWidget( + context, + ImageAttachment( + attachment: attachments[0], + message: message, + messageTheme: messageTheme, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + onAttachmentTap: onAttachmentTap != null + ? () { + onAttachmentTap?.call(message, attachments[0]); + } + : null, ), - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap?.call(message, attachment); - } - : null, + border, + reverse, ); }, - 'video': (context, message, attachment) { - return VideoAttachment( - attachment: attachment, - messageTheme: messageTheme, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + 'video': (context, message, attachments) { + var border = RoundedRectangleBorder( + side: BorderSide.none, + borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, + ); + + return wrapAttachmentWidget( + context, + Column( + children: attachments.map((attachment) { + return VideoAttachment( + attachment: attachment, + messageTheme: messageTheme, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + message: message, + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + onAttachmentTap: onAttachmentTap != null + ? () { + onAttachmentTap?.call(message, attachment); + } + : null, + ); + }).toList(), ), - message: message, - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, - onAttachmentTap: onAttachmentTap != null - ? () { - onAttachmentTap?.call(message, attachment); - } - : null, + border, + reverse, ); }, - 'giphy': (context, message, attachment) { - return GiphyAttachment( - attachment: attachment, - messageTheme: messageTheme, - message: message, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + 'giphy': (context, message, attachments) { + var border = RoundedRectangleBorder( + side: BorderSide.none, + borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, + ); + + return wrapAttachmentWidget( + context, + Column( + children: attachments.map((attachment) { + return GiphyAttachment( + attachment: attachment, + messageTheme: messageTheme, + message: message, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + onShowMessage: onShowMessage, + onReturnAction: onReturnAction, + ); + }).toList(), ), - onShowMessage: onShowMessage, - onReturnAction: onReturnAction, + border, + reverse, ); }, - 'file': (context, message, attachment) { - return FileAttachment( - message: message, - attachment: attachment, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + 'file': (context, message, attachments) { + var border = RoundedRectangleBorder( + side: attachmentBorderSide ?? + BorderSide( + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ), + borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, + ); + + return wrapAttachmentWidget( + context, + Column( + children: attachments.map((attachment) { + return FileAttachment( + message: message, + attachment: attachment, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), + ); + }).toList(), ), + border, + reverse, ); }, }..addAll(customAttachmentBuilders ?? {}), @@ -830,55 +913,37 @@ class _MessageWidgetState extends State } Widget _parseAttachments() { - final images = widget.message.attachments - ?.where((element) => - element.type == 'image' && element.ogScrapeUrl == null) - ?.toList() ?? - []; + Map> attachmentGroups = {}; - if (images.length > 1) { - return Padding( - padding: widget.attachmentPadding, - child: wrapAttachmentWidget( - context, - Material( - color: widget.messageTheme.messageBackgroundColor, - child: ImageGroup( - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, - ), - images: images, - message: widget.message, - messageTheme: widget.messageTheme, - onShowMessage: widget.onShowMessage, - ), - ), - ), + widget.message.attachments + .where((element) => element.ogScrapeUrl == null) + .forEach((e) { + if (attachmentGroups[e.type] == null) { + attachmentGroups[e.type] = []; + } + + attachmentGroups[e.type].add(e); + }); + + List attachmentList = []; + + attachmentGroups.forEach((type, attachments) { + final attachmentBuilder = widget.attachmentBuilders[type]; + + if (attachmentBuilder == null) return SizedBox(); + final attachmentWidget = attachmentBuilder( + context, + widget.message, + attachments, ); - } + attachmentList.add(attachmentWidget); + }); return Padding( padding: widget.attachmentPadding, child: Column( mainAxisSize: MainAxisSize.min, - children: widget.message.attachments - ?.where((element) => element.ogScrapeUrl == null) - ?.map((attachment) { - final attachmentBuilder = - widget.attachmentBuilders[attachment.type]; - - if (attachmentBuilder == null) return SizedBox(); - final attachmentWidget = attachmentBuilder( - context, - widget.message, - attachment, - ); - return wrapAttachmentWidget( - context, - attachmentWidget, - ); - })?.insertBetween(SizedBox( + children: attachmentList?.insertBetween(SizedBox( height: widget.attachmentPadding.vertical / 2, )) ?? [], @@ -886,24 +951,6 @@ class _MessageWidgetState extends State ); } - Widget wrapAttachmentWidget( - BuildContext context, - Widget attachmentWidget, - ) { - final attachmentShape = - widget.attachmentShape ?? _getDefaultAttachmentShape(context); - return Material( - clipBehavior: Clip.antiAlias, - shape: attachmentShape, - type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: attachmentWidget, - ), - ); - } - void onLongPress(BuildContext context) { if (widget.message.isEphemeral || widget.message.status == MessageSendingStatus.sending) { diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 8d59757e..fee747a9 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -348,3 +350,21 @@ StreamSvgIcon getFileTypeImage(String type) { break; } } + +Widget wrapAttachmentWidget( + BuildContext context, + Widget attachmentWidget, + ShapeBorder attachmentShape, + bool reverse, +) { + return Material( + clipBehavior: Clip.antiAlias, + shape: attachmentShape, + type: MaterialType.transparency, + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: attachmentWidget, + ), + ); +}