diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 11eed013d..e925b4311 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,5 +1,8 @@ ## Upcomming +✅ Added +- Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains a third parameter (default `BottomRow` widget with `copyWith` method available) to allow easier customization. + 🔄 Changed - Updated `lottie` dependency to `^2.0.0` @@ -7,6 +10,9 @@ - Updated `connectivity_plus` dependency to `^3.0.2` - Updated `dart_vlc` dependency to `^0.4.0` - Updated `file_picker` dependency to `^5.2.4` +- Deprecated `StreamMessageWidget.bottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. +- Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. +- Deprecated `StreamMessageWidget.usernameBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. 🐞 Fixed - [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading. diff --git a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart index a77b6de2e..5b1b14663 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/bottom_row.dart @@ -32,6 +32,7 @@ class BottomRow extends StatelessWidget { this.deletedBottomRowBuilder, this.onThreadTap, this.usernameBuilder, + this.sendingIndicatorBuilder, }); /// {@macro messageIsDeleted} @@ -88,6 +89,61 @@ class BottomRow extends StatelessWidget { /// {@macro usernameBuilder} final Widget Function(BuildContext, Message)? usernameBuilder; + /// {@macro sendingIndicatorBuilder} + final Widget Function(BuildContext, Message)? sendingIndicatorBuilder; + + /// {@template copyWith} + /// Creates a copy of [BottomRow] with specified attributes + /// overridden. + /// {@endtemplate} + BottomRow copyWith({ + Key? key, + bool? isDeleted, + Message? message, + bool? showThreadReplyIndicator, + bool? showInChannel, + bool? showTimeStamp, + bool? showUsername, + bool? reverse, + bool? showSendingIndicator, + bool? hasUrlAttachments, + bool? isGiphy, + bool? isOnlyEmoji, + StreamMessageThemeData? messageTheme, + StreamChatThemeData? streamChatTheme, + bool? hasNonUrlAttachments, + StreamChatState? streamChat, + Widget Function(BuildContext, Message)? deletedBottomRowBuilder, + void Function(Message)? onThreadTap, + Widget Function(BuildContext, Message)? usernameBuilder, + Widget Function(BuildContext, Message)? sendingIndicatorBuilder, + }) => + BottomRow( + key: key ?? this.key, + isDeleted: isDeleted ?? this.isDeleted, + message: message ?? this.message, + showThreadReplyIndicator: + showThreadReplyIndicator ?? this.showThreadReplyIndicator, + showInChannel: showInChannel ?? this.showInChannel, + showTimeStamp: showTimeStamp ?? this.showTimeStamp, + showUsername: showUsername ?? this.showUsername, + reverse: reverse ?? this.reverse, + showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator, + hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments, + isGiphy: isGiphy ?? this.isGiphy, + isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji, + messageTheme: messageTheme ?? this.messageTheme, + streamChatTheme: streamChatTheme ?? this.streamChatTheme, + hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments, + streamChat: streamChat ?? this.streamChat, + deletedBottomRowBuilder: + deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, + onThreadTap: onThreadTap ?? this.onThreadTap, + usernameBuilder: usernameBuilder ?? this.usernameBuilder, + sendingIndicatorBuilder: + sendingIndicatorBuilder ?? this.sendingIndicatorBuilder, + ); + @override Widget build(BuildContext context) { if (isDeleted) { @@ -147,13 +203,14 @@ class BottomRow extends StatelessWidget { ), if (showSendingIndicator) WidgetSpan( - child: SendingIndicatorWrapper( - messageTheme: messageTheme, - message: message, - hasNonUrlAttachments: hasNonUrlAttachments, - streamChat: streamChat, - streamChatTheme: streamChatTheme, - ), + child: sendingIndicatorBuilder?.call(context, message) ?? + SendingIndicatorWrapper( + messageTheme: messageTheme, + message: message, + hasNonUrlAttachments: hasNonUrlAttachments, + streamChat: streamChat, + streamChatTheme: streamChatTheme, + ), ), ]); diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart index d2675a0a6..0975320f6 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart @@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction import 'package:stream_chat_flutter/src/context_menu_items/stream_chat_context_menu_item.dart'; import 'package:stream_chat_flutter/src/dialogs/dialogs.dart'; import 'package:stream_chat_flutter/src/message_actions_modal/message_actions_modal.dart'; +import 'package:stream_chat_flutter/src/message_widget/bottom_row.dart'; import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart'; import 'package:stream_chat_flutter/src/message_widget/reactions/message_reactions_modal.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -79,8 +80,15 @@ class StreamMessageWidget extends StatefulWidget { this.userAvatarBuilder, this.editMessageInputBuilder, this.textBuilder, - this.bottomRowBuilder, - this.deletedBottomRowBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.bottomRowBuilder, + this.bottomRowBuilderWithDefaultWidget, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.deletedBottomRowBuilder, this.customAttachmentBuilders, this.padding, this.textPadding = const EdgeInsets.symmetric( @@ -92,11 +100,18 @@ class StreamMessageWidget extends StatefulWidget { this.onQuotedMessageTap, this.customActions = const [], this.onAttachmentTap, - this.usernameBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.usernameBuilder, this.imageAttachmentThumbnailSize = const Size(400, 400), this.imageAttachmentThumbnailResizeType = 'clip', this.imageAttachmentThumbnailCropType = 'center', - }) : attachmentBuilders = { + }) : assert( + bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, + 'You can only use one of the two bottom row builders', + ), + attachmentBuilders = { 'image': (context, message, attachments) { final border = RoundedRectangleBorder( side: attachmentBorderSide ?? @@ -306,7 +321,13 @@ class StreamMessageWidget extends StatefulWidget { /// {@template bottomRowBuilder} /// Widget builder for building a bottom row below the message /// {@endtemplate} - final Widget Function(BuildContext, Message)? bottomRowBuilder; + final BottomRowBuilder? bottomRowBuilder; + + /// {@template bottomRowBuilderWithDefaultWidget} + /// Widget builder for building a bottom row below the message. + /// Also contains the default bottom row widget. + /// {@endtemplate} + final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; /// {@template deletedBottomRowBuilder} /// Widget builder for building a bottom row below a deleted message @@ -537,9 +558,19 @@ class StreamMessageWidget extends StatefulWidget { void Function(Message)? onReplyTap, Widget Function(BuildContext, Message)? editMessageInputBuilder, Widget Function(BuildContext, Message)? textBuilder, - Widget Function(BuildContext, Message)? usernameBuilder, - Widget Function(BuildContext, Message)? bottomRowBuilder, - Widget Function(BuildContext, Message)? deletedBottomRowBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') Widget Function(BuildContext, Message)? usernameBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') BottomRowBuilder? bottomRowBuilder, + BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') Widget Function(BuildContext, Message)? deletedBottomRowBuilder, void Function(BuildContext, Message)? onMessageActions, Message? message, StreamMessageThemeData? messageTheme, @@ -587,6 +618,29 @@ class StreamMessageWidget extends StatefulWidget { String? imageAttachmentThumbnailResizeType, String? imageAttachmentThumbnailCropType, }) { + assert( + bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, + 'You can only use one of the two bottom row builders', + ); + + var _bottomRowBuilderWithDefaultWidget = + bottomRowBuilderWithDefaultWidget ?? + this.bottomRowBuilderWithDefaultWidget; + + _bottomRowBuilderWithDefaultWidget ??= (context, message, defaultWidget) { + final _bottomRowBuilder = bottomRowBuilder ?? this.bottomRowBuilder; + if (_bottomRowBuilder != null) { + return _bottomRowBuilder(context, message); + } + + return defaultWidget.copyWith( + onThreadTap: onThreadTap ?? this.onThreadTap, + usernameBuilder: usernameBuilder ?? this.usernameBuilder, + deletedBottomRowBuilder: + deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, + ); + }; + return StreamMessageWidget( key: key ?? this.key, onMentionTap: onMentionTap ?? this.onMentionTap, @@ -595,10 +649,7 @@ class StreamMessageWidget extends StatefulWidget { editMessageInputBuilder: editMessageInputBuilder ?? this.editMessageInputBuilder, textBuilder: textBuilder ?? this.textBuilder, - usernameBuilder: usernameBuilder ?? this.usernameBuilder, - bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder, - deletedBottomRowBuilder: - deletedBottomRowBuilder ?? this.deletedBottomRowBuilder, + bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget, onMessageActions: onMessageActions ?? this.onMessageActions, message: message ?? this.message, messageTheme: messageTheme ?? this.messageTheme, @@ -838,52 +889,69 @@ class _StreamMessageWidgetState extends State ? Alignment.centerRight : Alignment.centerLeft, widthFactor: widget.widthFactor, - child: MessageWidgetContent( - streamChatTheme: _streamChatTheme, - showUsername: showUsername, - showTimeStamp: showTimeStamp, - showThreadReplyIndicator: showThreadReplyIndicator, - showSendingIndicator: showSendingIndicator, - showInChannel: showInChannel, - isGiphy: isGiphy, - isOnlyEmoji: isOnlyEmoji, - hasUrlAttachments: hasUrlAttachments, - messageTheme: widget.messageTheme, - reverse: widget.reverse, - message: widget.message, - hasNonUrlAttachments: hasNonUrlAttachments, - shouldShowReactions: shouldShowReactions, - hasQuotedMessage: hasQuotedMessage, - textPadding: widget.textPadding, - attachmentBuilders: widget.attachmentBuilders, - attachmentPadding: widget.attachmentPadding, - avatarWidth: avatarWidth, - bottomRowPadding: bottomRowPadding, - isFailedState: isFailedState, - isPinned: isPinned, - messageWidget: widget, - showBottomRow: showBottomRow, - showPinHighlight: widget.showPinHighlight, - showReactionPickerIndicator: - widget.showReactionPickerIndicator, - showReactions: showReactions, - showUserAvatar: widget.showUserAvatar, - streamChat: _streamChat, - translateUserAvatar: widget.translateUserAvatar, - deletedBottomRowBuilder: widget.deletedBottomRowBuilder, - onThreadTap: widget.onThreadTap, - shape: widget.shape, - borderSide: widget.borderSide, - borderRadiusGeometry: widget.borderRadiusGeometry, - textBuilder: widget.textBuilder, - onLinkTap: widget.onLinkTap, - onMentionTap: widget.onMentionTap, - onQuotedMessageTap: widget.onQuotedMessageTap, - bottomRowBuilder: widget.bottomRowBuilder, - onUserAvatarTap: widget.onUserAvatarTap, - userAvatarBuilder: widget.userAvatarBuilder, - usernameBuilder: widget.usernameBuilder, - ), + child: Builder(builder: (context) { + var _bottomRowBuilderWithDefaultWidget = + widget.bottomRowBuilderWithDefaultWidget; + + _bottomRowBuilderWithDefaultWidget ??= + (context, message, defaultWidget) { + final _bottomRowBuilder = widget.bottomRowBuilder; + if (_bottomRowBuilder != null) { + return _bottomRowBuilder(context, message); + } + + return defaultWidget.copyWith( + onThreadTap: widget.onThreadTap, + usernameBuilder: widget.usernameBuilder, + deletedBottomRowBuilder: widget.deletedBottomRowBuilder, + ); + }; + + return MessageWidgetContent( + streamChatTheme: _streamChatTheme, + showUsername: showUsername, + showTimeStamp: showTimeStamp, + showThreadReplyIndicator: showThreadReplyIndicator, + showSendingIndicator: showSendingIndicator, + showInChannel: showInChannel, + isGiphy: isGiphy, + isOnlyEmoji: isOnlyEmoji, + hasUrlAttachments: hasUrlAttachments, + messageTheme: widget.messageTheme, + reverse: widget.reverse, + message: widget.message, + hasNonUrlAttachments: hasNonUrlAttachments, + shouldShowReactions: shouldShowReactions, + hasQuotedMessage: hasQuotedMessage, + textPadding: widget.textPadding, + attachmentBuilders: widget.attachmentBuilders, + attachmentPadding: widget.attachmentPadding, + avatarWidth: avatarWidth, + bottomRowPadding: bottomRowPadding, + isFailedState: isFailedState, + isPinned: isPinned, + messageWidget: widget, + showBottomRow: showBottomRow, + showPinHighlight: widget.showPinHighlight, + showReactionPickerIndicator: + widget.showReactionPickerIndicator, + showReactions: showReactions, + showUserAvatar: widget.showUserAvatar, + streamChat: _streamChat, + translateUserAvatar: widget.translateUserAvatar, + shape: widget.shape, + borderSide: widget.borderSide, + borderRadiusGeometry: widget.borderRadiusGeometry, + textBuilder: widget.textBuilder, + onLinkTap: widget.onLinkTap, + onMentionTap: widget.onMentionTap, + onQuotedMessageTap: widget.onQuotedMessageTap, + bottomRowBuilderWithDefaultWidget: + _bottomRowBuilderWithDefaultWidget, + onUserAvatarTap: widget.onUserAvatarTap, + userAvatarBuilder: widget.userAvatarBuilder, + ); + }), ), ), ), diff --git a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart index d209fbee2..d02a43646 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart @@ -4,6 +4,18 @@ import 'package:stream_chat_flutter/src/message_widget/message_widget_content_co import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Signature for the builder function that will be called when the message +/// bottom row is built. Includes the [Message]. +typedef BottomRowBuilder = Widget Function(BuildContext, Message); + +/// Signature for the builder function that will be called when the message +/// bottom row is built. Includes the [Message] and the default [BottomRow]. +typedef BottomRowBuilderWithDefaultWidget = Widget Function( + BuildContext, + Message, + BottomRow, +); + /// {@template messageWidgetContent} /// The main content of a [StreamMessageWidget]. /// @@ -51,12 +63,28 @@ class MessageWidgetContent extends StatelessWidget { this.onMentionTap, this.onLinkTap, this.textBuilder, - this.bottomRowBuilder, - this.onThreadTap, - this.deletedBottomRowBuilder, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.bottomRowBuilder, + this.bottomRowBuilderWithDefaultWidget, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.onThreadTap, + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.deletedBottomRowBuilder, this.userAvatarBuilder, - this.usernameBuilder, - }); + @Deprecated(''' + Use [bottomRowBuilderWithDefaultWidget] instead. + Will be removed in the next major version. + ''') this.usernameBuilder, + }) : assert( + bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null, + 'You can only use one of the two bottom row builders', + ); /// {@macro reverse} final bool reverse; @@ -152,7 +180,10 @@ class MessageWidgetContent extends StatelessWidget { final double bottomRowPadding; /// {@macro bottomRowBuilder} - final Widget Function(BuildContext, Message)? bottomRowBuilder; + final BottomRowBuilder? bottomRowBuilder; + + /// {@macro bottomRowBuilderWithDefaultWidget} + final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget; /// {@macro showInChannelIndicator} final bool showInChannel; @@ -207,30 +238,7 @@ class MessageWidgetContent extends StatelessWidget { right: reverse ? bottomRowPadding : 0, bottom: isPinned && showPinHighlight ? 6.0 : 0.0, ), - child: bottomRowBuilder?.call( - context, - message, - ) ?? - BottomRow( - message: message, - reverse: reverse, - messageTheme: messageTheme, - hasUrlAttachments: hasUrlAttachments, - isOnlyEmoji: isOnlyEmoji, - isDeleted: message.isDeleted, - isGiphy: isGiphy, - showInChannel: showInChannel, - showSendingIndicator: showSendingIndicator, - showThreadReplyIndicator: showThreadReplyIndicator, - showTimeStamp: showTimeStamp, - showUsername: showUsername, - streamChatTheme: streamChatTheme, - onThreadTap: onThreadTap, - deletedBottomRowBuilder: deletedBottomRowBuilder, - streamChat: streamChat, - hasNonUrlAttachments: hasNonUrlAttachments, - usernameBuilder: usernameBuilder, - ), + child: _buildBottomRow(context), ), Padding( padding: EdgeInsets.only( @@ -457,4 +465,39 @@ class MessageWidgetContent extends StatelessWidget { ), ); } + + Widget _buildBottomRow(BuildContext context) { + final defaultWidget = BottomRow( + message: message, + reverse: reverse, + messageTheme: messageTheme, + hasUrlAttachments: hasUrlAttachments, + isOnlyEmoji: isOnlyEmoji, + isDeleted: message.isDeleted, + isGiphy: isGiphy, + showInChannel: showInChannel, + showSendingIndicator: showSendingIndicator, + showThreadReplyIndicator: showThreadReplyIndicator, + showTimeStamp: showTimeStamp, + showUsername: showUsername, + streamChatTheme: streamChatTheme, + onThreadTap: onThreadTap, + deletedBottomRowBuilder: deletedBottomRowBuilder, + streamChat: streamChat, + hasNonUrlAttachments: hasNonUrlAttachments, + usernameBuilder: usernameBuilder, + ); + + if (bottomRowBuilder != null) { + return bottomRowBuilder!(context, message); + } else if (bottomRowBuilderWithDefaultWidget != null) { + return bottomRowBuilderWithDefaultWidget!( + context, + message, + defaultWidget, + ); + } + + return defaultWidget; + } }