Merge remote-tracking branch 'origin/develop' into refactor/message-status

# Conflicts:
#	packages/stream_chat_flutter/lib/src/message_actions_modal/message_actions_modal.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget.dart
#	packages/stream_chat_flutter/lib/src/message_widget/message_widget_content.dart
#	packages/stream_chat_flutter/lib/src/message_widget/reactions/message_reactions_modal.dart
This commit is contained in:
xsahil03x
2023-07-12 17:59:52 +05:30
48 changed files with 1330 additions and 691 deletions
@@ -188,19 +188,16 @@ class _MessageCardState extends State<MessageCard> {
attachmentPadding: widget.attachmentPadding,
),
if (!widget.isGiphy)
ConstrainedBox(
constraints: BoxConstraints.loose(const Size.fromWidth(500)),
child: TextBubble(
messageTheme: widget.messageTheme,
message: widget.message,
textPadding: widget.textPadding,
textBuilder: widget.textBuilder,
isOnlyEmoji: widget.isOnlyEmoji,
hasQuotedMessage: widget.hasQuotedMessage,
hasUrlAttachments: widget.hasUrlAttachments,
onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap,
),
TextBubble(
messageTheme: widget.messageTheme,
message: widget.message,
textPadding: widget.textPadding,
textBuilder: widget.textBuilder,
isOnlyEmoji: widget.isOnlyEmoji,
hasQuotedMessage: widget.hasQuotedMessage,
hasUrlAttachments: widget.hasUrlAttachments,
onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap,
),
if (widget.hasUrlAttachments && !widget.hasQuotedMessage)
_buildUrlAttachment(),
@@ -2,6 +2,7 @@ import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/material.dart' hide ButtonStyle;
import 'package:flutter/services.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:meta/meta.dart';
import 'package:stream_chat_flutter/conditional_parent_builder/conditional_parent_builder.dart';
import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart';
import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction_picker.dart';
@@ -54,7 +55,10 @@ class StreamMessageWidget extends StatefulWidget {
this.attachmentBorderRadiusGeometry,
this.onMentionTap,
this.onMessageTap,
this.showReactionPickerIndicator = false,
bool? showReactionPicker,
@Deprecated('Use `showReactionPicker` instead')
bool showReactionPickerIndicator = true,
@internal this.showReactionPickerTail = false,
this.showUserAvatar = DisplayWidget.show,
this.showSendingIndicator = true,
this.showThreadReplyIndicator = false,
@@ -114,6 +118,7 @@ class StreamMessageWidget extends StatefulWidget {
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
),
showReactionPicker = showReactionPicker ?? showReactionPickerIndicator,
attachmentBuilders = {
'image': (context, message, attachments) {
final border = RoundedRectangleBorder(
@@ -460,10 +465,22 @@ class StreamMessageWidget extends StatefulWidget {
/// {@endtemplate}
final void Function(String)? onLinkTap;
/// {@template showReactionPicker}
/// Whether or not to show the reaction picker.
/// Used in [StreamMessageReactionsModal] and [MessageActionsModal].
/// {@endtemplate}
final bool showReactionPicker;
/// {@template showReactionPickerIndicator}
/// Used in [StreamMessageReactionsModal] and [MessageActionsModal]
/// {@endtemplate} @Deprecated('Use `showReactionPicker` instead')
bool get showReactionPickerIndicator => showReactionPicker;
/// {@template showReactionPickerTail}
/// Whether or not to show the reaction picker tail
/// {@endtemplate}
final bool showReactionPickerIndicator;
@internal
final bool showReactionPickerTail;
/// {@template onShowMessage}
/// Callback when show message is tapped
@@ -619,7 +636,10 @@ class StreamMessageWidget extends StatefulWidget {
bool? showInChannelIndicator,
void Function(User)? onUserAvatarTap,
void Function(String)? onLinkTap,
bool? showReactionPicker,
@Deprecated('Use `showReactionPicker` instead')
bool? showReactionPickerIndicator,
@internal bool? showReactionPickerTail,
List<Read>? readList,
ShowMessageCallback? onShowMessage,
bool? showUsername,
@@ -703,8 +723,11 @@ class StreamMessageWidget extends StatefulWidget {
showInChannelIndicator ?? this.showInChannelIndicator,
onUserAvatarTap: onUserAvatarTap ?? this.onUserAvatarTap,
onLinkTap: onLinkTap ?? this.onLinkTap,
showReactionPickerIndicator:
showReactionPickerIndicator ?? this.showReactionPickerIndicator,
showReactionPicker: showReactionPicker ??
showReactionPickerIndicator ??
this.showReactionPicker,
showReactionPickerTail:
showReactionPickerTail ?? this.showReactionPickerTail,
onShowMessage: onShowMessage ?? this.onShowMessage,
showUsername: showUsername ?? this.showUsername,
showTimestamp: showTimestamp ?? this.showTimestamp,
@@ -883,12 +906,12 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
if (!widget.message.state.isDeleted) {
return ContextMenuArea(
verticalPadding: 0,
builder: (context) => _buildContextMenu(),
builder: (_) => _buildContextMenu(),
child: child,
);
} else {
return child;
}
return child;
},
child: Material(
type: MaterialType.transparency,
@@ -961,9 +984,9 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
messageWidget: widget,
showBottomRow: showBottomRow,
showPinHighlight: widget.showPinHighlight,
showReactionPickerIndicator:
widget.showReactionPickerIndicator,
showReactionPickerTail: widget.showReactionPickerTail,
showReactions: showReactions,
onReactionsTap: () => _showMessageReactionsModal(context),
showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar,
@@ -994,14 +1017,15 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
final channel = StreamChannel.of(context).channel;
return [
StreamChatContextMenuItem(
child: StreamChannel(
channel: channel,
child: ContextMenuReactionPicker(
message: widget.message,
if (widget.showReactionPicker)
StreamChatContextMenuItem(
child: StreamChannel(
channel: channel,
child: ContextMenuReactionPicker(
message: widget.message,
),
),
),
),
if (shouldShowReplyAction) ...[
StreamChatContextMenuItem(
leading: StreamSvgIcon.reply(),
@@ -1145,20 +1169,7 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
];
}
void onLongPress(BuildContext context) {
if (widget.message.isEphemeral || widget.message.state.isOutgoing) {
return;
}
if (widget.onMessageActions != null) {
widget.onMessageActions!(context, widget.message);
} else {
_showMessageActionModalBottomSheet(context);
}
return;
}
void _showMessageActionModalBottomSheet(BuildContext context) {
void _showMessageReactionsModal(BuildContext context) {
final channel = StreamChannel.of(context).channel;
showDialog(
@@ -1168,7 +1179,8 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
barrierColor: _streamChatTheme.colorTheme.overlay,
builder: (context) => StreamChannel(
channel: channel,
child: MessageActionsModal(
child: StreamMessageReactionsModal(
showReactionPicker: widget.showReactionPicker,
messageWidget: widget.copyWith(
key: const Key('MessageWidget'),
message: widget.message.copyWith(
@@ -1182,37 +1194,92 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
translateUserAvatar: false,
showSendingIndicator: false,
padding: EdgeInsets.zero,
showReactionPickerIndicator:
widget.showReactions && widget.message.state.isCompleted,
// Show the tail if the reaction picker is visible.
showReactionPickerTail: widget.showReactionPicker,
showPinHighlight: false,
showUserAvatar:
widget.message.user!.id == channel.client.state.currentUser!.id
? DisplayWidget.gone
: DisplayWidget.show,
),
onCopyTap: (message) {
final text = message.text;
if (text != null) Clipboard.setData(ClipboardData(text: text));
},
onUserAvatarTap: widget.onUserAvatarTap,
messageTheme: widget.messageTheme,
reverse: widget.reverse,
showDeleteMessage: shouldShowDeleteAction,
onConfirmDeleteTap: widget.onConfirmDeleteTap,
message: widget.message,
editMessageInputBuilder: widget.editMessageInputBuilder,
onReplyTap: widget.onReplyTap,
onThreadReplyTap: widget.onThreadTap,
showResendMessage: shouldShowResendAction,
showCopyMessage: shouldShowCopyAction,
showEditMessage: shouldShowEditAction,
showReactions: widget.showReactions,
showReplyMessage: shouldShowReplyAction,
showThreadReplyMessage: shouldShowThreadReplyAction,
showFlagButton: widget.showFlagButton,
showPinButton: widget.showPinButton,
customActions: widget.customActions,
),
),
);
}
void onLongPress(BuildContext context) {
if (widget.message.isEphemeral || widget.message.state.isOutgoing) {
return;
}
if (widget.onMessageActions != null) {
return widget.onMessageActions!(context, widget.message);
}
return _showMessageActionModalBottomSheet(context);
}
void _showMessageActionModalBottomSheet(BuildContext context) {
final channel = StreamChannel.of(context).channel;
showDialog(
useRootNavigator: false,
context: context,
useSafeArea: false,
barrierColor: _streamChatTheme.colorTheme.overlay,
builder: (context) {
return StreamChannel(
channel: channel,
child: MessageActionsModal(
messageWidget: widget.copyWith(
key: const Key('MessageWidget'),
message: widget.message.copyWith(
text: (widget.message.text?.length ?? 0) > 200
? '${widget.message.text!.substring(0, 200)}...'
: widget.message.text,
),
showReactions: false,
showUsername: false,
showTimestamp: false,
translateUserAvatar: false,
showSendingIndicator: false,
padding: EdgeInsets.zero,
// Show both the tail and indicator if the indicator is shown.
showReactionPickerTail: widget.showReactionPickerIndicator,
showPinHighlight: false,
showUserAvatar: widget.message.user!.id ==
channel.client.state.currentUser!.id
? DisplayWidget.gone
: DisplayWidget.show,
),
onCopyTap: (message) {
final text = message.text;
if (text != null) Clipboard.setData(ClipboardData(text: text));
},
messageTheme: widget.messageTheme,
reverse: widget.reverse,
showDeleteMessage: shouldShowDeleteAction,
onConfirmDeleteTap: widget.onConfirmDeleteTap,
message: widget.message,
editMessageInputBuilder: widget.editMessageInputBuilder,
onReplyTap: widget.onReplyTap,
onThreadReplyTap: widget.onThreadTap,
showResendMessage: shouldShowResendAction,
showCopyMessage: shouldShowCopyAction,
showEditMessage: shouldShowEditAction,
showReactionPicker: widget.showReactionPickerIndicator,
showReplyMessage: shouldShowReplyAction,
showThreadReplyMessage: shouldShowThreadReplyAction,
showFlagButton: widget.showFlagButton,
showPinButton: widget.showPinButton,
customActions: widget.customActions,
),
);
},
);
}
}
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:meta/meta.dart';
import 'package:stream_chat_flutter/src/message_widget/message_widget_content_components.dart';
import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -21,6 +22,7 @@ typedef BottomRowBuilderWithDefaultWidget = Widget Function(
///
/// Should not be used outside of [MessageWidget.
/// {@endtemplate}
@internal
class MessageWidgetContent extends StatelessWidget {
/// {@macro messageWidgetContent}
const MessageWidgetContent({
@@ -33,6 +35,7 @@ class MessageWidgetContent extends StatelessWidget {
required this.showUserAvatar,
required this.avatarWidth,
required this.showReactions,
required this.onReactionsTap,
required this.messageTheme,
required this.shouldShowReactions,
required this.streamChatTheme,
@@ -45,7 +48,7 @@ class MessageWidgetContent extends StatelessWidget {
required this.attachmentBuilders,
required this.attachmentPadding,
required this.textPadding,
required this.showReactionPickerIndicator,
required this.showReactionPickerTail,
required this.translateUserAvatar,
required this.bottomRowPadding,
required this.showInChannel,
@@ -111,6 +114,11 @@ class MessageWidgetContent extends StatelessWidget {
/// {@macro showReactions}
final bool showReactions;
/// Callback called when the reactions icon is tapped.
///
/// Do not confuse this with the tap action on the reactions picker.
final VoidCallback onReactionsTap;
/// {@macro messageTheme}
final StreamMessageThemeData messageTheme;
@@ -174,8 +182,8 @@ class MessageWidgetContent extends StatelessWidget {
/// {@macro quotedMessageBuilder}
final Widget Function(BuildContext, Message)? quotedMessageBuilder;
/// {@macro showReactionPickerIndicator}
final bool showReactionPickerIndicator;
/// {@macro showReactionPickerTail}
final bool showReactionPickerTail;
/// {@macro translateUserAvatar}
final bool translateUserAvatar;
@@ -288,9 +296,7 @@ class MessageWidgetContent extends StatelessWidget {
ownId: streamChat.currentUser!.id,
reverse: reverse,
shouldShowReactions: shouldShowReactions,
onTap: () => _showMessageReactionsModal(
context,
),
onTap: onReactionsTap,
)
: null,
anchor: Aligned(
@@ -363,7 +369,8 @@ class MessageWidgetContent extends StatelessWidget {
shape: shape,
),
),
if (showReactionPickerIndicator)
// TODO: Make tail part of the Reaction Picker.
if (showReactionPickerTail)
Positioned(
right: reverse ? null : 4,
left: reverse ? 4 : null,
@@ -374,6 +381,7 @@ class MessageWidgetContent extends StatelessWidget {
Colors.transparent,
Colors.transparent,
tailCirclesSpace: 1,
flipTail: !reverse,
),
),
),
@@ -434,47 +442,6 @@ class MessageWidgetContent extends StatelessWidget {
);
}
void _showMessageReactionsModal(BuildContext context) {
final channel = StreamChannel.of(context).channel;
showDialog(
useRootNavigator: false,
context: context,
useSafeArea: false,
barrierColor: streamChatTheme.colorTheme.overlay,
builder: (context) => StreamChannel(
channel: channel,
child: StreamMessageReactionsModal(
messageWidget: messageWidget.copyWith(
key: const Key('MessageWidget'),
message: message.copyWith(
text: (message.text?.length ?? 0) > 200
? '${message.text!.substring(0, 200)}...'
: message.text,
),
showReactions: false,
showUsername: false,
showTimestamp: false,
translateUserAvatar: false,
showSendingIndicator: false,
padding: EdgeInsets.zero,
showReactionPickerIndicator:
showReactions && message.state.isCompleted,
showPinHighlight: false,
showUserAvatar:
message.user!.id == channel.client.state.currentUser!.id
? DisplayWidget.gone
: DisplayWidget.show,
),
onUserAvatarTap: onUserAvatarTap,
messageTheme: messageTheme,
reverse: reverse,
message: message,
showReactions: showReactions,
),
),
);
}
Widget _buildBottomRow(BuildContext context) {
final defaultWidget = BottomRow(
message: message,
@@ -15,7 +15,7 @@ class StreamMessageReactionsModal extends StatelessWidget {
required this.message,
required this.messageWidget,
required this.messageTheme,
this.showReactions,
this.showReactionPicker = true,
this.reverse = false,
this.onUserAvatarTap,
});
@@ -32,8 +32,8 @@ class StreamMessageReactionsModal extends StatelessWidget {
/// {@macro reverse}
final bool reverse;
/// {@macro showReactions}
final bool? showReactions;
/// Flag for showing reaction picker.
final bool showReactionPicker;
/// {@macro onUserAvatarTap}
final void Function(User)? onUserAvatarTap;
@@ -56,8 +56,7 @@ class StreamMessageReactionsModal extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
if ((showReactions ?? hasReactionPermission) &&
message.state.isCompleted)
if (showReactionPicker && hasReactionPermission)
LayoutBuilder(
builder: (context, constraints) {
return Align(
@@ -43,19 +43,9 @@ double calculateReactionsHorizontalAlignment(
final signal = user?.id == message.user?.id ? 1 : -1;
final result = signal * (1 - divFactor * 2.0);
return _capResult(result);
}
// Ensure reactions don't get pushed past the edge of the screen.
//
// This happens if divFactor is really big. When this happens, we can simply
// move the model all the way to the end of screen.
double _capResult(double result) {
if (result > 1.0) {
return 1;
} else if (result < -1.0) {
return -1;
} else {
return result;
}
// Ensure reactions don't get pushed past the edge of the screen.
//
// This happens if divFactor is really big. When this happens, we can simply
// move the model all the way to the end of screen.
return result.clamp(-1, 1);
}