Merge pull request #1611 from GetStream/feat/quoted-message-builder

This commit is contained in:
Sahil Kumar
2023-06-16 02:55:24 +05:30
committed by GitHub
7 changed files with 122 additions and 74 deletions
+16
View File
@@ -35,6 +35,22 @@
) )
``` ```
- Added support for `StreamMessageWidget.quotedMessageBuilder` and `StreamMessageInput.quotedMessageBuilder` to override
the default quoted message widget. [#1547](https://github.com/GetStream/stream-chat-flutter/issues/1547)
```dart
StreamMessageWidget(
...,
quotedMessageBuilder: (context, message) {
return Container(
color: Colors.red,
child: Text('Quoted Message'),
);
},
)
```
## 6.3.0 ## 6.3.0
🐞 Fixed 🐞 Fixed
@@ -20,7 +20,6 @@ class StreamQuotedMessageWidget extends StatelessWidget {
this.textLimit = 170, this.textLimit = 170,
this.attachmentThumbnailBuilders, this.attachmentThumbnailBuilders,
this.padding = const EdgeInsets.all(8), this.padding = const EdgeInsets.all(8),
this.onTap,
this.onQuotedMessageClear, this.onQuotedMessageClear,
}); });
@@ -46,9 +45,6 @@ class StreamQuotedMessageWidget extends StatelessWidget {
/// Padding around the widget /// Padding around the widget
final EdgeInsetsGeometry padding; final EdgeInsetsGeometry padding;
/// Callback for tap on widget
final GestureTapCallback? onTap;
/// Callback for clearing quoted messages. /// Callback for clearing quoted messages.
final VoidCallback? onQuotedMessageClear; final VoidCallback? onQuotedMessageClear;
@@ -77,19 +73,12 @@ class StreamQuotedMessageWidget extends StatelessWidget {
showOnlineStatus: false, showOnlineStatus: false,
), ),
]; ];
return MouseRegion( return Padding(
cursor: SystemMouseCursors.click, padding: padding,
child: GestureDetector( child: Row(
behavior: HitTestBehavior.opaque, crossAxisAlignment: CrossAxisAlignment.end,
onTap: onTap, mainAxisSize: MainAxisSize.min,
child: Padding( children: reverse ? children.reversed.toList() : children,
padding: padding,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: reverse ? children.reversed.toList() : children,
),
),
), ),
); );
} }
@@ -258,23 +247,26 @@ class _ParseAttachments extends StatelessWidget {
child = attachmentBuilder(context, attachment); child = attachmentBuilder(context, attachment);
} }
} }
child = AbsorbPointer(child: child);
final isImageFile = attachment.title?.mimeType?.type == 'image';
final isVideoFile = attachment.title?.mimeType?.type == 'video';
return Material( return Material(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
type: MaterialType.transparency, type: MaterialType.transparency,
shape: attachment.type == 'file' shape: attachment.type == 'file' && (!isImageFile && !isVideoFile)
? null ? null
: RoundedRectangleBorder( : RoundedRectangleBorder(
side: const BorderSide(width: 0, color: Colors.transparent), side: const BorderSide(width: 0, color: Colors.transparent),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: child, child: AbsorbPointer(child: child),
); );
} }
Map<String, QuotedMessageAttachmentThumbnailBuilder> Map<String, QuotedMessageAttachmentThumbnailBuilder>
get _defaultAttachmentBuilder { get _defaultAttachmentBuilder {
return { final builders = <String, QuotedMessageAttachmentThumbnailBuilder>{
'image': (_, attachment) { 'image': (_, attachment) {
return StreamImageAttachment( return StreamImageAttachment(
attachment: attachment, attachment: attachment,
@@ -315,16 +307,33 @@ class _ParseAttachments extends StatelessWidget {
fit: BoxFit.cover, fit: BoxFit.cover,
); );
}, },
'file': (_, attachment) {
return SizedBox(
height: 32,
width: 32,
child: getFileTypeImage(
attachment.extraData['mime_type'] as String?,
),
);
},
}; };
builders['file'] = (_, attachment) {
return SizedBox(
height: 32,
width: 32,
child: Builder(
builder: (context) {
final isImageFile = attachment.title?.mimeType?.type == 'image';
if (isImageFile) {
return builders['image']!(context, attachment);
}
final isVideoFile = attachment.title?.mimeType?.type == 'video';
if (isVideoFile) {
return builders['video']!(context, attachment);
}
return getFileTypeImage(
attachment.extraData['mime_type'] as String?,
);
},
),
);
};
return builders;
} }
} }
@@ -129,6 +129,7 @@ class StreamMessageInput extends StatefulWidget {
this.customAutocompleteTriggers = const [], this.customAutocompleteTriggers = const [],
this.mentionAllAppUsers = false, this.mentionAllAppUsers = false,
this.sendButtonBuilder, this.sendButtonBuilder,
this.quotedMessageBuilder,
this.shouldKeepFocusAfterMessage, this.shouldKeepFocusAfterMessage,
this.validator = _defaultValidator, this.validator = _defaultValidator,
this.restorationId, this.restorationId,
@@ -265,6 +266,9 @@ class StreamMessageInput extends StatefulWidget {
/// Builder for creating send button /// Builder for creating send button
final MessageRelatedBuilder? sendButtonBuilder; final MessageRelatedBuilder? sendButtonBuilder;
/// Builder for building quoted message
final Widget Function(BuildContext, Message)? quotedMessageBuilder;
/// Defines if the [StreamMessageInput] loses focuses after a message is sent. /// Defines if the [StreamMessageInput] loses focuses after a message is sent.
/// The default behaviour keeps focus until a command is enabled. /// The default behaviour keeps focus until a command is enabled.
final bool? shouldKeepFocusAfterMessage; final bool? shouldKeepFocusAfterMessage;
@@ -1126,14 +1130,20 @@ class StreamMessageInputState extends State<StreamMessageInput>
if (!_hasQuotedMessage) return const Offstage(); if (!_hasQuotedMessage) return const Offstage();
final containsUrl = _effectiveController.message.quotedMessage!.attachments final containsUrl = _effectiveController.message.quotedMessage!.attachments
.any((element) => element.titleLink != null); .any((element) => element.titleLink != null);
return StreamQuotedMessageWidget(
reverse: true, return widget.quotedMessageBuilder?.call(
showBorder: !containsUrl, context,
message: _effectiveController.message.quotedMessage!, _effectiveController.message.quotedMessage!,
messageTheme: _streamChatTheme.otherMessageTheme, ) ??
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), StreamQuotedMessageWidget(
onQuotedMessageClear: widget.onQuotedMessageCleared, reverse: true,
); showBorder: !containsUrl,
message: _effectiveController.message.quotedMessage!,
messageTheme: _streamChatTheme.otherMessageTheme,
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
onQuotedMessageClear: widget.onQuotedMessageCleared,
attachmentThumbnailBuilders: widget.attachmentThumbnailBuilders,
);
} }
Widget _buildAttachments() { Widget _buildAttachments() {
@@ -29,6 +29,7 @@ class MessageCard extends StatefulWidget {
this.borderSide, this.borderSide,
this.borderRadiusGeometry, this.borderRadiusGeometry,
this.textBuilder, this.textBuilder,
this.quotedMessageBuilder,
this.onLinkTap, this.onLinkTap,
this.onMentionTap, this.onMentionTap,
this.onQuotedMessageTap, this.onQuotedMessageTap,
@@ -82,6 +83,9 @@ class MessageCard extends StatefulWidget {
/// {@macro textBuilder} /// {@macro textBuilder}
final Widget Function(BuildContext, Message)? textBuilder; final Widget Function(BuildContext, Message)? textBuilder;
/// {@macro quotedMessageBuilder}
final Widget Function(BuildContext, Message)? quotedMessageBuilder;
/// {@macro onLinkTap} /// {@macro onLinkTap}
final void Function(String)? onLinkTap; final void Function(String)? onLinkTap;
@@ -129,8 +133,12 @@ class _MessageCardState extends State<MessageCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final onQuotedMessageTap = widget.onQuotedMessageTap;
final quotedMessageBuilder = widget.quotedMessageBuilder;
return Card( return Card(
elevation: 0, elevation: 0,
clipBehavior: Clip.hardEdge,
margin: EdgeInsets.symmetric( margin: EdgeInsets.symmetric(
horizontal: (widget.isFailedState ? 15.0 : 0.0) + horizontal: (widget.isFailedState ? 15.0 : 0.0) +
(widget.showUserAvatar == DisplayWidget.gone ? 0 : 4.0), (widget.showUserAvatar == DisplayWidget.gone ? 0 : 4.0),
@@ -150,15 +158,27 @@ class _MessageCardState extends State<MessageCard> {
maxWidth: widthLimit ?? double.infinity, maxWidth: widthLimit ?? double.infinity,
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (widget.hasQuotedMessage) if (widget.hasQuotedMessage)
QuotedMessage( MouseRegion(
reverse: widget.reverse, cursor: SystemMouseCursors.click,
message: widget.message, child: InkWell(
hasNonUrlAttachments: widget.hasNonUrlAttachments, onTap: !widget.message.quotedMessage!.isDeleted &&
onQuotedMessageTap: widget.onQuotedMessageTap, onQuotedMessageTap != null
? () => onQuotedMessageTap(widget.message.quotedMessageId)
: null,
child: quotedMessageBuilder?.call(
context,
widget.message.quotedMessage!,
) ??
QuotedMessage(
reverse: widget.reverse,
message: widget.message,
hasNonUrlAttachments: widget.hasNonUrlAttachments,
),
),
), ),
if (widget.hasNonUrlAttachments) if (widget.hasNonUrlAttachments)
ParseAttachments( ParseAttachments(
@@ -79,6 +79,7 @@ class StreamMessageWidget extends StatefulWidget {
this.onMessageActions, this.onMessageActions,
this.onShowMessage, this.onShowMessage,
this.userAvatarBuilder, this.userAvatarBuilder,
this.quotedMessageBuilder,
this.editMessageInputBuilder, this.editMessageInputBuilder,
this.textBuilder, this.textBuilder,
@Deprecated(''' @Deprecated('''
@@ -354,6 +355,11 @@ class StreamMessageWidget extends StatefulWidget {
/// {@endtemplate} /// {@endtemplate}
final Widget Function(BuildContext, User)? userAvatarBuilder; final Widget Function(BuildContext, User)? userAvatarBuilder;
/// {@template quotedMessageBuilder}
/// Widget builder for building quoted message
/// {@endtemplate}
final Widget Function(BuildContext, Message)? quotedMessageBuilder;
/// {@template message} /// {@template message}
/// The message to display. /// The message to display.
/// {@endtemplate} /// {@endtemplate}
@@ -577,6 +583,7 @@ class StreamMessageWidget extends StatefulWidget {
Future<void> Function(Message)? onConfirmDeleteTap, Future<void> Function(Message)? onConfirmDeleteTap,
Widget Function(BuildContext, Message)? editMessageInputBuilder, Widget Function(BuildContext, Message)? editMessageInputBuilder,
Widget Function(BuildContext, Message)? textBuilder, Widget Function(BuildContext, Message)? textBuilder,
Widget Function(BuildContext, Message)? quotedMessageBuilder,
@Deprecated(''' @Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead. Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version. Will be removed in the next major version.
@@ -670,6 +677,7 @@ class StreamMessageWidget extends StatefulWidget {
editMessageInputBuilder: editMessageInputBuilder:
editMessageInputBuilder ?? this.editMessageInputBuilder, editMessageInputBuilder ?? this.editMessageInputBuilder,
textBuilder: textBuilder ?? this.textBuilder, textBuilder: textBuilder ?? this.textBuilder,
quotedMessageBuilder: quotedMessageBuilder ?? this.quotedMessageBuilder,
bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget, bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget,
onMessageActions: onMessageActions ?? this.onMessageActions, onMessageActions: onMessageActions ?? this.onMessageActions,
message: message ?? this.message, message: message ?? this.message,
@@ -965,6 +973,7 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
borderSide: widget.borderSide, borderSide: widget.borderSide,
borderRadiusGeometry: widget.borderRadiusGeometry, borderRadiusGeometry: widget.borderRadiusGeometry,
textBuilder: widget.textBuilder, textBuilder: widget.textBuilder,
quotedMessageBuilder: widget.quotedMessageBuilder,
onLinkTap: widget.onLinkTap, onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap, onMentionTap: widget.onMentionTap,
onQuotedMessageTap: widget.onQuotedMessageTap, onQuotedMessageTap: widget.onQuotedMessageTap,
@@ -63,6 +63,7 @@ class MessageWidgetContent extends StatelessWidget {
this.onMentionTap, this.onMentionTap,
this.onLinkTap, this.onLinkTap,
this.textBuilder, this.textBuilder,
this.quotedMessageBuilder,
@Deprecated(''' @Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead. Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version. Will be removed in the next major version.
@@ -170,6 +171,9 @@ class MessageWidgetContent extends StatelessWidget {
/// {@macro textBuilder} /// {@macro textBuilder}
final Widget Function(BuildContext, Message)? textBuilder; final Widget Function(BuildContext, Message)? textBuilder;
/// {@macro quotedMessageBuilder}
final Widget Function(BuildContext, Message)? quotedMessageBuilder;
/// {@macro showReactionPickerIndicator} /// {@macro showReactionPickerIndicator}
final bool showReactionPickerIndicator; final bool showReactionPickerIndicator;
@@ -351,6 +355,8 @@ class MessageWidgetContent extends StatelessWidget {
onMentionTap: onMentionTap, onMentionTap: onMentionTap,
onLinkTap: onLinkTap, onLinkTap: onLinkTap,
textBuilder: textBuilder, textBuilder: textBuilder,
quotedMessageBuilder:
quotedMessageBuilder,
borderRadiusGeometry: borderRadiusGeometry:
borderRadiusGeometry, borderRadiusGeometry,
borderSide: borderSide, borderSide: borderSide,
@@ -7,63 +7,41 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// ///
/// Used in [QuotedMessageCard]. Should not be used elsewhere. /// Used in [QuotedMessageCard]. Should not be used elsewhere.
/// {@endtemplate} /// {@endtemplate}
class QuotedMessage extends StatefulWidget { class QuotedMessage extends StatelessWidget {
/// {@macro quotedMessage} /// {@macro quotedMessage}
const QuotedMessage({ const QuotedMessage({
super.key, super.key,
required this.message, required this.message,
required this.reverse, required this.reverse,
required this.hasNonUrlAttachments, required this.hasNonUrlAttachments,
this.onQuotedMessageTap,
}); });
/// {@macro message} /// {@macro message}
final Message message; final Message message;
/// {@macro onQuotedMessageTap}
final OnQuotedMessageTap? onQuotedMessageTap;
/// {@macro reverse} /// {@macro reverse}
final bool reverse; final bool reverse;
/// {@macro hasNonUrlAttachments} /// {@macro hasNonUrlAttachments}
final bool hasNonUrlAttachments; final bool hasNonUrlAttachments;
@override
State<QuotedMessage> createState() => _QuotedMessageState();
}
class _QuotedMessageState extends State<QuotedMessage> {
late StreamChatState _streamChat;
late StreamChatThemeData _streamChatTheme;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_streamChatTheme = StreamChatTheme.of(context);
_streamChat = StreamChat.of(context);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isMyMessage = widget.message.user?.id == _streamChat.currentUser?.id; final streamChat = StreamChat.of(context);
final onTap = widget.message.quotedMessage?.isDeleted != true && final chatThemeData = StreamChatTheme.of(context);
widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) final isMyMessage = message.user?.id == streamChat.currentUser?.id;
: null;
final chatThemeData = _streamChatTheme;
return StreamQuotedMessageWidget( return StreamQuotedMessageWidget(
onTap: onTap, message: message.quotedMessage!,
message: widget.message.quotedMessage!,
messageTheme: isMyMessage messageTheme: isMyMessage
? chatThemeData.otherMessageTheme ? chatThemeData.otherMessageTheme
: chatThemeData.ownMessageTheme, : chatThemeData.ownMessageTheme,
reverse: widget.reverse, reverse: reverse,
padding: EdgeInsets.only( padding: EdgeInsets.only(
right: 8, right: 8,
left: 8, left: 8,
top: 8, top: 8,
bottom: widget.hasNonUrlAttachments ? 8 : 0, bottom: hasNonUrlAttachments ? 8 : 0,
), ),
); );
} }