Files
stream-chat-flutter/lib/src/message_reactions_modal.dart
T
2021-01-12 12:05:01 +01:00

250 lines
8.8 KiB
Dart

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<double>(
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: <Widget>[
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,
),
],
),
);
}
}