diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 11ca3d09..ea75a9c8 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -9,6 +9,8 @@ - [[#1069]](https://github.com/GetStream/stream-chat-flutter/issues/1069) Fixed message swipe to reply using same direction for both current user and other users. It now uses `SwipeDirection.startToEnd` for current user and `SwipeDirection.endToStart` for other users. +- [[#1590]](https://github.com/GetStream/stream-chat-flutter/issues/1590) + Fixed `StreamMessageWidget.showReactionPickerIndicator` not toggling the reaction picker indicator visibility. ✅ Added @@ -77,6 +79,14 @@ }, ) ``` +- Deprecated `StreamMessageWidget.showReactionPickerIndicator` in favor of `StreamMessageWidget.showReactionPicker`. + + ```diff + StreamMessageWidget( + - showReactionPickerIndicator: true/false, + + showReactionPicker: true/false, + ) + ``` ## 6.4.0 diff --git a/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj index e5d8f743..4c7de605 100644 --- a/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/stream_chat_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -204,6 +204,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 332d813b..1db7f73e 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -356,7 +356,11 @@ class _ChannelPageState extends State { ), ); }, - child: defaultWidget.copyWith(onReplyTap: reply), + child: defaultWidget.copyWith( + onReplyTap: reply, + // showReactionPicker: true, + // showReactionPickerIndicator: false, + ), ); }, ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart index 0b04ee61..1df1b411 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/handler/stream_attachment_handler_io.dart @@ -29,14 +29,16 @@ class StreamAttachmentHandlerDesktop extends StreamAttachmentHandler { options: options, ); - // Open the native file browser so the user can select the download path. - final path = await getSavePath(suggestedName: data.fileName); - - if (path == null) { + // Open the native file browser so the user can select the save location. + final saveLocation = await getSaveLocation(suggestedName: data.fileName); + if (saveLocation == null) { // Operation was canceled by the user. return null; } + // Get the path to the user's selected location. + final path = saveLocation.path; + // Create an XFile for proper file saving. final file = data.toXFile(path: path); diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart index 7fcda8a9..2af8e5e6 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart @@ -15,7 +15,7 @@ class MessageActionsModal extends StatefulWidget { required this.message, required this.messageWidget, required this.messageTheme, - this.showReactions = true, + this.showReactionPicker = true, this.showDeleteMessage = true, this.showEditMessage = true, this.onReplyTap, @@ -54,8 +54,8 @@ class MessageActionsModal extends StatefulWidget { /// [StreamMessageThemeData] for message final StreamMessageThemeData messageTheme; - /// Flag for showing reactions - final bool showReactions; + /// Flag for showing reaction picker. + final bool showReactionPicker; /// Callback when copy is tapped final OnMessageTap? onCopyTap; @@ -105,6 +105,10 @@ class _MessageActionsModalState extends State { final user = StreamChat.of(context).currentUser; final orientation = mediaQueryData.orientation; + final _userPermissions = StreamChannel.of(context).channel.ownCapabilities; + final hasReactionPermission = + _userPermissions.contains(PermissionType.sendReaction); + final fontSize = widget.messageTheme.messageTextStyle?.fontSize; final streamChatThemeData = StreamChatTheme.of(context); @@ -115,12 +119,10 @@ class _MessageActionsModalState extends State { child: Padding( padding: const EdgeInsets.all(8), child: Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (widget.showReactions && - (widget.message.status == MessageSendingStatus.sent)) + if (widget.showReactionPicker && hasReactionPermission) LayoutBuilder( builder: (context, constraints) { return Align( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index 963b2ac5..614a0f38 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -2,6 +2,7 @@ import 'package:contextmenu/contextmenu.dart'; import 'package:flutter/material.dart' hide ButtonStyle; import 'package:flutter/services.dart'; import 'package:flutter_portal/flutter_portal.dart'; +import 'package:meta/meta.dart'; import 'package:stream_chat_flutter/conditional_parent_builder/conditional_parent_builder.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction_picker.dart'; @@ -54,7 +55,10 @@ class StreamMessageWidget extends StatefulWidget { this.attachmentBorderRadiusGeometry, this.onMentionTap, this.onMessageTap, - this.showReactionPickerIndicator = false, + bool? showReactionPicker, + @Deprecated('Use `showReactionPicker` instead') + bool showReactionPickerIndicator = true, + @internal this.showReactionPickerTail = false, this.showUserAvatar = DisplayWidget.show, this.showSendingIndicator = true, this.showThreadReplyIndicator = false, @@ -114,6 +118,7 @@ class StreamMessageWidget extends StatefulWidget { bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, 'You can only use one of the two bottom row builders', ), + showReactionPicker = showReactionPicker ?? showReactionPickerIndicator, attachmentBuilders = { 'image': (context, message, attachments) { final border = RoundedRectangleBorder( @@ -460,10 +465,22 @@ class StreamMessageWidget extends StatefulWidget { /// {@endtemplate} final void Function(String)? onLinkTap; + /// {@template showReactionPicker} + /// Whether or not to show the reaction picker. + /// Used in [StreamMessageReactionsModal] and [MessageActionsModal]. + /// {@endtemplate} + final bool showReactionPicker; + /// {@template showReactionPickerIndicator} /// Used in [StreamMessageReactionsModal] and [MessageActionsModal] + /// {@endtemplate} @Deprecated('Use `showReactionPicker` instead') + bool get showReactionPickerIndicator => showReactionPicker; + + /// {@template showReactionPickerTail} + /// Whether or not to show the reaction picker tail /// {@endtemplate} - final bool showReactionPickerIndicator; + @internal + final bool showReactionPickerTail; /// {@template onShowMessage} /// Callback when show message is tapped @@ -619,7 +636,10 @@ class StreamMessageWidget extends StatefulWidget { bool? showInChannelIndicator, void Function(User)? onUserAvatarTap, void Function(String)? onLinkTap, + bool? showReactionPicker, + @Deprecated('Use `showReactionPicker` instead') bool? showReactionPickerIndicator, + @internal bool? showReactionPickerTail, List? readList, ShowMessageCallback? onShowMessage, bool? showUsername, @@ -703,8 +723,11 @@ class StreamMessageWidget extends StatefulWidget { showInChannelIndicator ?? this.showInChannelIndicator, onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap, onLinkTap: onLinkTap ?? this.onLinkTap, - showReactionPickerIndicator: - showReactionPickerIndicator ?? this.showReactionPickerIndicator, + showReactionPicker: showReactionPicker ?? + showReactionPickerIndicator ?? + this.showReactionPicker, + showReactionPickerTail: + showReactionPickerTail ?? this.showReactionPickerTail, onShowMessage: onShowMessage ?? this.onShowMessage, showUsername: showUsername ?? this.showUsername, showTimestamp: showTimestamp ?? this.showTimestamp, @@ -885,12 +908,12 @@ class _StreamMessageWidgetState extends State if (!widget.message.isDeleted) { return ContextMenuArea( verticalPadding: 0, - builder: (context) => _buildContextMenu(), + builder: (_) => _buildContextMenu(), child: child, ); - } else { - return child; } + + return child; }, child: Material( type: MaterialType.transparency, @@ -963,9 +986,9 @@ class _StreamMessageWidgetState extends State messageWidget: widget, showBottomRow: showBottomRow, showPinHighlight: widget.showPinHighlight, - showReactionPickerIndicator: - widget.showReactionPickerIndicator, + showReactionPickerTail: widget.showReactionPickerTail, showReactions: showReactions, + onReactionsTap: () => _showMessageReactionsModal(context), showUserAvatar: widget.showUserAvatar, streamChat: _streamChat, translateUserAvatar: widget.translateUserAvatar, @@ -996,14 +1019,15 @@ class _StreamMessageWidgetState extends State final channel = StreamChannel.of(context).channel; return [ - StreamChatContextMenuItem( - child: StreamChannel( - channel: channel, - child: ContextMenuReactionPicker( - message: widget.message, + if (widget.showReactionPicker) + StreamChatContextMenuItem( + child: StreamChannel( + channel: channel, + child: ContextMenuReactionPicker( + message: widget.message, + ), ), ), - ), if (shouldShowReplyAction) ...[ StreamChatContextMenuItem( leading: StreamSvgIcon.reply(), @@ -1149,21 +1173,7 @@ class _StreamMessageWidgetState extends State ]; } - void onLongPress(BuildContext context) { - if (widget.message.isEphemeral || - widget.message.status == MessageSendingStatus.sending) { - return; - } - - if (widget.onMessageActions != null) { - widget.onMessageActions!(context, widget.message); - } else { - _showMessageActionModalBottomSheet(context); - } - return; - } - - void _showMessageActionModalBottomSheet(BuildContext context) { + void _showMessageReactionsModal(BuildContext context) { final channel = StreamChannel.of(context).channel; showDialog( @@ -1173,7 +1183,8 @@ class _StreamMessageWidgetState extends State barrierColor: _streamChatTheme.colorTheme.overlay, builder: (context) => StreamChannel( channel: channel, - child: MessageActionsModal( + child: StreamMessageReactionsModal( + showReactionPicker: widget.showReactionPicker, messageWidget: widget.copyWith( key: const Key('MessageWidget'), message: widget.message.copyWith( @@ -1187,37 +1198,93 @@ class _StreamMessageWidgetState extends State translateUserAvatar: false, showSendingIndicator: false, padding: EdgeInsets.zero, - showReactionPickerIndicator: widget.showReactions && - (widget.message.status == MessageSendingStatus.sent), + // Show the tail if the reaction picker is visible. + showReactionPickerTail: widget.showReactionPicker, showPinHighlight: false, showUserAvatar: widget.message.user!.id == channel.client.state.currentUser!.id ? DisplayWidget.gone : DisplayWidget.show, ), - onCopyTap: (message) { - final text = message.text; - if (text != null) Clipboard.setData(ClipboardData(text: text)); - }, + onUserAvatarTap: widget.onUserAvatarTap, messageTheme: widget.messageTheme, reverse: widget.reverse, - showDeleteMessage: shouldShowDeleteAction, - onConfirmDeleteTap: widget.onConfirmDeleteTap, message: widget.message, - editMessageInputBuilder: widget.editMessageInputBuilder, - onReplyTap: widget.onReplyTap, - onThreadReplyTap: widget.onThreadTap, - showResendMessage: shouldShowResendAction, - showCopyMessage: shouldShowCopyAction, - showEditMessage: shouldShowEditAction, - showReactions: widget.showReactions, - showReplyMessage: shouldShowReplyAction, - showThreadReplyMessage: shouldShowThreadReplyAction, - showFlagButton: widget.showFlagButton, - showPinButton: widget.showPinButton, - customActions: widget.customActions, ), ), ); } + + void onLongPress(BuildContext context) { + if (widget.message.isEphemeral || + widget.message.status == MessageSendingStatus.sending) { + return; + } + + if (widget.onMessageActions != null) { + return widget.onMessageActions!(context, widget.message); + } + + return _showMessageActionModalBottomSheet(context); + } + + void _showMessageActionModalBottomSheet(BuildContext context) { + final channel = StreamChannel.of(context).channel; + + showDialog( + useRootNavigator: false, + context: context, + useSafeArea: false, + barrierColor: _streamChatTheme.colorTheme.overlay, + builder: (context) { + return StreamChannel( + channel: channel, + child: MessageActionsModal( + messageWidget: widget.copyWith( + key: const Key('MessageWidget'), + message: widget.message.copyWith( + text: (widget.message.text?.length ?? 0) > 200 + ? '${widget.message.text!.substring(0, 200)}...' + : widget.message.text, + ), + showReactions: false, + showUsername: false, + showTimestamp: false, + translateUserAvatar: false, + showSendingIndicator: false, + padding: EdgeInsets.zero, + // Show both the tail and indicator if the indicator is shown. + showReactionPickerTail: widget.showReactionPickerIndicator, + showPinHighlight: false, + showUserAvatar: widget.message.user!.id == + channel.client.state.currentUser!.id + ? DisplayWidget.gone + : DisplayWidget.show, + ), + onCopyTap: (message) { + final text = message.text; + if (text != null) Clipboard.setData(ClipboardData(text: text)); + }, + messageTheme: widget.messageTheme, + reverse: widget.reverse, + showDeleteMessage: shouldShowDeleteAction, + onConfirmDeleteTap: widget.onConfirmDeleteTap, + message: widget.message, + editMessageInputBuilder: widget.editMessageInputBuilder, + onReplyTap: widget.onReplyTap, + onThreadReplyTap: widget.onThreadTap, + showResendMessage: shouldShowResendAction, + showCopyMessage: shouldShowCopyAction, + showEditMessage: shouldShowEditAction, + showReactionPicker: widget.showReactionPickerIndicator, + showReplyMessage: shouldShowReplyAction, + showThreadReplyMessage: shouldShowThreadReplyAction, + showFlagButton: widget.showFlagButton, + showPinButton: widget.showPinButton, + customActions: widget.customActions, + ), + ); + }, + ); + } } diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index f8543013..2910ee62 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; +import 'package:meta/meta.dart'; import 'package:stream_chat_flutter/src/message_widget/message_widget_content_components.dart'; import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -21,6 +22,7 @@ typedef BottomRowBuilderWithDefaultWidget = Widget Function( /// /// Should not be used outside of [MessageWidget. /// {@endtemplate} +@internal class MessageWidgetContent extends StatelessWidget { /// {@macro messageWidgetContent} const MessageWidgetContent({ @@ -33,6 +35,7 @@ class MessageWidgetContent extends StatelessWidget { required this.showUserAvatar, required this.avatarWidth, required this.showReactions, + required this.onReactionsTap, required this.messageTheme, required this.shouldShowReactions, required this.streamChatTheme, @@ -45,7 +48,7 @@ class MessageWidgetContent extends StatelessWidget { required this.attachmentBuilders, required this.attachmentPadding, required this.textPadding, - required this.showReactionPickerIndicator, + required this.showReactionPickerTail, required this.translateUserAvatar, required this.bottomRowPadding, required this.showInChannel, @@ -111,6 +114,11 @@ class MessageWidgetContent extends StatelessWidget { /// {@macro showReactions} final bool showReactions; + /// Callback called when the reactions icon is tapped. + /// + /// Do not confuse this with the tap action on the reactions picker. + final VoidCallback onReactionsTap; + /// {@macro messageTheme} final StreamMessageThemeData messageTheme; @@ -174,8 +182,8 @@ class MessageWidgetContent extends StatelessWidget { /// {@macro quotedMessageBuilder} final Widget Function(BuildContext, Message)? quotedMessageBuilder; - /// {@macro showReactionPickerIndicator} - final bool showReactionPickerIndicator; + /// {@macro showReactionPickerTail} + final bool showReactionPickerTail; /// {@macro translateUserAvatar} final bool translateUserAvatar; @@ -288,9 +296,7 @@ class MessageWidgetContent extends StatelessWidget { ownId: streamChat.currentUser!.id, reverse: reverse, shouldShowReactions: shouldShowReactions, - onTap: () => _showMessageReactionsModal( - context, - ), + onTap: onReactionsTap, ) : null, anchor: Aligned( @@ -363,7 +369,8 @@ class MessageWidgetContent extends StatelessWidget { shape: shape, ), ), - if (showReactionPickerIndicator) + // TODO: Make tail part of the Reaction Picker. + if (showReactionPickerTail) Positioned( right: reverse ? null : 4, left: reverse ? 4 : null, @@ -374,6 +381,7 @@ class MessageWidgetContent extends StatelessWidget { Colors.transparent, Colors.transparent, tailCirclesSpace: 1, + flipTail: !reverse, ), ), ), @@ -434,47 +442,6 @@ class MessageWidgetContent extends StatelessWidget { ); } - void _showMessageReactionsModal(BuildContext context) { - final channel = StreamChannel.of(context).channel; - showDialog( - useRootNavigator: false, - context: context, - useSafeArea: false, - barrierColor: streamChatTheme.colorTheme.overlay, - builder: (context) => StreamChannel( - channel: channel, - child: StreamMessageReactionsModal( - messageWidget: messageWidget.copyWith( - key: const Key('MessageWidget'), - message: message.copyWith( - text: (message.text?.length ?? 0) > 200 - ? '${message.text!.substring(0, 200)}...' - : message.text, - ), - showReactions: false, - showUsername: false, - showTimestamp: false, - translateUserAvatar: false, - showSendingIndicator: false, - padding: EdgeInsets.zero, - showReactionPickerIndicator: - showReactions && (message.status == MessageSendingStatus.sent), - showPinHighlight: false, - showUserAvatar: - message.user!.id == channel.client.state.currentUser!.id - ? DisplayWidget.gone - : DisplayWidget.show, - ), - onUserAvatarTap: onUserAvatarTap, - messageTheme: messageTheme, - reverse: reverse, - message: message, - showReactions: showReactions, - ), - ), - ); - } - Widget _buildBottomRow(BuildContext context) { final defaultWidget = BottomRow( message: message, diff --git a/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart index cd66d08d..8dadc47d 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart @@ -15,7 +15,7 @@ class StreamMessageReactionsModal extends StatelessWidget { required this.message, required this.messageWidget, required this.messageTheme, - this.showReactions, + this.showReactionPicker = true, this.reverse = false, this.onUserAvatarTap, }); @@ -32,8 +32,8 @@ class StreamMessageReactionsModal extends StatelessWidget { /// {@macro reverse} final bool reverse; - /// {@macro showReactions} - final bool? showReactions; + /// Flag for showing reaction picker. + final bool showReactionPicker; /// {@macro onUserAvatarTap} final void Function(User)? onUserAvatarTap; @@ -56,8 +56,7 @@ class StreamMessageReactionsModal extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if ((showReactions ?? hasReactionPermission) && - (message.status == MessageSendingStatus.sent)) + if (showReactionPicker && hasReactionPermission) LayoutBuilder( builder: (context, constraints) { return Align( diff --git a/packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_align.dart b/packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_align.dart index e4e1ad48..a7baad7e 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_align.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/reactions/reactions_align.dart @@ -43,19 +43,9 @@ double calculateReactionsHorizontalAlignment( final signal = user?.id == message.user?.id ? 1 : -1; final result = signal * (1 - divFactor * 2.0); - return _capResult(result); -} - -// Ensure reactions don't get pushed past the edge of the screen. -// -// This happens if divFactor is really big. When this happens, we can simply -// move the model all the way to the end of screen. -double _capResult(double result) { - if (result > 1.0) { - return 1; - } else if (result < -1.0) { - return -1; - } else { - return result; - } + // Ensure reactions don't get pushed past the edge of the screen. + // + // This happens if divFactor is really big. When this happens, we can simply + // move the model all the way to the end of screen. + return result.clamp(-1, 1); } diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart new file mode 100644 index 00000000..a7afeea8 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_attachment_list_test.dart @@ -0,0 +1,190 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import '../mocks.dart'; + +Widget wrapWithStreamChat( + Widget child, { + StreamChatClient? client, +}) { + return MaterialApp( + home: StreamChat( + client: client ?? MockClient(), + child: child, + ), + ); +} + +void main() { + group('StreamMessageInputAttachmentList tests', () { + testWidgets( + 'StreamMessageInputAttachmentList should render attachments', + (WidgetTester tester) async { + final attachments = [ + Attachment(type: 'file', id: 'file1'), + Attachment(type: 'file', id: 'file2'), + Attachment(type: 'media', id: 'media1'), + ]; + + await tester.pumpWidget( + wrapWithStreamChat( + StreamMessageInputAttachmentList( + attachments: attachments, + ), + ), + ); + + // Expect 2 file attachments and 1 media attachment + expect(find.byType(MessageInputFileAttachments), findsOneWidget); + expect(find.byType(MessageInputMediaAttachments), findsOneWidget); + }, + ); + + testWidgets( + 'StreamMessageInputAttachmentList should call onRemovePressed callback', + (WidgetTester tester) async { + Attachment? removedAttachment; + + final attachments = [ + Attachment(type: 'file', id: 'file1'), + Attachment(type: 'file', id: 'file2'), + ]; + + await tester.pumpWidget( + wrapWithStreamChat( + StreamMessageInputAttachmentList( + attachments: attachments, + onRemovePressed: (attachment) { + removedAttachment = attachment; + }, + ), + ), + ); + + final removeButtons = find.byType(RemoveAttachmentButton); + + // Tap the first remove button + await tester.tap(removeButtons.first); + await tester.pump(); + + // Expect the onRemovePressed callback to be called with the second + // attachment as they are reversed in the UI. + expect(removedAttachment, attachments[1]); + }, + ); + + testWidgets( + '''StreamMessageInputAttachmentList should display empty box if no attachments''', + (WidgetTester tester) async { + final attachments = []; + + await tester.pumpWidget( + wrapWithStreamChat( + StreamMessageInputAttachmentList( + attachments: attachments, + ), + ), + ); + + // Expect an empty box + expect(find.byType(SizedBox), findsOneWidget); + }, + ); + }); + + group('MessageInputFileAttachments tests', () { + testWidgets( + 'MessageInputFileAttachments should render file attachments', + (WidgetTester tester) async { + final attachments = [ + Attachment(type: 'file', id: 'file1'), + Attachment(type: 'file', id: 'file2'), + ]; + + await tester.pumpWidget( + wrapWithStreamChat( + MessageInputFileAttachments( + attachments: attachments, + ), + ), + ); + + // Expect 2 file attachments + expect(find.byType(ClipRRect), findsNWidgets(2)); + }, + ); + + testWidgets( + 'MessageInputFileAttachments should call onRemovePressed callback', + (WidgetTester tester) async { + Attachment? removedAttachment; + + final attachments = [ + Attachment(type: 'file', id: 'file1'), + ]; + + await tester.pumpWidget( + wrapWithStreamChat( + MessageInputFileAttachments( + attachments: attachments, + onRemovePressed: (attachment) { + removedAttachment = attachment; + }, + ), + ), + ); + + final removeButton = find.byType(RemoveAttachmentButton); + + // Tap the remove button + await tester.tap(removeButton); + await tester.pump(); + + // Expect the onRemovePressed callback to be called with the attachment + expect(removedAttachment, attachments.first); + }, + ); + }); + + group('MessageInputMediaAttachments tests', () { + testWidgets( + 'MessageInputMediaAttachments should render media attachments', + (WidgetTester tester) async { + final attachments = [ + Attachment(type: 'media', id: 'media1'), + Attachment(type: 'media', id: 'media2'), + ]; + + await tester.pumpWidget( + wrapWithStreamChat( + MessageInputMediaAttachments( + attachments: attachments, + ), + ), + ); + + // Expect 2 media attachments + expect(find.byType(Stack), findsNWidgets(2)); + }, + ); + + testWidgets( + 'MessageInputMediaAttachments should display empty box if no attachments', + (WidgetTester tester) async { + final attachments = []; + + await tester.pumpWidget( + wrapWithStreamChat( + MessageInputMediaAttachments( + attachments: attachments, + ), + ), + ); + + // Expect an empty box + expect(find.byType(SizedBox), findsOneWidget); + }, + ); + }); +} diff --git a/packages/stream_chat_flutter/test/src/message_reactions_modal/message_reactions_modal_test.dart b/packages/stream_chat_flutter/test/src/message_reactions_modal/message_reactions_modal_test.dart index 54f3675b..20ecd58e 100644 --- a/packages/stream_chat_flutter/test/src/message_reactions_modal/message_reactions_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_reactions_modal/message_reactions_modal_test.dart @@ -104,7 +104,7 @@ void main() { message: message, messageTheme: streamTheme.ownMessageTheme, reverse: true, - showReactions: false, + showReactionPicker: false, onUserAvatarTap: onUserAvatarTap, ), ),