import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'message_widget.dart'; import 'stream_chat_theme.dart'; class MessageReactionsModal extends StatelessWidget { final Widget Function(BuildContext, Message) editMessageInputBuilder; final void Function(Message) onThreadTap; final Message message; final MessageTheme messageTheme; final bool reverse; final bool showReactions; final DisplayWidget showUserAvatar; final ShapeBorder messageShape; final void Function(User) onUserAvatarTap; const MessageReactionsModal({ Key key, @required this.message, @required this.messageTheme, this.showReactions = true, this.onThreadTap, this.editMessageInputBuilder, this.messageShape, this.reverse = false, this.showUserAvatar = DisplayWidget.show, this.onUserAvatarTap, }) : super(key: key); @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final user = StreamChat.of(context).user; final roughMaxSize = 2 * size.width / 3; var messageTextLength = message.text.length; if (message.quotedMessage != null) { var quotedMessageLength = message.quotedMessage.text.length + 40; if (message.quotedMessage.attachments?.isNotEmpty == true) { quotedMessageLength += 40; } if (quotedMessageLength > messageTextLength) { messageTextLength = quotedMessageLength; } } final roughSentenceSize = messageTextLength * messageTheme.messageText.fontSize * 1.2; final divFactor = message.attachments?.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); return TweenAnimationBuilder( tween: Tween(begin: 0.0, end: 1.0), duration: Duration(milliseconds: 300), curve: Curves.easeInOutBack, builder: (context, val, snapshot) { return GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => Navigator.maybePop(context), child: Stack( children: [ Positioned.fill( child: BackdropFilter( filter: ImageFilter.blur( sigmaX: 10, sigmaY: 10, ), child: Container( color: StreamChatTheme.of(context).colorTheme.overlay, ), ), ), Transform.scale( scale: val, child: Center( child: SingleChildScrollView( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (showReactions && (message.status == MessageSendingStatus.SENT || message.status == null)) Align( alignment: Alignment( user.id == message.user.id ? (divFactor > 1.0 ? 0.0 : (1.0 - divFactor)) : (divFactor > 1.0 ? 0.0 : -(1.0 - divFactor)), 0.0), child: ReactionPicker( message: message, messageTheme: messageTheme, ), ), IgnorePointer( child: MessageWidget( key: Key('MessageWidget'), reverse: reverse, message: message.copyWith( text: message.text.length > 200 ? '${message.text.substring(0, 200)}...' : message.text, ), messageTheme: messageTheme, showReactions: false, showUsername: false, showUserAvatar: showUserAvatar, showThreadReplyIndicator: false, showTimestamp: false, translateUserAvatar: false, showSendingIndicator: false, shape: messageShape, showInChannelIndicator: false, showReactionPickerIndicator: showReactions && (message.status == MessageSendingStatus.SENT || message.status == null), ), ), if (message.latestReactions?.isNotEmpty == true) _buildReactionCard(context), ], ), ), ), ), ), ], ), ); }, ); } Padding _buildReactionCard(BuildContext context) { final currentUser = StreamChat.of(context).user; return Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, ), child: Card( color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.hardEdge, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), margin: EdgeInsets.zero, child: Padding( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Message Reactions', style: StreamChatTheme.of(context).textTheme.headlineBold, ), const SizedBox(height: 16), Flexible( child: SingleChildScrollView( child: Wrap( spacing: 16, runSpacing: 16, alignment: WrapAlignment.start, children: message.latestReactions .map((e) => _buildReaction( e, currentUser, context, )) .toList(), ), ), ), ], ), ), ), ); } Widget _buildReaction( Reaction reaction, User currentUser, BuildContext context, ) { final isCurrentUser = reaction.user.id == currentUser.id; return ConstrainedBox( constraints: BoxConstraints.loose(Size( 64, 98, )), child: Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ Stack( children: [ UserAvatar( onTap: onUserAvatarTap, user: reaction.user, constraints: BoxConstraints.tightFor( height: 64, width: 64, ), borderRadius: BorderRadius.circular(32), ), Positioned( child: Align( alignment: reverse ? Alignment.centerRight : Alignment.centerLeft, child: ReactionBubble( reactions: [reaction], flipTail: !reverse, borderColor: messageTheme.reactionsBorderColor, backgroundColor: messageTheme.reactionsBackgroundColor, highlightOwnReactions: false, ), ), bottom: 6, left: isCurrentUser ? 0 : null, right: isCurrentUser ? 0 : null, ), ], ), const SizedBox(height: 8), Text( reaction.user.name, style: StreamChatTheme.of(context).textTheme.footnoteBold, textAlign: TextAlign.center, ), ], ), ); } }