Merge pull request #497 from GetStream/feat/defaultMessageWidget
feat: make default message widget easier to reuse
This commit is contained in:
@@ -93,6 +93,7 @@ class ChannelPage extends StatelessWidget {
|
||||
BuildContext context,
|
||||
MessageDetails details,
|
||||
List<Message> messages,
|
||||
MessageWidget _,
|
||||
) {
|
||||
final message = details.message;
|
||||
final isCurrentUser = StreamChat.of(context).user!.id == message.user!.id;
|
||||
|
||||
@@ -15,6 +15,7 @@ class MessageActionsModal extends StatefulWidget {
|
||||
const MessageActionsModal({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageWidget,
|
||||
required this.messageTheme,
|
||||
this.showReactions = true,
|
||||
this.showDeleteMessage = true,
|
||||
@@ -27,18 +28,15 @@ class MessageActionsModal extends StatefulWidget {
|
||||
this.showThreadReplyMessage = true,
|
||||
this.showFlagButton = true,
|
||||
this.showPinButton = true,
|
||||
this.showPinHighlight = false,
|
||||
this.showUserAvatar = DisplayWidget.show,
|
||||
this.editMessageInputBuilder,
|
||||
this.messageShape,
|
||||
this.attachmentShape,
|
||||
this.reverse = false,
|
||||
this.customActions = const [],
|
||||
this.attachmentBorderRadiusGeometry,
|
||||
this.onCopyTap,
|
||||
this.textBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Widget that shows the message
|
||||
final Widget messageWidget;
|
||||
|
||||
/// Builder for edit message
|
||||
final Widget Function(BuildContext, Message)? editMessageInputBuilder;
|
||||
|
||||
@@ -84,30 +82,12 @@ class MessageActionsModal extends StatefulWidget {
|
||||
/// Flag for showing pin action
|
||||
final bool showPinButton;
|
||||
|
||||
/// Display Pin Highlight
|
||||
final bool showPinHighlight;
|
||||
|
||||
/// Flag for reversing message
|
||||
final bool reverse;
|
||||
|
||||
/// [ShapeBorder] to apply to the widget
|
||||
final ShapeBorder? messageShape;
|
||||
|
||||
/// [ShapeBorder] to apply to attachment
|
||||
final ShapeBorder? attachmentShape;
|
||||
|
||||
/// Enum for displaying user avatar
|
||||
final DisplayWidget showUserAvatar;
|
||||
|
||||
/// [BorderRadius] for attachment border
|
||||
final BorderRadius? attachmentBorderRadiusGeometry;
|
||||
|
||||
/// List of custom actions
|
||||
final List<MessageAction> customActions;
|
||||
|
||||
/// Customize the MessageWidget textBuilder
|
||||
final Widget Function(BuildContext context, Message message)? textBuilder;
|
||||
|
||||
@override
|
||||
_MessageActionsModalState createState() => _MessageActionsModalState();
|
||||
}
|
||||
@@ -142,9 +122,6 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
? 1
|
||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||
|
||||
final hasFileAttachment =
|
||||
widget.message.attachments.any((it) => it.type == 'file') == true;
|
||||
|
||||
final streamChatThemeData = StreamChatTheme.of(context);
|
||||
|
||||
final numberOfReactions = streamChatThemeData.reactionIcons.length;
|
||||
@@ -178,41 +155,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
IgnorePointer(
|
||||
child: MessageWidget(
|
||||
key: const Key('MessageWidget'),
|
||||
reverse: widget.reverse,
|
||||
attachmentBorderRadiusGeometry: widget
|
||||
.attachmentBorderRadiusGeometry
|
||||
?.mirrorBorderIfReversed(reverse: !widget.reverse),
|
||||
message: widget.message.copyWith(
|
||||
text: widget.message.text!.length > 200
|
||||
// ignore: lines_longer_than_80_chars
|
||||
? '${widget.message.text!.substring(0, 200)}...'
|
||||
: widget.message.text,
|
||||
),
|
||||
messageTheme: widget.messageTheme,
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showReplyMessage: false,
|
||||
showUserAvatar: widget.showUserAvatar,
|
||||
attachmentPadding: EdgeInsets.all(
|
||||
hasFileAttachment ? 4 : 2,
|
||||
),
|
||||
showTimestamp: false,
|
||||
translateUserAvatar: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
textPadding: EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: widget.message.text!.isOnlyEmoji ? 0 : 16.0,
|
||||
),
|
||||
showReactionPickerIndicator: widget.showReactions &&
|
||||
(widget.message.status == MessageSendingStatus.sent),
|
||||
showSendingIndicator: false,
|
||||
shape: widget.messageShape,
|
||||
attachmentShape: widget.attachmentShape,
|
||||
showPinHighlight: false,
|
||||
textBuilder: widget.textBuilder,
|
||||
),
|
||||
child: widget.messageWidget,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
|
||||
@@ -19,16 +19,22 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
/// Widget builder for message
|
||||
/// [defaultMessageWidget] is the default [MessageWidget] configuration
|
||||
/// Use [defaultMessageWidget.copyWith] to easily customize it
|
||||
typedef MessageBuilder = Widget Function(
|
||||
BuildContext,
|
||||
MessageDetails,
|
||||
List<Message>,
|
||||
MessageWidget defaultMessageWidget,
|
||||
);
|
||||
|
||||
/// Widget builder for parent message
|
||||
/// [defaultMessageWidget] is the default [MessageWidget] configuration
|
||||
/// Use [defaultMessageWidget.copyWith] to easily customize it
|
||||
typedef ParentMessageBuilder = Widget Function(
|
||||
BuildContext,
|
||||
Message?,
|
||||
MessageWidget defaultMessageWidget,
|
||||
);
|
||||
|
||||
/// Widget builder for system message
|
||||
@@ -56,12 +62,12 @@ typedef ReplyTapCallback = void Function(Message);
|
||||
class MessageDetails {
|
||||
/// Constructor for creating [MessageDetails]
|
||||
MessageDetails(
|
||||
BuildContext context,
|
||||
String currentUserId,
|
||||
this.message,
|
||||
List<Message> messages,
|
||||
this.index,
|
||||
) {
|
||||
isMyMessage = message.user?.id == StreamChat.of(context).user?.id;
|
||||
isMyMessage = message.user?.id == currentUserId;
|
||||
isLastUser = index + 1 < messages.length &&
|
||||
message.user?.id == messages[index + 1].user?.id;
|
||||
isNextUser =
|
||||
@@ -69,19 +75,19 @@ class MessageDetails {
|
||||
}
|
||||
|
||||
/// True if the message belongs to the current user
|
||||
bool? isMyMessage;
|
||||
late final bool isMyMessage;
|
||||
|
||||
/// True if the user message is the same of the previous message
|
||||
bool? isLastUser;
|
||||
late final bool isLastUser;
|
||||
|
||||
/// True if the user message is the same of the next message
|
||||
bool? isNextUser;
|
||||
late final bool isNextUser;
|
||||
|
||||
/// The message
|
||||
Message message;
|
||||
final Message message;
|
||||
|
||||
/// The index of the message
|
||||
int index;
|
||||
final int index;
|
||||
}
|
||||
|
||||
/// 
|
||||
@@ -545,11 +551,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
itemBuilder: (context, i) {
|
||||
if (i == itemCount - 1) {
|
||||
if (widget.parentMessage == null) return const Offstage();
|
||||
return widget.parentMessageBuilder?.call(
|
||||
context,
|
||||
widget.parentMessage,
|
||||
) ??
|
||||
buildParentMessage(widget.parentMessage!);
|
||||
return buildParentMessage(widget.parentMessage!);
|
||||
}
|
||||
|
||||
if (i == itemCount - 2) {
|
||||
@@ -576,46 +578,21 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
const Offstage();
|
||||
}
|
||||
|
||||
final topMessageIndex = itemCount -
|
||||
4; // 7 -> parent // 6 -> header // 5 -> loader
|
||||
const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer
|
||||
|
||||
final message = messages[i - 2];
|
||||
Widget messageWidget;
|
||||
|
||||
if (i == topMessageIndex) {
|
||||
messageWidget = _buildTopMessage(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
streamChannel,
|
||||
i,
|
||||
);
|
||||
} else if (i == bottomMessageIndex) {
|
||||
if (i == bottomMessageIndex) {
|
||||
messageWidget = _buildBottomMessage(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
streamChannel!,
|
||||
i,
|
||||
i - 2,
|
||||
);
|
||||
} else {
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = Builder(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
builder: (context) => widget.messageBuilder!(
|
||||
context,
|
||||
MessageDetails(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
i,
|
||||
),
|
||||
messages),
|
||||
);
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, i);
|
||||
}
|
||||
messageWidget = buildMessage(message, messages, i - 2);
|
||||
}
|
||||
return messageWidget;
|
||||
},
|
||||
@@ -785,34 +762,6 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
isThreadConversation: _isThreadConversation,
|
||||
);
|
||||
|
||||
Widget _buildTopMessage(
|
||||
BuildContext context,
|
||||
Message message,
|
||||
List<Message> messages,
|
||||
StreamChannelState? streamChannel,
|
||||
int index,
|
||||
) {
|
||||
Widget messageWidget;
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = Builder(
|
||||
key: const ValueKey<String>('TOP-MESSAGE'),
|
||||
builder: (_) => widget.messageBuilder!(
|
||||
context,
|
||||
MessageDetails(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
messages.length - 1,
|
||||
),
|
||||
messages,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, index);
|
||||
}
|
||||
return messageWidget;
|
||||
}
|
||||
|
||||
Widget _buildBottomMessage(
|
||||
BuildContext context,
|
||||
Message message,
|
||||
@@ -820,24 +769,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
StreamChannelState streamChannel,
|
||||
int index,
|
||||
) {
|
||||
Widget messageWidget;
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = Builder(
|
||||
key: ValueKey<String>('BOTTOM-MESSAGE-${message.id}'),
|
||||
builder: (_) => widget.messageBuilder!(
|
||||
context,
|
||||
MessageDetails(
|
||||
context,
|
||||
message,
|
||||
messages,
|
||||
0,
|
||||
),
|
||||
messages,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
messageWidget = buildMessage(message, messages, index);
|
||||
}
|
||||
final messageWidget = buildMessage(message, messages, index);
|
||||
|
||||
return VisibilityDetector(
|
||||
key: ValueKey<String>('BOTTOM-MESSAGE-${message.id}'),
|
||||
@@ -871,7 +803,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
final currentUserMember =
|
||||
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
||||
|
||||
return MessageWidget(
|
||||
final defaultMessageWidget = MessageWidget(
|
||||
showReplyMessage: false,
|
||||
showResendMessage: false,
|
||||
showThreadReplyMessage: false,
|
||||
@@ -925,6 +857,16 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
showPinButton: currentUserMember != null &&
|
||||
widget.pinPermissions.contains(currentUserMember.role),
|
||||
);
|
||||
|
||||
if (widget.parentMessageBuilder != null) {
|
||||
return widget.parentMessageBuilder!.call(
|
||||
context,
|
||||
widget.parentMessage,
|
||||
defaultMessageWidget,
|
||||
);
|
||||
}
|
||||
|
||||
return defaultMessageWidget;
|
||||
}
|
||||
|
||||
Widget buildMessage(
|
||||
@@ -1015,7 +957,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
final currentUserMember =
|
||||
members.firstWhere((e) => e.user!.id == currentUser!.id);
|
||||
|
||||
Widget child = MessageWidget(
|
||||
Widget messageWidget = MessageWidget(
|
||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||
message: message,
|
||||
reverse: isMyMessage,
|
||||
@@ -1130,6 +1072,21 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
showPinButton: widget.pinPermissions.contains(currentUserMember.role),
|
||||
);
|
||||
|
||||
if (widget.messageBuilder != null) {
|
||||
messageWidget = widget.messageBuilder!(
|
||||
context,
|
||||
MessageDetails(
|
||||
userId,
|
||||
message,
|
||||
messages,
|
||||
index,
|
||||
),
|
||||
messages,
|
||||
messageWidget as MessageWidget,
|
||||
);
|
||||
}
|
||||
|
||||
var child = messageWidget;
|
||||
if (!message.isDeleted &&
|
||||
!message.isSystem &&
|
||||
!message.isEphemeral &&
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.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';
|
||||
@@ -15,17 +14,16 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
const MessageReactionsModal({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageWidget,
|
||||
required this.messageTheme,
|
||||
this.showReactions = true,
|
||||
this.messageShape,
|
||||
this.attachmentShape,
|
||||
this.reverse = false,
|
||||
this.showUserAvatar = DisplayWidget.show,
|
||||
this.onUserAvatarTap,
|
||||
this.attachmentBorderRadiusGeometry,
|
||||
this.textBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Widget that shows the message
|
||||
final Widget messageWidget;
|
||||
|
||||
/// Message to display reactions of
|
||||
final Message message;
|
||||
|
||||
@@ -38,24 +36,9 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
/// Flag to show reactions on message
|
||||
final bool showReactions;
|
||||
|
||||
/// Enum to change user avatar config
|
||||
final DisplayWidget showUserAvatar;
|
||||
|
||||
/// [ShapeBorder] to apply to message
|
||||
final ShapeBorder? messageShape;
|
||||
|
||||
/// [ShapeBorder] to apply to attachment
|
||||
final ShapeBorder? attachmentShape;
|
||||
|
||||
/// Callback when user avatar is tapped
|
||||
final void Function(User)? onUserAvatarTap;
|
||||
|
||||
/// [BorderRadius] to apply to attachments
|
||||
final BorderRadius? attachmentBorderRadiusGeometry;
|
||||
|
||||
/// Customize the MessageWidget textBuilder
|
||||
final Widget Function(BuildContext context, Message message)? textBuilder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
@@ -77,8 +60,6 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
final divFactor = message.attachments.isNotEmpty == true
|
||||
? 1
|
||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||
final hasFileAttachment =
|
||||
message.attachments.any((it) => it.type == 'file') == true;
|
||||
|
||||
final numberOfReactions = StreamChatTheme.of(context).reactionIcons.length;
|
||||
final shiftFactor =
|
||||
@@ -110,38 +91,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
IgnorePointer(
|
||||
child: MessageWidget(
|
||||
key: const 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,
|
||||
showTimestamp: false,
|
||||
translateUserAvatar: false,
|
||||
showSendingIndicator: false,
|
||||
shape: messageShape,
|
||||
attachmentShape: attachmentShape,
|
||||
padding: const EdgeInsets.all(0),
|
||||
attachmentBorderRadiusGeometry: attachmentBorderRadiusGeometry
|
||||
?.mirrorBorderIfReversed(reverse: !reverse),
|
||||
attachmentPadding: EdgeInsets.all(
|
||||
hasFileAttachment ? 4 : 2,
|
||||
),
|
||||
textPadding: EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
horizontal: message.text!.isOnlyEmoji ? 0 : 16.0,
|
||||
),
|
||||
showReactionPickerIndicator: showReactions &&
|
||||
(message.status == MessageSendingStatus.sent),
|
||||
textBuilder: textBuilder,
|
||||
showPinHighlight: false,
|
||||
),
|
||||
child: messageWidget,
|
||||
),
|
||||
if (message.latestReactions?.isNotEmpty == true) ...[
|
||||
const SizedBox(height: 8),
|
||||
|
||||
@@ -397,6 +397,117 @@ class MessageWidget extends StatefulWidget {
|
||||
/// Customize onTap on attachment
|
||||
final void Function(Message message, Attachment attachment)? onAttachmentTap;
|
||||
|
||||
/// Creates a copy of [MessageWidget] with specified attributes overridden.
|
||||
MessageWidget copyWith({
|
||||
Key? key,
|
||||
void Function(User)? onMentionTap,
|
||||
void Function(Message)? onThreadTap,
|
||||
void Function(Message)? onReplyTap,
|
||||
Widget Function(BuildContext, Message)? editMessageInputBuilder,
|
||||
Widget Function(BuildContext, Message)? textBuilder,
|
||||
Widget Function(BuildContext, Message)? usernameBuilder,
|
||||
void Function(BuildContext, Message)? onMessageActions,
|
||||
Message? message,
|
||||
MessageTheme? messageTheme,
|
||||
bool? reverse,
|
||||
ShapeBorder? shape,
|
||||
ShapeBorder? attachmentShape,
|
||||
BorderSide? borderSide,
|
||||
BorderSide? attachmentBorderSide,
|
||||
BorderRadiusGeometry? borderRadiusGeometry,
|
||||
BorderRadiusGeometry? attachmentBorderRadiusGeometry,
|
||||
EdgeInsetsGeometry? padding,
|
||||
EdgeInsets? textPadding,
|
||||
EdgeInsetsGeometry? attachmentPadding,
|
||||
DisplayWidget? showUserAvatar,
|
||||
bool? showSendingIndicator,
|
||||
bool? showReactions,
|
||||
bool? allRead,
|
||||
bool? showThreadReplyIndicator,
|
||||
bool? showInChannelIndicator,
|
||||
void Function(User)? onUserAvatarTap,
|
||||
void Function(String)? onLinkTap,
|
||||
bool? showReactionPickerIndicator,
|
||||
List<Read>? readList,
|
||||
ShowMessageCallback? onShowMessage,
|
||||
ValueChanged<ReturnActionType>? onReturnAction,
|
||||
bool? showUsername,
|
||||
bool? showTimestamp,
|
||||
bool? showReplyMessage,
|
||||
bool? showThreadReplyMessage,
|
||||
bool? showEditMessage,
|
||||
bool? showCopyMessage,
|
||||
bool? showDeleteMessage,
|
||||
bool? showResendMessage,
|
||||
bool? showFlagButton,
|
||||
bool? showPinButton,
|
||||
bool? showPinHighlight,
|
||||
Map<String, AttachmentBuilder>? customAttachmentBuilders,
|
||||
bool? translateUserAvatar,
|
||||
OnQuotedMessageTap? onQuotedMessageTap,
|
||||
void Function(Message)? onMessageTap,
|
||||
List<MessageAction>? customActions,
|
||||
void Function(Message message, Attachment attachment)? onAttachmentTap,
|
||||
}) =>
|
||||
MessageWidget(
|
||||
key: key ?? this.key,
|
||||
onMentionTap: onMentionTap ?? this.onMentionTap,
|
||||
onThreadTap: onThreadTap ?? this.onThreadTap,
|
||||
onReplyTap: onReplyTap ?? this.onReplyTap,
|
||||
editMessageInputBuilder:
|
||||
editMessageInputBuilder ?? this.editMessageInputBuilder,
|
||||
textBuilder: textBuilder ?? this.textBuilder,
|
||||
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
|
||||
onMessageActions: onMessageActions ?? this.onMessageActions,
|
||||
message: message ?? this.message,
|
||||
messageTheme: messageTheme ?? this.messageTheme,
|
||||
reverse: reverse ?? this.reverse,
|
||||
shape: shape ?? this.shape,
|
||||
attachmentShape: attachmentShape ?? this.attachmentShape,
|
||||
borderSide: borderSide ?? this.borderSide,
|
||||
attachmentBorderSide: attachmentBorderSide ?? this.attachmentBorderSide,
|
||||
borderRadiusGeometry: borderRadiusGeometry ?? this.borderRadiusGeometry,
|
||||
attachmentBorderRadiusGeometry: attachmentBorderRadiusGeometry ??
|
||||
this.attachmentBorderRadiusGeometry,
|
||||
padding: padding ?? this.padding,
|
||||
textPadding: textPadding ?? this.textPadding,
|
||||
attachmentPadding: attachmentPadding ?? this.attachmentPadding,
|
||||
showUserAvatar: showUserAvatar ?? this.showUserAvatar,
|
||||
showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator,
|
||||
showReactions: showReactions ?? this.showReactions,
|
||||
allRead: allRead ?? this.allRead,
|
||||
showThreadReplyIndicator:
|
||||
showThreadReplyIndicator ?? this.showThreadReplyIndicator,
|
||||
showInChannelIndicator:
|
||||
showInChannelIndicator ?? this.showInChannelIndicator,
|
||||
onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap,
|
||||
onLinkTap: onLinkTap ?? this.onLinkTap,
|
||||
showReactionPickerIndicator:
|
||||
showReactionPickerIndicator ?? this.showReactionPickerIndicator,
|
||||
readList: readList ?? this.readList,
|
||||
onShowMessage: onShowMessage ?? this.onShowMessage,
|
||||
onReturnAction: onReturnAction ?? this.onReturnAction,
|
||||
showUsername: showUsername ?? this.showUsername,
|
||||
showTimestamp: showTimestamp ?? this.showTimestamp,
|
||||
showReplyMessage: showReplyMessage ?? this.showReplyMessage,
|
||||
showThreadReplyMessage:
|
||||
showThreadReplyMessage ?? this.showThreadReplyMessage,
|
||||
showEditMessage: showEditMessage ?? this.showEditMessage,
|
||||
showCopyMessage: showCopyMessage ?? this.showCopyMessage,
|
||||
showDeleteMessage: showDeleteMessage ?? this.showDeleteMessage,
|
||||
showResendMessage: showResendMessage ?? this.showResendMessage,
|
||||
showFlagButton: showFlagButton ?? this.showFlagButton,
|
||||
showPinButton: showPinButton ?? this.showPinButton,
|
||||
showPinHighlight: showPinHighlight ?? this.showPinHighlight,
|
||||
customAttachmentBuilders:
|
||||
customAttachmentBuilders ?? attachmentBuilders,
|
||||
translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar,
|
||||
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
|
||||
onMessageTap: onMessageTap ?? this.onMessageTap,
|
||||
customActions: customActions ?? this.customActions,
|
||||
onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap,
|
||||
);
|
||||
|
||||
@override
|
||||
_MessageWidgetState createState() => _MessageWidgetState();
|
||||
}
|
||||
@@ -461,7 +572,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
super.build(context);
|
||||
final avatarWidth =
|
||||
widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40;
|
||||
final leftPadding =
|
||||
final bottomRowPadding =
|
||||
widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5;
|
||||
|
||||
return Material(
|
||||
@@ -516,7 +627,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
if (widget.showUserAvatar ==
|
||||
if (!widget.reverse &&
|
||||
widget.showUserAvatar ==
|
||||
DisplayWidget.show &&
|
||||
widget.message.user != null) ...[
|
||||
_buildUserAvatar(),
|
||||
@@ -642,6 +754,13 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.reverse &&
|
||||
widget.showUserAvatar ==
|
||||
DisplayWidget.show &&
|
||||
widget.message.user != null) ...[
|
||||
_buildUserAvatar(),
|
||||
const SizedBox(width: 4),
|
||||
]
|
||||
],
|
||||
),
|
||||
if (showBottomRow)
|
||||
@@ -652,7 +771,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
if (showBottomRow)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: leftPadding,
|
||||
left: !widget.reverse ? bottomRowPadding : 0,
|
||||
right: widget.reverse ? bottomRowPadding : 0,
|
||||
bottom:
|
||||
isPinned && widget.showPinHighlight ? 6.0 : 0.0,
|
||||
),
|
||||
@@ -912,19 +1032,30 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: MessageActionsModal(
|
||||
textBuilder: widget.textBuilder,
|
||||
messageWidget: widget.copyWith(
|
||||
key: const Key('MessageWidget'),
|
||||
message: widget.message.copyWith(
|
||||
text: widget.message.text!.length > 200
|
||||
? '${widget.message.text!.substring(0, 200)}...'
|
||||
: widget.message.text,
|
||||
),
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showTimestamp: false,
|
||||
translateUserAvatar: false,
|
||||
showSendingIndicator: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
showReactionPickerIndicator: widget.showReactions &&
|
||||
(widget.message.status == MessageSendingStatus.sent),
|
||||
showPinHighlight: false,
|
||||
showUserAvatar:
|
||||
widget.message.user!.id == channel.client.state.user!.id
|
||||
? DisplayWidget.gone
|
||||
: DisplayWidget.show,
|
||||
),
|
||||
onCopyTap: (message) =>
|
||||
Clipboard.setData(ClipboardData(text: message.text)),
|
||||
attachmentBorderRadiusGeometry:
|
||||
widget.attachmentBorderRadiusGeometry as BorderRadius?,
|
||||
showUserAvatar:
|
||||
widget.message.user!.id == channel.client.state.user!.id
|
||||
? DisplayWidget.gone
|
||||
: DisplayWidget.show,
|
||||
messageTheme: widget.messageTheme,
|
||||
messageShape: widget.shape ?? _getDefaultShape(context),
|
||||
attachmentShape: widget.attachmentShape ??
|
||||
_getDefaultAttachmentShape(context),
|
||||
reverse: widget.reverse,
|
||||
showDeleteMessage: widget.showDeleteMessage || isDeleteFailed,
|
||||
message: widget.message,
|
||||
@@ -964,18 +1095,29 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: MessageReactionsModal(
|
||||
textBuilder: widget.textBuilder,
|
||||
attachmentBorderRadiusGeometry:
|
||||
widget.attachmentBorderRadiusGeometry as BorderRadius?,
|
||||
showUserAvatar:
|
||||
widget.message.user!.id == channel.client.state.user!.id
|
||||
? DisplayWidget.gone
|
||||
: DisplayWidget.show,
|
||||
messageWidget: widget.copyWith(
|
||||
key: const Key('MessageWidget'),
|
||||
message: widget.message.copyWith(
|
||||
text: widget.message.text!.length > 200
|
||||
? '${widget.message.text!.substring(0, 200)}...'
|
||||
: widget.message.text,
|
||||
),
|
||||
showReactions: false,
|
||||
showUsername: false,
|
||||
showTimestamp: false,
|
||||
translateUserAvatar: false,
|
||||
showSendingIndicator: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
showReactionPickerIndicator: widget.showReactions &&
|
||||
(widget.message.status == MessageSendingStatus.sent),
|
||||
showPinHighlight: false,
|
||||
showUserAvatar:
|
||||
widget.message.user!.id == channel.client.state.user!.id
|
||||
? DisplayWidget.gone
|
||||
: DisplayWidget.show,
|
||||
),
|
||||
onUserAvatarTap: widget.onUserAvatarTap,
|
||||
messageTheme: widget.messageTheme,
|
||||
messageShape: widget.shape ?? _getDefaultShape(context),
|
||||
attachmentShape:
|
||||
widget.attachmentShape ?? _getDefaultAttachmentShape(context),
|
||||
reverse: widget.reverse,
|
||||
message: widget.message,
|
||||
showReactions: widget.showReactions,
|
||||
@@ -984,28 +1126,6 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
);
|
||||
}
|
||||
|
||||
ShapeBorder _getDefaultAttachmentShape(BuildContext context) {
|
||||
final hasFiles =
|
||||
widget.message.attachments.any((it) => it.type == 'file') == true;
|
||||
return RoundedRectangleBorder(
|
||||
side: hasFiles
|
||||
? widget.attachmentBorderSide ??
|
||||
BorderSide(
|
||||
color: _streamChatTheme.colorTheme.greyWhisper,
|
||||
)
|
||||
: BorderSide.none,
|
||||
borderRadius: widget.attachmentBorderRadiusGeometry ?? BorderRadius.zero,
|
||||
);
|
||||
}
|
||||
|
||||
ShapeBorder _getDefaultShape(BuildContext context) => RoundedRectangleBorder(
|
||||
side: widget.borderSide ??
|
||||
BorderSide(
|
||||
color: _streamChatTheme.colorTheme.greyWhisper,
|
||||
),
|
||||
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
|
||||
);
|
||||
|
||||
Widget _parseAttachments() {
|
||||
final attachmentGroups = <String, List<Attachment>>{};
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ void main() {
|
||||
id: 'user-id',
|
||||
),
|
||||
),
|
||||
messageWidget: const Text(
|
||||
'test',
|
||||
key: Key('MessageWidget'),
|
||||
),
|
||||
messageTheme: streamTheme.ownMessageTheme,
|
||||
),
|
||||
),
|
||||
@@ -86,6 +90,10 @@ void main() {
|
||||
),
|
||||
),
|
||||
messageTheme: streamTheme.ownMessageTheme,
|
||||
messageWidget: const Text(
|
||||
'test',
|
||||
key: Key('MessageWidget'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -123,6 +131,7 @@ void main() {
|
||||
client: client,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
@@ -178,6 +187,7 @@ void main() {
|
||||
client: client,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
onReplyTap: (m) {
|
||||
tapped = true;
|
||||
},
|
||||
@@ -223,6 +233,7 @@ void main() {
|
||||
client: client,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
onThreadReplyTap: (m) {
|
||||
tapped = true;
|
||||
},
|
||||
@@ -272,6 +283,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
text: 'test',
|
||||
user: User(
|
||||
@@ -320,6 +332,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
editMessageInputBuilder: (context, m) => const Text('test'),
|
||||
message: Message(
|
||||
text: 'test',
|
||||
@@ -371,6 +384,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
onCopyTap: (m) => tapped = true,
|
||||
message: Message(
|
||||
text: 'test',
|
||||
@@ -420,6 +434,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
status: MessageSendingStatus.failed,
|
||||
text: 'test',
|
||||
@@ -469,6 +484,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
status: MessageSendingStatus.failed_update,
|
||||
text: 'test',
|
||||
@@ -516,6 +532,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
id: 'testid',
|
||||
text: 'test',
|
||||
@@ -571,6 +588,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
id: 'testid',
|
||||
text: 'test',
|
||||
@@ -626,6 +644,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
id: 'testid',
|
||||
text: 'test',
|
||||
@@ -679,6 +698,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
id: 'testid',
|
||||
text: 'test',
|
||||
@@ -734,6 +754,7 @@ void main() {
|
||||
channel: channel,
|
||||
child: SizedBox(
|
||||
child: MessageActionsModal(
|
||||
messageWidget: const Text('test'),
|
||||
message: Message(
|
||||
id: 'testid',
|
||||
text: 'test',
|
||||
|
||||
@@ -34,6 +34,10 @@ void main() {
|
||||
client: client,
|
||||
streamChatThemeData: streamTheme,
|
||||
child: MessageReactionsModal(
|
||||
messageWidget: const Text(
|
||||
'test',
|
||||
key: Key('MessageWidget'),
|
||||
),
|
||||
message: message,
|
||||
messageTheme: streamTheme.ownMessageTheme,
|
||||
),
|
||||
@@ -43,28 +47,9 @@ void main() {
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 1000));
|
||||
|
||||
expect(find.byType(MessageWidget), findsOneWidget);
|
||||
final messageWidget =
|
||||
tester.widget<MessageWidget>(find.byType(MessageWidget));
|
||||
expect(messageWidget.message, message);
|
||||
expect(messageWidget.messageTheme, streamTheme.ownMessageTheme);
|
||||
expect(messageWidget.attachmentBorderRadiusGeometry, null);
|
||||
expect(messageWidget.showUserAvatar, DisplayWidget.show);
|
||||
expect(messageWidget.reverse, false);
|
||||
expect(messageWidget.attachmentShape, null);
|
||||
expect(messageWidget.shape, null);
|
||||
expect(messageWidget.onUserAvatarTap, null);
|
||||
expect(messageWidget.showReactions, false);
|
||||
expect(messageWidget.showUsername, false);
|
||||
expect(messageWidget.showThreadReplyIndicator, false);
|
||||
expect(messageWidget.showTimestamp, false);
|
||||
expect(messageWidget.translateUserAvatar, false);
|
||||
expect(messageWidget.showSendingIndicator, false);
|
||||
|
||||
expect(find.byType(ReactionBubble), findsNothing);
|
||||
|
||||
//only one avatar (the message one)
|
||||
expect(find.byType(UserAvatar), findsOneWidget);
|
||||
expect(find.byType(UserAvatar), findsNothing);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -105,14 +90,14 @@ void main() {
|
||||
client: client,
|
||||
streamChatThemeData: streamTheme,
|
||||
child: MessageReactionsModal(
|
||||
messageWidget: const Text(
|
||||
'test',
|
||||
key: Key('MessageWidget'),
|
||||
),
|
||||
message: message,
|
||||
messageTheme: streamTheme.ownMessageTheme,
|
||||
showUserAvatar: DisplayWidget.gone,
|
||||
reverse: true,
|
||||
attachmentBorderRadiusGeometry: BorderRadius.circular(1),
|
||||
attachmentShape: const RoundedRectangleBorder(),
|
||||
showReactions: false,
|
||||
messageShape: const RoundedRectangleBorder(),
|
||||
onUserAvatarTap: onUserAvatarTap,
|
||||
),
|
||||
),
|
||||
@@ -121,27 +106,7 @@ void main() {
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 1000));
|
||||
|
||||
expect(find.byType(MessageWidget), findsOneWidget);
|
||||
final messageWidget =
|
||||
tester.widget<MessageWidget>(find.byType(MessageWidget));
|
||||
expect(messageWidget.message, message);
|
||||
expect(messageWidget.messageTheme, streamTheme.ownMessageTheme);
|
||||
expect(messageWidget.attachmentBorderRadiusGeometry,
|
||||
BorderRadius.circular(1));
|
||||
expect(messageWidget.showUserAvatar, DisplayWidget.gone);
|
||||
expect(messageWidget.reverse, true);
|
||||
expect(messageWidget.showReactions, false);
|
||||
expect(messageWidget.attachmentShape, const RoundedRectangleBorder());
|
||||
expect(messageWidget.shape, const RoundedRectangleBorder());
|
||||
expect(messageWidget.showReactions, false);
|
||||
expect(messageWidget.showUsername, false);
|
||||
expect(messageWidget.showThreadReplyIndicator, false);
|
||||
expect(messageWidget.showTimestamp, false);
|
||||
expect(messageWidget.translateUserAvatar, false);
|
||||
expect(messageWidget.showSendingIndicator, false);
|
||||
|
||||
final userAvatar = tester.widget<UserAvatar>(find.byType(UserAvatar));
|
||||
expect(userAvatar.onTap, onUserAvatarTap);
|
||||
expect(find.byKey(const Key('MessageWidget')), findsOneWidget);
|
||||
|
||||
expect(find.byType(ReactionBubble), findsOneWidget);
|
||||
expect(find.byType(UserAvatar), findsOneWidget);
|
||||
|
||||
Reference in New Issue
Block a user