add reaction picker widget

This commit is contained in:
Salvatore Giordano
2020-03-03 11:29:22 +01:00
parent f3a6ba68a4
commit 62e386b36b
4 changed files with 112 additions and 79 deletions
+34 -79
View File
@@ -10,6 +10,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
import 'package:stream_chat_flutter/src/message_input.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/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_channel.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart';
@@ -34,12 +35,12 @@ class MessageWidget extends StatefulWidget {
@required this.message, @required this.message,
@required this.nextMessage, @required this.nextMessage,
this.onThreadTap, this.onThreadTap,
this.onMessageActions,
this.isParent = false, this.isParent = false,
this.onMessageEdit,
}) : super(key: key); }) : super(key: key);
/// Function called when editing a message /// Function called on long press
final Function(Message) onMessageEdit; final Function(BuildContext, Message) onMessageActions;
/// This message /// This message
final Message message; final Message message;
@@ -68,6 +69,7 @@ class _MessageWidgetState extends State<MessageWidget>
MessageTheme messageTheme; MessageTheme messageTheme;
StreamChatState streamChat; StreamChatState streamChat;
StreamChannelState streamChannel; StreamChannelState streamChannel;
bool isMyMessage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -80,9 +82,10 @@ class _MessageWidgetState extends State<MessageWidget>
final messageUserId = widget.message.user.id; final messageUserId = widget.message.user.id;
final previousUserId = widget.previousMessage?.user?.id; final previousUserId = widget.previousMessage?.user?.id;
final nextUserId = widget.nextMessage?.user?.id; final nextUserId = widget.nextMessage?.user?.id;
final isMyMessage = messageUserId == currentUserId;
final isLastUser = previousUserId == messageUserId; final isLastUser = previousUserId == messageUserId;
final isNextUser = nextUserId == messageUserId; final isNextUser = nextUserId == messageUserId;
isMyMessage = messageUserId == currentUserId;
final alignment = final alignment =
isMyMessage ? Alignment.centerRight : Alignment.centerLeft; isMyMessage ? Alignment.centerRight : Alignment.centerLeft;
@@ -100,11 +103,11 @@ class _MessageWidgetState extends State<MessageWidget>
crossAxisAlignment: crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
_buildBubble(context, isMyMessage, isLastUser), _buildBubble(context, isLastUser),
streamChannel.channel.config.replies streamChannel.channel.config.replies
? _buildThreadIndicator(context, isMyMessage) ? _buildThreadIndicator(context)
: SizedBox(), : SizedBox(),
isNextUser ? SizedBox() : _buildTimestamp(isMyMessage, alignment), isNextUser ? SizedBox() : _buildTimestamp(alignment),
], ],
), ),
isNextUser isNextUser
@@ -166,7 +169,7 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
Widget _buildThreadIndicator(BuildContext context, bool isMyMessage) { Widget _buildThreadIndicator(BuildContext context) {
var row = [ var row = [
Text( Text(
'Replies: ${widget.message.replyCount}', 'Replies: ${widget.message.replyCount}',
@@ -208,7 +211,6 @@ class _MessageWidgetState extends State<MessageWidget>
Widget _buildBubble( Widget _buildBubble(
BuildContext context, BuildContext context,
bool isMyMessage,
bool isLastUser, bool isLastUser,
) { ) {
var nOfAttachmentWidgets = 0; var nOfAttachmentWidgets = 0;
@@ -221,13 +223,13 @@ class _MessageWidgetState extends State<MessageWidget>
Widget attachmentWidget; Widget attachmentWidget;
if (attachment.type == 'video') { if (attachment.type == 'video') {
attachmentWidget = _buildVideo(attachment, isMyMessage, isLastUser); attachmentWidget = _buildVideo(attachment, isLastUser);
} else if (attachment.type == 'image' || attachment.type == 'giphy') { } else if (attachment.type == 'image' || attachment.type == 'giphy') {
attachmentWidget = _buildImage(isMyMessage, isLastUser, attachment); attachmentWidget = _buildImage(isLastUser, attachment);
} }
if (attachmentWidget != null) { if (attachmentWidget != null) {
final boxDecoration = _buildBoxDecoration(isMyMessage, isLastUser) final boxDecoration = _buildBoxDecoration(isLastUser)
.copyWith(color: Color(0xffebebeb)); .copyWith(color: Color(0xffebebeb));
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 2.0), padding: const EdgeInsets.only(bottom: 2.0),
@@ -319,7 +321,7 @@ class _MessageWidgetState extends State<MessageWidget>
children: <Widget>[ children: <Widget>[
streamChannel.channel.config.reactions streamChannel.channel.config.reactions
? Align( ? Align(
child: _buildReactions(isMyMessage), child: _buildReactions(),
alignment: isMyMessage alignment: isMyMessage
? Alignment.centerLeft ? Alignment.centerLeft
: Alignment.centerRight, : Alignment.centerRight,
@@ -344,7 +346,7 @@ class _MessageWidgetState extends State<MessageWidget>
left: isMyMessage ? 8.0 : 0.0, left: isMyMessage ? 8.0 : 0.0,
), ),
child: Container( child: Container(
decoration: _buildBoxDecoration(isMyMessage, decoration: _buildBoxDecoration(
isLastUser || nOfAttachmentWidgets > 0), isLastUser || nOfAttachmentWidgets > 0),
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
constraints: BoxConstraints.loose(Size.fromWidth(300)), constraints: BoxConstraints.loose(Size.fromWidth(300)),
@@ -385,11 +387,15 @@ class _MessageWidgetState extends State<MessageWidget>
return GestureDetector( return GestureDetector(
child: column, child: column,
onLongPress: () { 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 && if (!streamChannel.channel.config.reactions &&
!streamChannel.channel.config.replies) { !streamChannel.channel.config.replies) {
return; return;
@@ -405,10 +411,6 @@ class _MessageWidgetState extends State<MessageWidget>
), ),
context: context, context: context,
builder: (_) { builder: (_) {
final fontSize = 30.0;
final textStyle = TextStyle(
fontSize: fontSize,
);
return SafeArea( return SafeArea(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -417,49 +419,10 @@ class _MessageWidgetState extends State<MessageWidget>
Container( Container(
color: Colors.black87, color: Colors.black87,
child: streamChannel.channel.config.reactions child: streamChannel.channel.config.reactions
? Row( ? ReactionPicker(
crossAxisAlignment: CrossAxisAlignment.start, channel: StreamChannel.of(context).channel,
mainAxisAlignment: MainAxisAlignment.center, reactionToEmoji: reactionToEmoji,
mainAxisSize: MainAxisSize.min, message: widget.message,
children: reactionToEmoji.keys.map((reactionType) {
final ownReactionIndex = widget.message.ownReactions
.indexWhere((reaction) =>
reaction.type == reactionType);
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
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(),
) )
: SizedBox(), : SizedBox(),
), ),
@@ -605,10 +568,14 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
Widget _buildReactions(bool isMyMessage) { Widget _buildReactions() {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
_showMessageBottomSheet(context, isMyMessage); if (widget.onMessageActions != null) {
widget.onMessageActions(context, widget.message);
} else {
_showMessageBottomSheet(context);
}
}, },
child: Padding( child: Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
@@ -665,18 +632,7 @@ class _MessageWidgetState extends State<MessageWidget>
'wow': '😲', '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( Widget _buildImage(
bool isMyMessage,
bool isLastUser, bool isLastUser,
Attachment attachment, Attachment attachment,
) { ) {
@@ -689,7 +645,6 @@ class _MessageWidgetState extends State<MessageWidget>
Widget _buildVideo( Widget _buildVideo(
Attachment attachment, Attachment attachment,
bool isMyMessage,
bool isLastUser, bool isLastUser,
) { ) {
VideoPlayerController videoController; VideoPlayerController videoController;
@@ -759,7 +714,7 @@ class _MessageWidgetState extends State<MessageWidget>
super.dispose(); super.dispose();
} }
Widget _buildTimestamp(bool isMyMessage, Alignment alignment) { Widget _buildTimestamp(Alignment alignment) {
return Padding( return Padding(
padding: const EdgeInsets.only(top: 5.0), padding: const EdgeInsets.only(top: 5.0),
child: Text( child: Text(
@@ -769,7 +724,7 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
BoxDecoration _buildBoxDecoration(bool isMyMessage, bool isLastUser) { BoxDecoration _buildBoxDecoration(bool isLastUser) {
return BoxDecoration( return BoxDecoration(
border: isMyMessage ? null : Border.all(color: Colors.black.withAlpha(8)), border: isMyMessage ? null : Border.all(color: Colors.black.withAlpha(8)),
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
+78
View File
@@ -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<String, String> 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: <Widget>[
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();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB