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_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<MessageWidget>
MessageTheme messageTheme;
StreamChatState streamChat;
StreamChannelState streamChannel;
bool isMyMessage;
@override
Widget build(BuildContext context) {
@@ -80,9 +82,10 @@ class _MessageWidgetState extends State<MessageWidget>
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<MessageWidget>
crossAxisAlignment:
isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
_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<MessageWidget>
);
}
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<MessageWidget>
Widget _buildBubble(
BuildContext context,
bool isMyMessage,
bool isLastUser,
) {
var nOfAttachmentWidgets = 0;
@@ -221,13 +223,13 @@ class _MessageWidgetState extends State<MessageWidget>
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<MessageWidget>
children: <Widget>[
streamChannel.channel.config.reactions
? Align(
child: _buildReactions(isMyMessage),
child: _buildReactions(),
alignment: isMyMessage
? Alignment.centerLeft
: Alignment.centerRight,
@@ -344,7 +346,7 @@ class _MessageWidgetState extends State<MessageWidget>
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<MessageWidget>
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<MessageWidget>
),
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<MessageWidget>
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: <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(),
? ReactionPicker(
channel: StreamChannel.of(context).channel,
reactionToEmoji: reactionToEmoji,
message: widget.message,
)
: SizedBox(),
),
@@ -605,10 +568,14 @@ class _MessageWidgetState extends State<MessageWidget>
);
}
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<MessageWidget>
'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<MessageWidget>
Widget _buildVideo(
Attachment attachment,
bool isMyMessage,
bool isLastUser,
) {
VideoPlayerController videoController;
@@ -759,7 +714,7 @@ class _MessageWidgetState extends State<MessageWidget>
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<MessageWidget>
);
}
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(
+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