diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 2570bdad..1b322a9f 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -10,6 +10,7 @@ import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; +import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; @@ -34,12 +35,12 @@ class MessageWidget extends StatefulWidget { @required this.message, @required this.nextMessage, this.onThreadTap, + this.onMessageActions, this.isParent = false, - this.onMessageEdit, }) : super(key: key); - /// Function called when editing a message - final Function(Message) onMessageEdit; + /// Function called on long press + final Function(BuildContext, Message) onMessageActions; /// This message final Message message; @@ -68,6 +69,7 @@ class _MessageWidgetState extends State MessageTheme messageTheme; StreamChatState streamChat; StreamChannelState streamChannel; + bool isMyMessage; @override Widget build(BuildContext context) { @@ -80,9 +82,10 @@ class _MessageWidgetState extends State final messageUserId = widget.message.user.id; final previousUserId = widget.previousMessage?.user?.id; final nextUserId = widget.nextMessage?.user?.id; - final isMyMessage = messageUserId == currentUserId; final isLastUser = previousUserId == messageUserId; final isNextUser = nextUserId == messageUserId; + + isMyMessage = messageUserId == currentUserId; final alignment = isMyMessage ? Alignment.centerRight : Alignment.centerLeft; @@ -100,11 +103,11 @@ class _MessageWidgetState extends State crossAxisAlignment: isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - _buildBubble(context, isMyMessage, isLastUser), + _buildBubble(context, isLastUser), streamChannel.channel.config.replies - ? _buildThreadIndicator(context, isMyMessage) + ? _buildThreadIndicator(context) : SizedBox(), - isNextUser ? SizedBox() : _buildTimestamp(isMyMessage, alignment), + isNextUser ? SizedBox() : _buildTimestamp(alignment), ], ), isNextUser @@ -166,7 +169,7 @@ class _MessageWidgetState extends State ); } - Widget _buildThreadIndicator(BuildContext context, bool isMyMessage) { + Widget _buildThreadIndicator(BuildContext context) { var row = [ Text( 'Replies: ${widget.message.replyCount}', @@ -208,7 +211,6 @@ class _MessageWidgetState extends State Widget _buildBubble( BuildContext context, - bool isMyMessage, bool isLastUser, ) { var nOfAttachmentWidgets = 0; @@ -221,13 +223,13 @@ class _MessageWidgetState extends State Widget attachmentWidget; if (attachment.type == 'video') { - attachmentWidget = _buildVideo(attachment, isMyMessage, isLastUser); + attachmentWidget = _buildVideo(attachment, isLastUser); } else if (attachment.type == 'image' || attachment.type == 'giphy') { - attachmentWidget = _buildImage(isMyMessage, isLastUser, attachment); + attachmentWidget = _buildImage(isLastUser, attachment); } if (attachmentWidget != null) { - final boxDecoration = _buildBoxDecoration(isMyMessage, isLastUser) + final boxDecoration = _buildBoxDecoration(isLastUser) .copyWith(color: Color(0xffebebeb)); return Padding( padding: const EdgeInsets.only(bottom: 2.0), @@ -319,7 +321,7 @@ class _MessageWidgetState extends State children: [ streamChannel.channel.config.reactions ? Align( - child: _buildReactions(isMyMessage), + child: _buildReactions(), alignment: isMyMessage ? Alignment.centerLeft : Alignment.centerRight, @@ -344,7 +346,7 @@ class _MessageWidgetState extends State left: isMyMessage ? 8.0 : 0.0, ), child: Container( - decoration: _buildBoxDecoration(isMyMessage, + decoration: _buildBoxDecoration( isLastUser || nOfAttachmentWidgets > 0), padding: EdgeInsets.all(10), constraints: BoxConstraints.loose(Size.fromWidth(300)), @@ -385,11 +387,15 @@ class _MessageWidgetState extends State return GestureDetector( child: column, onLongPress: () { - _showMessageBottomSheet(context, isMyMessage); + if (widget.onMessageActions != null) { + widget.onMessageActions(context, widget.message); + } else { + _showMessageBottomSheet(context); + } }); } - void _showMessageBottomSheet(BuildContext context, bool isMyMessage) { + void _showMessageBottomSheet(BuildContext context) { if (!streamChannel.channel.config.reactions && !streamChannel.channel.config.replies) { return; @@ -405,10 +411,6 @@ class _MessageWidgetState extends State ), context: context, builder: (_) { - final fontSize = 30.0; - final textStyle = TextStyle( - fontSize: fontSize, - ); return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, @@ -417,49 +419,10 @@ class _MessageWidgetState extends State Container( color: Colors.black87, child: streamChannel.channel.config.reactions - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: reactionToEmoji.keys.map((reactionType) { - final ownReactionIndex = widget.message.ownReactions - .indexWhere((reaction) => - reaction.type == reactionType); - return Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - IconButton( - iconSize: fontSize + 10, - icon: Text( - reactionToEmoji[reactionType], - style: textStyle, - ), - onPressed: () { - if (ownReactionIndex != -1) { - removeReaction(reactionType); - } else { - sendReaction(reactionType); - } - }, - ), - ownReactionIndex != -1 - ? Padding( - padding: - const EdgeInsets.only(bottom: 4.0), - child: Text( - widget - .message - .ownReactions[ownReactionIndex] - .score - .toString(), - style: TextStyle(color: Colors.white), - ), - ) - : SizedBox(), - ], - ); - }).toList(), + ? ReactionPicker( + channel: StreamChannel.of(context).channel, + reactionToEmoji: reactionToEmoji, + message: widget.message, ) : SizedBox(), ), @@ -605,10 +568,14 @@ class _MessageWidgetState extends State ); } - Widget _buildReactions(bool isMyMessage) { + Widget _buildReactions() { return GestureDetector( onTap: () { - _showMessageBottomSheet(context, isMyMessage); + if (widget.onMessageActions != null) { + widget.onMessageActions(context, widget.message); + } else { + _showMessageBottomSheet(context); + } }, child: Padding( padding: EdgeInsets.symmetric( @@ -665,18 +632,7 @@ class _MessageWidgetState extends State 'wow': '😲', }; - void sendReaction(String reactionType) { - streamChannel.channel.sendReaction(widget.message.id, reactionType); - Navigator.of(context).pop(); - } - - void removeReaction(String reactionType) { - streamChannel.channel.deleteReaction(widget.message.id, reactionType); - Navigator.of(context).pop(); - } - Widget _buildImage( - bool isMyMessage, bool isLastUser, Attachment attachment, ) { @@ -689,7 +645,6 @@ class _MessageWidgetState extends State Widget _buildVideo( Attachment attachment, - bool isMyMessage, bool isLastUser, ) { VideoPlayerController videoController; @@ -759,7 +714,7 @@ class _MessageWidgetState extends State super.dispose(); } - Widget _buildTimestamp(bool isMyMessage, Alignment alignment) { + Widget _buildTimestamp(Alignment alignment) { return Padding( padding: const EdgeInsets.only(top: 5.0), child: Text( @@ -769,7 +724,7 @@ class _MessageWidgetState extends State ); } - BoxDecoration _buildBoxDecoration(bool isMyMessage, bool isLastUser) { + BoxDecoration _buildBoxDecoration(bool isLastUser) { return BoxDecoration( border: isMyMessage ? null : Border.all(color: Colors.black.withAlpha(8)), borderRadius: BorderRadius.only( diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart new file mode 100644 index 00000000..c5186a93 --- /dev/null +++ b/lib/src/reaction_picker.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; + +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker_paint.png) +/// +/// It shows a reaction picker +/// +/// Usually you don't use this widget as it's one of the default widgets used by [MessageWidget.onMessageActions]. +class ReactionPicker extends StatelessWidget { + const ReactionPicker({ + Key key, + @required this.reactionToEmoji, + @required this.message, + @required this.channel, + this.size = 40, + }) : super(key: key); + + final Map reactionToEmoji; + final Message message; + final double size; + final Channel channel; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: reactionToEmoji.keys.map((reactionType) { + final ownReactionIndex = message.ownReactions + .indexWhere((reaction) => reaction.type == reactionType); + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + IconButton( + iconSize: size, + icon: Text( + reactionToEmoji[reactionType], + style: TextStyle( + fontSize: size - 10, + ), + ), + onPressed: () { + if (ownReactionIndex != -1) { + removeReaction(context, reactionType); + } else { + sendReaction(context, reactionType); + } + }, + ), + ownReactionIndex != -1 + ? Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: Text( + message.ownReactions[ownReactionIndex].score.toString(), + style: TextStyle(color: Colors.white), + ), + ) + : SizedBox(), + ], + ); + }).toList(), + ); + } + + void sendReaction(BuildContext context, String reactionType) { + channel.sendReaction(message.id, reactionType); + Navigator.of(context).pop(); + } + + void removeReaction(BuildContext context, String reactionType) { + channel.deleteReaction(message.id, reactionType); + Navigator.of(context).pop(); + } +} diff --git a/screenshots/reaction_picker.png b/screenshots/reaction_picker.png new file mode 100644 index 00000000..eafefa31 Binary files /dev/null and b/screenshots/reaction_picker.png differ diff --git a/screenshots/reaction_picker_paint.png b/screenshots/reaction_picker_paint.png new file mode 100644 index 00000000..20028f4c Binary files /dev/null and b/screenshots/reaction_picker_paint.png differ