diff --git a/fonts/stream-icons.ttf b/fonts/stream-icons.ttf index 2e4d8540..80660da4 100644 Binary files a/fonts/stream-icons.ttf and b/fonts/stream-icons.ttf differ diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index e02386d9..ec8d38be 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart'; @@ -19,6 +20,7 @@ class MessageActionsModal extends StatelessWidget { final MessageTheme messageTheme; final bool showReactions; final bool showDeleteMessage; + final bool showCopyMessage; final bool showEditMessage; final bool showReply; final bool reverse; @@ -32,6 +34,7 @@ class MessageActionsModal extends StatelessWidget { this.showDeleteMessage, this.showEditMessage, this.onThreadTap, + this.showCopyMessage = true, this.showReply, this.editMessageInputBuilder, this.messageShape, @@ -103,13 +106,14 @@ class MessageActionsModal extends StatelessWidget { children: ListTile.divideTiles( context: context, tiles: [ - if (showEditMessage) _buildEditMessage(context), if (showReply && (message.status == MessageSendingStatus.SENT || message.status == null) && message.parentId == null) _buildReplyButton(context), + if (showEditMessage) _buildEditMessage(context), if (showDeleteMessage) _buildDeleteButton(context), + if (showDeleteMessage) _buildCopyButton(context), ], ).toList(), ), @@ -142,6 +146,23 @@ class MessageActionsModal extends StatelessWidget { ); } + Widget _buildCopyButton(BuildContext context) { + return ListTile( + title: Text( + 'Copy message', + style: Theme.of(context).textTheme.headline6, + ), + leading: Icon( + StreamIcons.copy, + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + onTap: () async { + await Clipboard.setData(ClipboardData(text: message.text)); + Navigator.pop(context); + }, + ); + } + Widget _buildEditMessage(BuildContext context) { return ListTile( title: Text( diff --git a/pubspec.yaml b/pubspec.yaml index 3fb1b5ff..2c1158ae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: http_parser: ^3.1.4 flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 + clipboard: ^0.1.2+8 flutter: fonts: diff --git a/test/src/message_action_modal_test.dart b/test/src/message_action_modal_test.dart new file mode 100644 index 00000000..e491e81e --- /dev/null +++ b/test/src/message_action_modal_test.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show two reactions', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + + final themeData = ThemeData(); + final streamTheme = StreamChatThemeData.getDefaultTheme(themeData); + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: StreamChat( + streamChatThemeData: streamTheme, + client: client, + child: Container( + child: MessageReactionsModal( + message: Message( + text: 'test', + user: User( + id: 'user-id', + ), + latestReactions: [ + Reaction( + type: 'thumbs_up', + user: User(id: 'test'), + ), + Reaction( + type: 'love', + user: User(id: 'test'), + ), + ], + ), + messageTheme: streamTheme.ownMessageTheme, + ), + ), + ), + ), + ); + await tester.pump(); + + expect(find.byIcon(StreamIcons.thumbs_up_reaction), findsNWidgets(2)); + expect(find.byIcon(StreamIcons.love_reaction), findsNWidgets(2)); + expect(find.text('test'), findsNWidgets(2)); + }, + ); +}