From ca4bef0ad04eae96f048a879c59c9b2652641bc8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 25 Feb 2020 18:10:58 +0100 Subject: [PATCH] add reactions --- example/lib/multiple_conversation.dart | 2 +- lib/src/message_list_view.dart | 2 +- lib/src/message_widget.dart | 171 +++++++++++++++---------- 3 files changed, 102 insertions(+), 73 deletions(-) diff --git a/example/lib/multiple_conversation.dart b/example/lib/multiple_conversation.dart index d0cce521..64466191 100644 --- a/example/lib/multiple_conversation.dart +++ b/example/lib/multiple_conversation.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void main() async { - final client = Client('qk4nn7rpcn75'); + final client = Client('qk4nn7rpcn75', logLevel: Level.INFO); await client.setUser( User(id: 'wild-breeze-7'), diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 63327a24..0bc5528d 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -287,7 +287,7 @@ class _MessageListViewState extends State { ? widget.threadBuilder(context, message) : null); }; - } else { + } else if (widget.threadBuilder != null) { _onThreadTap = (message) { Navigator.push( context, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index fe1439eb..efeb6d6e 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -222,21 +222,34 @@ class _MessageWidgetState extends State ); if (widget.message.text.trim().isNotEmpty) { - column.children.add(Container( - margin: EdgeInsets.only( - top: nOfAttachmentWidgets > 0 ? 5 : 0, - ), - decoration: _buildBoxDecoration( - isMyMessage, isLastUser || nOfAttachmentWidgets > 0), - padding: EdgeInsets.all(10), - constraints: BoxConstraints.loose(Size.fromWidth(300)), - child: MarkdownBody( - data: '${widget.message.text}', - onTapLink: (link) { - _launchURL(link); - }, - styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)), - ), + final topPadding = widget.message.latestReactions.isNotEmpty ? 36.0 : 0.0; + column.children.add(Stack( + overflow: Overflow.visible, + children: [ + widget.message.latestReactions.isNotEmpty + ? Positioned( + left: isMyMessage ? 0 : null, + right: !isMyMessage ? 0 : null, + top: 0, + child: _buildReactions(isMyMessage)) + : SizedBox(), + Container( + margin: EdgeInsets.only( + top: nOfAttachmentWidgets > 0 ? (topPadding) : (topPadding), + ), + decoration: _buildBoxDecoration( + isMyMessage, isLastUser || nOfAttachmentWidgets > 0), + padding: EdgeInsets.all(10), + constraints: BoxConstraints.loose(Size.fromWidth(300)), + child: MarkdownBody( + data: '${widget.message.text}', + onTapLink: (link) { + _launchURL(link); + }, + styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context)), + ), + ), + ], )); } @@ -253,73 +266,72 @@ class _MessageWidgetState extends State return Row( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, - children: [ - IconButton( + children: reactionToEmoji.keys.map((reactionType) { + return IconButton( iconSize: fontSize + 10, icon: Text( - '♥️', + reactionToEmoji[reactionType], style: textStyle, ), onPressed: () { - sendReaction('love'); + sendReaction(reactionType); }, - ), - IconButton( - iconSize: fontSize + 10, - icon: Text( - '😂', - style: textStyle, - ), - onPressed: () { - sendReaction('haha'); - }, - ), - IconButton( - iconSize: fontSize + 10, - icon: Text( - '👍', - style: textStyle, - ), - onPressed: () { - sendReaction('like'); - }, - ), - IconButton( - iconSize: fontSize + 10, - icon: Text( - '😕', - style: textStyle, - ), - onPressed: () { - sendReaction('sad'); - }, - ), - IconButton( - iconSize: fontSize + 10, - icon: Text( - '😡', - style: textStyle, - ), - onPressed: () { - sendReaction('angry'); - }, - ), - IconButton( - iconSize: fontSize + 10, - icon: Text( - '😲', - style: textStyle, - ), - onPressed: () { - sendReaction('wow'); - }, - ), - ], + ); + }).toList(), ); }); }); } + Widget _buildReactions(bool isMyMessage) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Stack( + overflow: Overflow.visible, + children: [ + widget.message.latestReactions.isNotEmpty + ? Positioned( + left: isMyMessage ? 0 : null, + right: !isMyMessage ? 0 : null, + bottom: 1, + child: CustomPaint( + painter: ReactionBubblePainter(), + ), + ) + : SizedBox(), + AnimatedContainer( + transform: Matrix4.translationValues( + -16 * (isMyMessage ? 1.0 : -1.0), 0, 0), + padding: widget.message.latestReactions.isNotEmpty + ? const EdgeInsets.all(8) + : EdgeInsets.zero, + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.all(Radius.circular(14))), + duration: Duration(milliseconds: 300), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [] + ..addAll(widget.message.latestReactions.map((reaction) { + return Text(reactionToEmoji[reaction.type] ?? '?'); + })), + ), + ), + ], + ), + ); + } + + final Map reactionToEmoji = { + 'love': '♥️', + 'haha': '😂', + 'like': '👍', + 'sad': '😕', + 'angry': '😡', + 'wow': '😲', + }; + void sendReaction(String reactionType) { StreamChannel.of(context) .channelClient @@ -437,3 +449,20 @@ class _MessageWidgetState extends State return widget.message.attachments.isNotEmpty; } } + +class ReactionBubblePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..color = Colors.black; + final path = Path(); + path.arcToPoint(Offset(-5, 0)); + path.arcToPoint(Offset(0, 10), radius: Radius.circular(16)); + path.arcToPoint(Offset(5, 0), radius: Radius.circular(16)); + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) { + return true; + } +}