From 914a82bb4944359f939575b6069bc51803c05f3d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 7 Dec 2020 22:29:40 +0530 Subject: [PATCH 01/30] Add Message Reply feature Signed-off-by: Sahil Kumar --- example/lib/main.dart | 36 +++- lib/src/extension.dart | 5 + lib/src/file_attachment.dart | 61 +------ lib/src/message_actions_modal.dart | 39 +++- lib/src/message_input.dart | 200 ++++++++++++++------ lib/src/message_list_view.dart | 100 ++++++---- lib/src/message_reactions_modal.dart | 2 +- lib/src/message_widget.dart | 90 +++++---- lib/src/reply_message_widget.dart | 264 +++++++++++++++++++++++++++ lib/src/stream_svg_icon.dart | 12 ++ lib/src/swipeable.dart | 162 ++++++++++++++++ lib/src/utils.dart | 104 +++++++++++ 12 files changed, 858 insertions(+), 217 deletions(-) create mode 100644 lib/src/extension.dart create mode 100644 lib/src/reply_message_widget.dart create mode 100644 lib/src/swipeable.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 4b529e23..ab002a62 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -415,10 +415,31 @@ class ChannelQuerySearchResultPage extends StatelessWidget { } } -class ChannelPage extends StatelessWidget { - const ChannelPage({ - Key key, - }) : super(key: key); +class ChannelPage extends StatefulWidget { + @override + _ChannelPageState createState() => _ChannelPageState(); +} + +class _ChannelPageState extends State { + Message _replyMessage; + FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + } + + @override + void dispose() { + _focusNode.dispose(); + super.dispose(); + } + + void _reply(Message message) { + setState(() => _replyMessage = message); + _focusNode.requestFocus(); + } @override Widget build(BuildContext context) { @@ -433,6 +454,8 @@ class ChannelPage extends StatelessWidget { child: Stack( children: [ MessageListView( + onMessageSwiped: _reply, + onReplyTap: _reply, threadBuilder: (_, parentMessage) { return ThreadPage( parent: parentMessage, @@ -458,7 +481,10 @@ class ChannelPage extends StatelessWidget { ], ), ), - MessageInput(), + MessageInput( + focusNode: _focusNode, + replyMessage: _replyMessage, + ), ], ), ); diff --git a/lib/src/extension.dart b/lib/src/extension.dart new file mode 100644 index 00000000..d0b18fe8 --- /dev/null +++ b/lib/src/extension.dart @@ -0,0 +1,5 @@ +extension StringExtension on String { + String capitalize() { + return "${this[0].toUpperCase()}${this.substring(1)}"; + } +} diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index fff13bbe..599b71f6 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -32,7 +32,7 @@ class FileAttachment extends StatelessWidget { child: Row( children: [ Container( - child: _getFileTypeImage(attachment.extraData['mime_type']), + child: getFileTypeImage(attachment.extraData['mime_type']), height: 40.0, width: 33.33, margin: EdgeInsets.all(8.0), @@ -116,63 +116,4 @@ class FileAttachment extends StatelessWidget { ), ); } - - StreamSvgIcon _getFileTypeImage(String type) { - switch (type) { - case '7z': - return StreamSvgIcon.filetype_7z(); - break; - case 'csv': - return StreamSvgIcon.filetype_csv(); - break; - case 'doc': - return StreamSvgIcon.filetype_doc(); - break; - case 'docx': - return StreamSvgIcon.filetype_docx(); - break; - case 'html': - return StreamSvgIcon.filetype_html(); - break; - case 'md': - return StreamSvgIcon.filetype_md(); - break; - case 'odt': - return StreamSvgIcon.filetype_odt(); - break; - case 'pdf': - return StreamSvgIcon.filetype_pdf(); - break; - case 'ppt': - return StreamSvgIcon.filetype_ppt(); - break; - case 'pptx': - return StreamSvgIcon.filetype_pptx(); - break; - case 'rar': - return StreamSvgIcon.filetype_rar(); - break; - case 'rtf': - return StreamSvgIcon.filetype_rtf(); - break; - case 'tar': - return StreamSvgIcon.filetype_tar(); - break; - case 'txt': - return StreamSvgIcon.filetype_txt(); - break; - case 'xls': - return StreamSvgIcon.filetype_xls(); - break; - case 'xlsx': - return StreamSvgIcon.filetype_xlsx(); - break; - case 'zip': - return StreamSvgIcon.filetype_zip(); - break; - default: - return StreamSvgIcon.filetype_Generic(); - break; - } - } } diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 1967cd2b..e8966197 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -15,7 +15,8 @@ import 'stream_chat_theme.dart'; class MessageActionsModal extends StatelessWidget { final Widget Function(BuildContext, Message) editMessageInputBuilder; - final void Function(Message) onThreadTap; + final void Function(Message) onThreadReplyTap; + final void Function(Message) onReplyTap; final Message message; final MessageTheme messageTheme; final bool showReactions; @@ -23,6 +24,7 @@ class MessageActionsModal extends StatelessWidget { final bool showCopyMessage; final bool showEditMessage; final bool showReply; + final bool showThreadReply; final bool reverse; final ShapeBorder messageShape; final DisplayWidget showUserAvatar; @@ -34,9 +36,11 @@ class MessageActionsModal extends StatelessWidget { this.showReactions = true, this.showDeleteMessage = true, this.showEditMessage = true, - this.onThreadTap, + this.onReplyTap, + this.onThreadReplyTap, this.showCopyMessage = true, this.showReply = true, + this.showThreadReply = true, this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, this.messageShape, @@ -114,6 +118,7 @@ class MessageActionsModal extends StatelessWidget { showReactions: false, showUsername: false, showReplyIndicator: false, + showThreadReplyIndicator: false, showUserAvatar: showUserAvatar, showTimestamp: false, translateUserAvatar: false, @@ -158,6 +163,12 @@ class MessageActionsModal extends StatelessWidget { message.status == null) && message.parentId == null) _buildReplyButton(context), + if (showThreadReply && + (message.status == + MessageSendingStatus.SENT || + message.status == null) && + message.parentId == null) + _buildThreadReplyButton(context), if (showEditMessage) _buildEditMessage(context), if (showDeleteMessage) @@ -181,6 +192,24 @@ class MessageActionsModal extends StatelessWidget { ); } + Widget _buildReplyButton(BuildContext context) { + return ListTile( + title: Text( + 'Reply', + style: Theme.of(context).textTheme.headline6, + ), + leading: StreamSvgIcon.reply( + color: StreamChatTheme.of(context).primaryIconTheme.color, + ), + onTap: () { + Navigator.pop(context); + if (onReplyTap != null) { + onReplyTap(message); + } + }, + ); + } + Widget _buildDeleteButton(BuildContext context) { return ListTile( title: Text( @@ -313,7 +342,7 @@ class MessageActionsModal extends StatelessWidget { ); } - Widget _buildReplyButton(BuildContext context) { + Widget _buildThreadReplyButton(BuildContext context) { return ListTile( title: Text( 'Thread reply', @@ -324,8 +353,8 @@ class MessageActionsModal extends StatelessWidget { ), onTap: () { Navigator.pop(context); - if (onThreadTap != null) { - onThreadTap(message); + if (onThreadReplyTap != null) { + onThreadReplyTap(message); } }, ); diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 4e30afe1..18b6d014 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:emojis/emoji.dart'; import 'package:file_picker/file_picker.dart'; @@ -21,9 +22,10 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; -import 'package:photo_manager/photo_manager.dart'; +import 'extension.dart'; import '../stream_chat_flutter.dart'; +import 'reply_message_widget.dart'; import 'stream_channel.dart'; typedef FileUploader = Future Function(PlatformFile, Channel); @@ -108,6 +110,7 @@ class MessageInput extends StatefulWidget { this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilders, this.focusNode, + this.replyMessage, }) : super(key: key); /// Message to edit @@ -156,6 +159,9 @@ class MessageInput extends StatefulWidget { /// The focus node associated to the TextField final FocusNode focusNode; + /// + final Message replyMessage; + @override MessageInputState createState() => MessageInputState(); @@ -167,7 +173,7 @@ class MessageInput extends StatefulWidget { if (messageInputState == null) { throw Exception( - 'You must have a MessageInput widget as anchestor of your widget tree'); + 'You must have a MessageInput widget as ancestor of your widget tree'); } return messageInputState; @@ -197,9 +203,13 @@ class MessageInputState extends State { /// The editing controller passed to the input TextField TextEditingController textEditingController; + Message _replyMessage; + + bool get _hasReplyMessage => _replyMessage != null; + @override Widget build(BuildContext context) { - return SafeArea( + Widget child = SafeArea( child: GestureDetector( onPanUpdate: (details) { if (details.delta.dy > 0) { @@ -214,8 +224,31 @@ class MessageInputState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ + if (_hasReplyMessage) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.reply( + color: Colors.black.withOpacity(0.2), + ), + ), + Text( + 'Reply to Message', + style: TextStyle(fontWeight: FontWeight.bold), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.close_small(), + onPressed: () { + setState(() => _replyMessage = null); + }, + ), + ], + ), Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), child: _buildTextField(context), ), if (widget.parentMessage != null) @@ -228,6 +261,14 @@ class MessageInputState extends State { ), ), ); + if (widget.editMessage == null) { + child = Material( + color: Colors.white, + elevation: 8, + child: child, + ); + } + return child; } Flex _buildTextField(BuildContext context) { @@ -332,6 +373,9 @@ class MessageInputState extends State { _actionsShrunk = false; }); }, + visualDensity: VisualDensity.compact, + splashRadius: 24, + padding: const EdgeInsets.all(0), icon: StreamSvgIcon.emptyCircleLeft( color: StreamChatTheme.of(context).accentColor, ), @@ -356,12 +400,13 @@ class MessageInputState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(20.0), border: Border.all( - color: Colors.grey, + color: Colors.black.withOpacity(0.16), ), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ + _buildRepyToMessage(), _buildAttachments(), LimitedBox( maxHeight: widget.maxHeight, @@ -436,6 +481,7 @@ class MessageInputState extends State { } Timer _debounce; + void _onChanged(BuildContext context, String s) { if (_debounce?.isActive == true) _debounce.cancel(); _debounce = Timer( @@ -799,7 +845,7 @@ class MessageInputState extends State { Widget _buildPickerSection() { var _attachmentContainsFile = - _attachments.any((element) => element.attachment.type == 'file'); + _attachments.any((element) => element.attachment?.type == 'file'); switch (_filePickerIndex) { case 0: @@ -1232,6 +1278,25 @@ class MessageInputState extends State { _commandsOverlay = null; } + Widget _buildRepyToMessage() { + if (!_hasReplyMessage) { + return Offstage(); + } + final containsUrl = _replyMessage.attachments + ?.any((element) => element.ogScrapeUrl != null) == + true; + return Transform( + transform: Matrix4.rotationY(pi), + alignment: Alignment.center, + child: ReplyMessageWidget( + reverse: true, + showBorder: !containsUrl, + message: _replyMessage, + messageTheme: StreamChatTheme.of(context).otherMessageTheme, + ), + ); + } + Widget _buildAttachments() { return _attachments.isEmpty ? Container() @@ -1437,57 +1502,68 @@ class MessageInputState extends State { } Widget _buildCommandButton() { - return InkWell( - child: Padding( - padding: - const EdgeInsets.only(left: 4.0, right: 8.0, top: 8.0, bottom: 8.0), - child: StreamSvgIcon.lightning( + return Padding( + padding: const EdgeInsets.all(8.0), + child: IconButton( + icon: StreamSvgIcon.lightning( color: Color(0xFF000000).withAlpha(128), ), + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () { + if (_commandsOverlay == null) { + _commandsOverlay = _buildCommandsOverlayEntry(); + Overlay.of(context).insert(_commandsOverlay); + } else { + _commandsOverlay?.remove(); + _commandsOverlay = null; + } + }, ), - onTap: () { - if (_commandsOverlay == null) { - _commandsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_commandsOverlay); - } else { - _commandsOverlay?.remove(); - _commandsOverlay = null; - } - }, ); } Widget _buildAttachmentButton() { var padding = widget.editMessage == null ? 4.0 : 8.0; return Center( - child: InkWell( - child: Padding( - padding: - EdgeInsets.only(left: 8.0, right: padding, top: 8.0, bottom: 8.0), - child: StreamSvgIcon.attach( + child: Padding( + padding: + EdgeInsets.only(left: 8.0, right: padding, top: 8.0, bottom: 8.0), + child: IconButton( + icon: StreamSvgIcon.attach( color: _openFilePickerSection ? StreamChatTheme.of(context).accentColor : Color(0xFF000000).withAlpha(128), ), - ), - onTap: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, + ), ), ); } @@ -1786,15 +1862,11 @@ class MessageInputState extends State { return Padding( padding: const EdgeInsets.all(8.0), child: Center( - child: InkWell( - onTap: () { - sendMessage(); - }, child: StreamSvgIcon( assetName: _getIdleSendIcon(), color: Colors.grey, ), - )), + ), ); } @@ -1802,11 +1874,16 @@ class MessageInputState extends State { return Center( child: Padding( padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () { - sendMessage(); - }, - child: StreamSvgIcon( + child: IconButton( + onPressed: sendMessage, + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + icon: StreamSvgIcon( assetName: _getSendIcon(), color: StreamChatTheme.of(context).accentColor, ), @@ -1918,6 +1995,8 @@ class MessageInputState extends State { void initState() { super.initState(); + _replyMessage = widget.replyMessage; + _focusNode = widget.focusNode ?? FocusNode(); _emojiNames = Emoji.all().map((e) => e.name); @@ -1970,6 +2049,7 @@ class MessageInputState extends State { } bool _initialized = false; + @override void didChangeDependencies() { if (widget.editMessage != null && !_initialized) { @@ -1978,6 +2058,14 @@ class MessageInputState extends State { } super.didChangeDependencies(); } + + @override + void didUpdateWidget(MessageInput oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.replyMessage?.id != _replyMessage?.id) { + _replyMessage = widget.replyMessage; + } + } } class _SendingAttachment { @@ -1994,12 +2082,6 @@ class _SendingAttachment { }); } -extension StringExtension on String { - String capitalize() { - return "${this[0].toUpperCase()}${this.substring(1)}"; - } -} - /// Represents a 2-tuple, or pair. class Tuple2 { /// Returns the first item of the tuple diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 430e0b85..2881496e 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -13,6 +13,7 @@ import 'package:visibility_detector/visibility_detector.dart'; import '../stream_chat_flutter.dart'; import 'date_divider.dart'; import 'stream_channel.dart'; +import 'swipeable.dart'; typedef MessageBuilder = Widget Function( BuildContext, @@ -26,6 +27,9 @@ typedef ParentMessageBuilder = Widget Function( typedef ThreadBuilder = Widget Function(BuildContext context, Message parent); typedef ThreadTapCallback = void Function(Message, Widget); +typedef OnMessageSwiped = void Function(Message); +typedef ReplyTapCallback = void Function(Message); + class MessageDetails { /// True if the message belongs to the current user bool isMyMessage; @@ -106,12 +110,14 @@ class MessageListView extends StatefulWidget { this.parentMessage, this.threadBuilder, this.onThreadTap, + this.onReplyTap, this.dateDividerBuilder, this.scrollPhysics = const AlwaysScrollableScrollPhysics(), this.initialScrollIndex = 0, this.initialAlignment = 0, this.scrollController, this.itemPositionListener, + this.onMessageSwiped, }) : super(key: key); /// Function used to build a custom message widget @@ -153,6 +159,12 @@ class MessageListView extends StatefulWidget { /// The ScrollPhysics used by the ListView final ScrollPhysics scrollPhysics; + /// Called when message item gets swiped + final OnMessageSwiped onMessageSwiped; + + /// + final ReplyTapCallback onReplyTap; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -273,7 +285,7 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder( + builder: (context) => widget.messageBuilder( context, MessageDetails( context, @@ -540,6 +552,7 @@ class _MessageListViewState extends State { return MessageWidget( showReplyIndicator: false, + showThreadReplyIndicator: false, message: message, reverse: isMyMessage, showUsername: !isMyMessage, @@ -597,47 +610,54 @@ class _MessageListViewState extends State { final allRead = readList.length >= (channel.memberCount ?? 0) - 1; - return MessageWidget( - key: ValueKey('MESSAGE-${message.id}'), - message: message, - reverse: isMyMessage, - showReactions: !message.isDeleted, - padding: EdgeInsets.only( - left: 8.0, - right: 8.0, - bottom: index == 0 ? 30 : (isNextUser ? 5 : 10), + return Swipeable( + onSwipeEnd: () => widget.onMessageSwiped(message), + backgroundIcon: StreamSvgIcon.reply( + color: StreamChatTheme.of(context).accentColor, ), - showUsername: !isMyMessage && !isNextUser, - showSendingIndicator: isMyMessage && - (index == 0 || message.status != MessageSendingStatus.SENT) - ? DisplayWidget.show - : DisplayWidget.hide, - showTimestamp: !isNextUser || readList?.isNotEmpty == true, - showEditMessage: isMyMessage, - showDeleteMessage: isMyMessage, - borderSide: isMyMessage ? BorderSide.none : null, - onThreadTap: _onThreadTap, - attachmentBorderRadiusGeometry: BorderRadius.only( - topLeft: Radius.circular(16), - bottomLeft: Radius.circular(!isNextUser ? 0 : 16), - topRight: Radius.circular(16), - bottomRight: Radius.circular(16), + child: MessageWidget( + key: ValueKey('MESSAGE-${message.id}'), + message: message, + reverse: isMyMessage, + showReactions: !message.isDeleted, + padding: EdgeInsets.only( + left: 8.0, + right: 8.0, + bottom: index == 0 ? 30 : (isNextUser ? 5 : 10), + ), + showUsername: !isMyMessage && !isNextUser, + showSendingIndicator: isMyMessage && + (index == 0 || message.status != MessageSendingStatus.SENT) + ? DisplayWidget.show + : DisplayWidget.hide, + showTimestamp: !isNextUser || readList?.isNotEmpty == true, + showEditMessage: isMyMessage, + showDeleteMessage: isMyMessage, + borderSide: isMyMessage ? BorderSide.none : null, + onThreadTap: _onThreadTap, + onReplyTap: widget.onReplyTap, + attachmentBorderRadiusGeometry: BorderRadius.only( + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(!isNextUser ? 0 : 16), + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + attachmentPadding: const EdgeInsets.all(2), + borderRadiusGeometry: BorderRadius.only( + topLeft: Radius.circular(16), + bottomLeft: Radius.circular(!isNextUser ? 0 : 16), + topRight: Radius.circular(16), + bottomRight: Radius.circular(16), + ), + showUserAvatar: isMyMessage + ? DisplayWidget.gone + : (isNextUser ? DisplayWidget.hide : DisplayWidget.show), + messageTheme: isMyMessage + ? StreamChatTheme.of(context).ownMessageTheme + : StreamChatTheme.of(context).otherMessageTheme, + readList: readList, + allRead: allRead, ), - attachmentPadding: const EdgeInsets.all(2), - borderRadiusGeometry: BorderRadius.only( - topLeft: Radius.circular(16), - bottomLeft: Radius.circular(!isNextUser ? 0 : 16), - topRight: Radius.circular(16), - bottomRight: Radius.circular(16), - ), - showUserAvatar: isMyMessage - ? DisplayWidget.gone - : (isNextUser ? DisplayWidget.hide : DisplayWidget.show), - messageTheme: isMyMessage - ? StreamChatTheme.of(context).ownMessageTheme - : StreamChatTheme.of(context).otherMessageTheme, - readList: readList, - allRead: allRead, ); } diff --git a/lib/src/message_reactions_modal.dart b/lib/src/message_reactions_modal.dart index db5d8710..e5043e62 100644 --- a/lib/src/message_reactions_modal.dart +++ b/lib/src/message_reactions_modal.dart @@ -105,7 +105,7 @@ class MessageReactionsModal extends StatelessWidget { showReactions: false, showUsername: false, showUserAvatar: showUserAvatar, - showReplyIndicator: false, + showThreadReplyIndicator: false, showTimestamp: false, translateUserAvatar: false, showSendingIndicator: DisplayWidget.gone, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index e5fe146f..27fa4df4 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -11,11 +11,13 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; +import 'package:stream_chat_flutter/src/reply_message_widget.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'image_group.dart'; import 'message_text.dart'; +import 'extension.dart'; typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); @@ -46,6 +48,7 @@ class MessageWidget extends StatefulWidget { /// The function called when tapping on replies final void Function(Message) onThreadTap; + final void Function(Message) onReplyTap; final Widget Function(BuildContext, Message) editMessageInputBuilder; final Widget Function(BuildContext, Message) textBuilder; @@ -55,6 +58,9 @@ class MessageWidget extends StatefulWidget { /// The message final Message message; + /// The replyMessage + final Message replyMessage; + /// The message theme final MessageTheme messageTheme; @@ -99,6 +105,9 @@ class MessageWidget extends StatefulWidget { final bool allRead; + /// If true the widget will show the thread reply indicator + final bool showThreadReplyIndicator; + /// If true the widget will show the reply indicator final bool showReplyIndicator; @@ -127,6 +136,7 @@ class MessageWidget extends StatefulWidget { Key key, @required this.message, @required this.messageTheme, + this.replyMessage, this.reverse = false, this.translateUserAvatar = true, this.shape, @@ -140,6 +150,8 @@ class MessageWidget extends StatefulWidget { this.showUserAvatar = DisplayWidget.show, this.showSendingIndicator = DisplayWidget.show, this.showReplyIndicator = true, + this.showThreadReplyIndicator = true, + this.onReplyTap, this.onThreadTap, this.showUsername = true, this.showTimestamp = true, @@ -213,6 +225,8 @@ class MessageWidget extends StatefulWidget { } class _MessageWidgetState extends State { + bool get _hasReplyMessage => widget.replyMessage != null; + @override Widget build(BuildContext context) { var leftPadding = widget.showUserAvatar != DisplayWidget.gone @@ -230,6 +244,9 @@ class _MessageWidgetState extends State { widget.message.attachments?.any((element) => element.type == 'file') == true; + final isMyMessage = + widget.message.user.id == StreamChat.of(context).user.id; + return Portal( child: Padding( padding: widget.padding ?? EdgeInsets.all(8), @@ -306,7 +323,8 @@ class _MessageWidgetState extends State { clipBehavior: Clip.antiAlias, shape: widget.shape ?? RoundedRectangleBorder( - side: isOnlyEmoji + side: isOnlyEmoji && + !_hasReplyMessage ? BorderSide.none : widget.borderSide ?? BorderSide( @@ -333,6 +351,19 @@ class _MessageWidgetState extends State { CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + if (_hasReplyMessage) + ReplyMessageWidget( + message: + widget.replyMessage, + messageTheme: isMyMessage + ? StreamChatTheme.of( + context) + .otherMessageTheme + : StreamChatTheme.of( + context) + .ownMessageTheme, + reverse: widget.reverse, + ), ..._parseAttachments(context), if (widget.message.text .trim() @@ -367,7 +398,7 @@ class _MessageWidgetState extends State { ), ], ), - if (widget.showReplyIndicator && + if (widget.showThreadReplyIndicator && widget.message.replyCount > 0) _buildReplyIndicator(leftPadding), ], @@ -393,7 +424,7 @@ class _MessageWidgetState extends State { var splitList = host.split('.'); var hostName = splitList.length == 3 ? splitList[1] : splitList[0]; var hostDisplayName = urlAttachment.authorName?.capitalize() ?? - _getWebsiteName(hostName.toLowerCase()) ?? + getWebsiteName(hostName.toLowerCase()) ?? hostName.capitalize(); return UrlAttachment( @@ -562,14 +593,16 @@ class _MessageWidgetState extends State { showDeleteMessage: widget.showDeleteMessage, message: widget.message, editMessageInputBuilder: widget.editMessageInputBuilder, - onThreadTap: widget.onThreadTap, + onReplyTap: widget.onReplyTap, + onThreadReplyTap: widget.onThreadTap, showEditMessage: widget.showEditMessage && widget.message.attachments ?.any((element) => element.type == 'giphy') != true, showReactions: widget.showReactions, - showReply: - widget.showReplyIndicator && widget.onThreadTap != null, + showReply: widget.showReplyIndicator, + showThreadReply: + widget.showThreadReplyIndicator && widget.onThreadTap != null, ), ); }); @@ -878,6 +911,10 @@ class _MessageWidgetState extends State { final isOnlyEmoji = widget.message.text.characters.every((c) => Emoji.byChar(c) != null); + if (_hasReplyMessage) { + return widget.messageTheme.messageBackgroundColor; + } + if ((widget.message.status == MessageSendingStatus.FAILED || widget.message.status == MessageSendingStatus.FAILED_UPDATE || widget.message.status == MessageSendingStatus.FAILED_DELETE)) { @@ -918,45 +955,4 @@ class _MessageWidgetState extends State { return; } } - - String _getWebsiteName(String hostName) { - switch (hostName) { - case 'reddit': - return 'Reddit'; - case 'youtube': - return 'Youtube'; - case 'wikipedia': - return 'Wikipedia'; - case 'twitter': - return 'Twitter'; - case 'facebook': - return 'Facebook'; - case 'amazon': - return 'Amazon'; - case 'yelp': - return 'Yelp'; - case 'imdb': - return 'IMDB'; - case 'pinterest': - return 'Pinterest'; - case 'tripadvisor': - return 'TripAdvisor'; - case 'instagram': - return 'Instagram'; - case 'walmart': - return 'Walmart'; - case 'craigslist': - return 'Craigslist'; - case 'ebay': - return 'eBay'; - case 'linkedin': - return 'LinkedIn'; - case 'google': - return 'Google'; - case 'apple': - return 'Apple'; - default: - return null; - } - } } diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart new file mode 100644 index 00000000..9ae6f23e --- /dev/null +++ b/lib/src/reply_message_widget.dart @@ -0,0 +1,264 @@ +import 'dart:math'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:emojis/emoji.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'attachment_error.dart'; +import 'image_attachment.dart'; +import 'message_text.dart'; +import 'stream_chat_theme.dart'; +import 'user_avatar.dart'; +import 'utils.dart'; + +/// +class ReplyMessageWidget extends StatelessWidget { + /// The message + final Message message; + + /// The message theme + final MessageTheme messageTheme; + + /// If true the widget will be mirrored + final bool reverse; + + /// If true the message will show a grey border + final bool showBorder; + + /// limit of the text message shown + final int textLimit; + + final Map _attachmentBuilders; + + /// + ReplyMessageWidget({ + Key key, + @required this.message, + @required this.messageTheme, + this.reverse = false, + this.showBorder = false, + this.textLimit = 170, + }) : _attachmentBuilders = { + 'image': (attachment) { + return ImageAttachment( + attachment: attachment, + message: message, + messageTheme: messageTheme, + size: Size(32, 32), + ); + }, + 'video': (attachment) { + final size = Size(32, 32); + if (attachment.thumbUrl != null) { + return Container( + height: size.height, + width: size.width, + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: CachedNetworkImageProvider( + attachment.thumbUrl, + ), + ), + ), + ); + } + return AttachmentError( + attachment: attachment, + size: size, + ); + }, + 'giphy': (attachment) { + final size = Size(32, 32); + return CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, + ), + fit: BoxFit.cover, + ); + }, + 'file': (attachment) { + return Container( + height: 32, + width: 32, + child: getFileTypeImage(attachment.extraData['mime_type']), + ); + }, + }, + super(key: key); + + bool get _hasAttachments => message.attachments?.isNotEmpty == true; + + bool get _containsScrapeUrl => + message.attachments?.any((element) => element.ogScrapeUrl != null) == + true; + + bool get _containsText => message?.text?.isNotEmpty == true; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 8, bottom: 6, right: 4, left: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded(child: _buildMessage(context)), + SizedBox(width: 4), + _buildUserAvatar(), + ], + ), + ); + } + + Widget _buildMessage(BuildContext context) { + final children = [ + if (_hasAttachments) ...[ + _parseAttachments(context), + SizedBox(width: 8), + ], + Expanded(child: _buildTextMessage()), + ]; + return Container( + constraints: BoxConstraints( + minHeight: 48.0, + ), + decoration: BoxDecoration( + color: _getBackgroundColor(), + border: showBorder ? Border.all(color: Colors.black.withOpacity(0.08)) : null, + borderRadius: BorderRadius.only( + topRight: Radius.circular(12), + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + ), + ), + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: + reverse ? MainAxisAlignment.end : MainAxisAlignment.start, + children: reverse ? children.reversed.toList() : children, + ), + ); + } + + Widget _buildTextMessage() { + final isOnlyEmoji = + message.text.characters.every((c) => Emoji.byChar(c) != null); + var msg = _hasAttachments && !_containsText + ? message.copyWith(text: message.attachments.last?.title ?? 'File') + : message; + if (msg.text.length > textLimit) { + msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...'); + } + return Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: MessageText( + message: msg, + messageTheme: isOnlyEmoji && _containsText + ? messageTheme.copyWith( + messageText: messageTheme.messageText.copyWith( + fontSize: 24, + )) + : messageTheme, + ), + ); + } + + Widget _buildUrlAttachment(Attachment attachment) { + final size = Size(32, 32); + if (attachment.thumbUrl != null) { + return Container( + height: size.height, + width: size.width, + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.cover, + image: CachedNetworkImageProvider( + attachment.imageUrl, + ), + ), + ), + ); + } + return AttachmentError( + attachment: attachment, + size: size, + ); + } + + Widget _parseAttachments(BuildContext context) { + Widget child; + Attachment attachment; + if (_containsScrapeUrl) { + attachment = message.attachments.firstWhere( + (element) => element.ogScrapeUrl != null, + ); + child = _buildUrlAttachment(attachment); + } else { + attachment = message.attachments.last; + final attachmentBuilder = _attachmentBuilders[attachment.type]; + if (attachmentBuilder == null) { + child = Offstage(); + } + child = attachmentBuilder(attachment); + } + return Material( + clipBehavior: Clip.hardEdge, + color: Colors.transparent, + shape: attachment.type == 'file' ? null : _getDefaultShape(context), + child: child, + ); + } + + ShapeBorder _getDefaultShape(BuildContext context) { + return RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white.withAlpha(24) + : Colors.black.withAlpha(24), + ), + borderRadius: BorderRadius.circular(8), + ); + } + + Widget _buildUserAvatar() { + return Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: UserAvatar( + user: message.user, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + showOnlineStatus: false, + ), + ), + ); + } + + Color _getBackgroundColor() { + if (_containsScrapeUrl) { + return Color(0xFFE9F2FF); + } + return messageTheme.messageBackgroundColor; + } +} diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index 42fed46e..607f7208 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -326,6 +326,18 @@ class StreamSvgIcon extends StatelessWidget { ); } + factory StreamSvgIcon.reply({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_curve_line_left_up_big.svg', + color: color, + width: size, + height: size, + ); + } + factory StreamSvgIcon.edit({ double size, Color color, diff --git a/lib/src/swipeable.dart b/lib/src/swipeable.dart new file mode 100644 index 00000000..bdaf6993 --- /dev/null +++ b/lib/src/swipeable.dart @@ -0,0 +1,162 @@ +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +/// +class Swipeable extends StatefulWidget { + final Widget child; + final Widget backgroundIcon; + final VoidCallback onSwipeStart; + final VoidCallback onSwipeCancel; + final VoidCallback onSwipeEnd; + final double threshold; + + /// + const Swipeable({ + @required this.child, + @required this.backgroundIcon, + this.onSwipeStart, + this.onSwipeCancel, + this.onSwipeEnd, + this.threshold = 82.0, + }); + + @override + State createState() => _SwipeableState(); +} + +class _SwipeableState extends State with TickerProviderStateMixin { + double _dragExtent = 0.0; + AnimationController _moveController; + AnimationController _iconMoveController; + Animation _moveAnimation; + Animation _iconTransitionAnimation; + Animation _iconFadeAnimation; + bool _pastThreshold = false; + + final _animationDuration = const Duration(milliseconds: 200); + + @override + void initState() { + super.initState(); + _moveController = + AnimationController(duration: _animationDuration, vsync: this); + _iconMoveController = + AnimationController(duration: _animationDuration, vsync: this); + _moveAnimation = Tween(begin: Offset.zero, end: Offset(1.0, 0.0)) + .animate(_moveController); + _iconTransitionAnimation = + Tween(begin: Offset(-0.1, 0.0), end: Offset(0.4, 0.0)) + .animate(_moveController); + _iconFadeAnimation = + Tween(begin: 0.7, end: 1.0).animate(_iconMoveController); + + final controllerValue = 0.0; + _moveController.animateTo(controllerValue); + _iconMoveController.animateTo(controllerValue); + } + + @override + void dispose() { + _moveController.dispose(); + _iconMoveController.dispose(); + super.dispose(); + } + + void _handleDragStart(DragStartDetails details) { + if (widget.onSwipeStart != null) { + widget.onSwipeStart(); + } + } + + void _handleDragUpdate(DragUpdateDetails details) { + print(_moveAnimation.value.dx); + + final delta = details.primaryDelta; + if (delta.isNegative) return; + + _dragExtent += delta; + + var movePastThresholdPixels = widget.threshold; + var newPos = _dragExtent.abs() / context.size.width; + + if (_dragExtent.abs() > movePastThresholdPixels) { + // how many "thresholds" past the threshold we are. 1 = the threshold 2 + // = two thresholds. + var n = _dragExtent.abs() / movePastThresholdPixels; + + // Take the number of thresholds past the threshold, and reduce this + // number + var reducedThreshold = math.pow(n, 0.3); + + var adjustedPixelPos = movePastThresholdPixels * reducedThreshold; + newPos = adjustedPixelPos / context.size.width; + + if (_dragExtent > 0 && !_pastThreshold) { + _iconMoveController.value = 1; + _pastThreshold = true; + } + } else { + // Send a cancel event if the user has swiped back underneath the + // threshold + if (_pastThreshold && widget.onSwipeCancel != null) { + widget.onSwipeCancel(); + } + _pastThreshold = false; + } + if (!_pastThreshold || newPos < _moveController.value) { + _iconMoveController.value = newPos; + } + _moveController.value = newPos; + } + + void _handleDragEnd(DragEndDetails details) { + _moveController.animateTo(0.0, duration: _animationDuration); + _iconMoveController.animateTo(0.0, duration: _animationDuration); + _dragExtent = 0.0; + if (_pastThreshold && widget.onSwipeEnd != null) { + widget.onSwipeEnd(); + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onHorizontalDragStart: _handleDragStart, + onHorizontalDragUpdate: _handleDragUpdate, + onHorizontalDragEnd: _handleDragEnd, + behavior: HitTestBehavior.opaque, + child: Stack( + alignment: Alignment.center, + fit: StackFit.passthrough, + children: [ + SlideTransition( + position: _iconTransitionAnimation, + child: Row( + children: [ + FadeTransition( + opacity: _iconFadeAnimation, + child: Container( + margin: const EdgeInsets.all(8), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: Colors.black.withOpacity(0.08), + ), + ), + child: widget.backgroundIcon, + ), + ), + ], + ), + ), + SlideTransition( + position: _moveAnimation, + child: widget.child, + ), + ], + ), + ); + } +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 20e3ce1f..aad972a4 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'stream_svg_icon.dart'; + Future launchURL(BuildContext context, String url) async { if (await canLaunch(url)) { await launch(url); @@ -47,3 +49,105 @@ Future showConfirmationDialog( /// Get random png with initials String getRandomPicUrl(User user) => 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; + +/// Get websiteName from [hostName] +String getWebsiteName(String hostName) { + switch (hostName) { + case 'reddit': + return 'Reddit'; + case 'youtube': + return 'Youtube'; + case 'wikipedia': + return 'Wikipedia'; + case 'twitter': + return 'Twitter'; + case 'facebook': + return 'Facebook'; + case 'amazon': + return 'Amazon'; + case 'yelp': + return 'Yelp'; + case 'imdb': + return 'IMDB'; + case 'pinterest': + return 'Pinterest'; + case 'tripadvisor': + return 'TripAdvisor'; + case 'instagram': + return 'Instagram'; + case 'walmart': + return 'Walmart'; + case 'craigslist': + return 'Craigslist'; + case 'ebay': + return 'eBay'; + case 'linkedin': + return 'LinkedIn'; + case 'google': + return 'Google'; + case 'apple': + return 'Apple'; + default: + return null; + } +} + +/// +StreamSvgIcon getFileTypeImage(String type) { + switch (type) { + case '7z': + return StreamSvgIcon.filetype_7z(); + break; + case 'csv': + return StreamSvgIcon.filetype_csv(); + break; + case 'doc': + return StreamSvgIcon.filetype_doc(); + break; + case 'docx': + return StreamSvgIcon.filetype_docx(); + break; + case 'html': + return StreamSvgIcon.filetype_html(); + break; + case 'md': + return StreamSvgIcon.filetype_md(); + break; + case 'odt': + return StreamSvgIcon.filetype_odt(); + break; + case 'pdf': + return StreamSvgIcon.filetype_pdf(); + break; + case 'ppt': + return StreamSvgIcon.filetype_ppt(); + break; + case 'pptx': + return StreamSvgIcon.filetype_pptx(); + break; + case 'rar': + return StreamSvgIcon.filetype_rar(); + break; + case 'rtf': + return StreamSvgIcon.filetype_rtf(); + break; + case 'tar': + return StreamSvgIcon.filetype_tar(); + break; + case 'txt': + return StreamSvgIcon.filetype_txt(); + break; + case 'xls': + return StreamSvgIcon.filetype_xls(); + break; + case 'xlsx': + return StreamSvgIcon.filetype_xlsx(); + break; + case 'zip': + return StreamSvgIcon.filetype_zip(); + break; + default: + return StreamSvgIcon.filetype_Generic(); + break; + } +} From 8223f4833005833bcba7f0465cef81fe543bf4f2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 7 Dec 2020 22:45:59 +0530 Subject: [PATCH 02/30] flutter format Signed-off-by: Sahil Kumar --- lib/src/reply_message_widget.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index 9ae6f23e..9a56c601 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -140,7 +140,9 @@ class ReplyMessageWidget extends StatelessWidget { ), decoration: BoxDecoration( color: _getBackgroundColor(), - border: showBorder ? Border.all(color: Colors.black.withOpacity(0.08)) : null, + border: showBorder + ? Border.all(color: Colors.black.withOpacity(0.08)) + : null, borderRadius: BorderRadius.only( topRight: Radius.circular(12), topLeft: Radius.circular(12), From e78bdc60278f96407d89b2c009363a8793e76648 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 8 Dec 2020 11:52:11 +0530 Subject: [PATCH 03/30] fix test Signed-off-by: Sahil Kumar --- test/src/message_action_modal_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/src/message_action_modal_test.dart b/test/src/message_action_modal_test.dart index 5b0c1678..0dcd8d87 100644 --- a/test/src/message_action_modal_test.dart +++ b/test/src/message_action_modal_test.dart @@ -71,6 +71,7 @@ void main() { showCopyMessage: false, showDeleteMessage: false, showReply: false, + showThreadReply: false, message: Message( text: 'test', user: User( @@ -87,6 +88,7 @@ void main() { await tester.pump(Duration(milliseconds: 1000)); expect(find.byKey(Key('MessageWidget')), findsOneWidget); + expect(find.text('Reply'), findsNothing); expect(find.text('Thread reply'), findsNothing); expect(find.text('Edit message'), findsNothing); expect(find.text('Delete message'), findsNothing); From c133002118a126774758bfaa145ca94d1eae4627 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 8 Dec 2020 17:01:29 +0530 Subject: [PATCH 04/30] [Swipeable] Allow widget to swipe backward Signed-off-by: Sahil Kumar --- lib/src/swipeable.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/swipeable.dart b/lib/src/swipeable.dart index bdaf6993..d56a8e24 100644 --- a/lib/src/swipeable.dart +++ b/lib/src/swipeable.dart @@ -70,13 +70,11 @@ class _SwipeableState extends State with TickerProviderStateMixin { } void _handleDragUpdate(DragUpdateDetails details) { - print(_moveAnimation.value.dx); - final delta = details.primaryDelta; - if (delta.isNegative) return; - _dragExtent += delta; + if (_dragExtent.isNegative) return; + var movePastThresholdPixels = widget.threshold; var newPos = _dragExtent.abs() / context.size.width; From 4c3cb91318056b2129935df4c1980081053092bc Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 8 Dec 2020 17:02:58 +0530 Subject: [PATCH 05/30] [Reply Message Widget] Remove default message text. Signed-off-by: Sahil Kumar --- lib/src/reply_message_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index 9a56c601..d09903cf 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -162,7 +162,7 @@ class ReplyMessageWidget extends StatelessWidget { final isOnlyEmoji = message.text.characters.every((c) => Emoji.byChar(c) != null); var msg = _hasAttachments && !_containsText - ? message.copyWith(text: message.attachments.last?.title ?? 'File') + ? message.copyWith(text: message.attachments.last?.title ?? '') : message; if (msg.text.length > textLimit) { msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...'); From 54061c8d509bd50271d0875612b0de32e6c95694 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 8 Dec 2020 17:12:33 +0530 Subject: [PATCH 06/30] [MessageInput] Remove reply message widget when sendMessage is called. Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 18b6d014..7aaa8f5f 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1925,6 +1925,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); + _replyMessage = null; setState(() { _messageIsPresent = false; From bec480dad7bcd1a7dcecce02af448cbf2d080499 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 9 Dec 2020 12:29:29 +0530 Subject: [PATCH 07/30] [Swipeable] Remove extra margin Signed-off-by: Sahil Kumar --- lib/src/swipeable.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/swipeable.dart b/lib/src/swipeable.dart index d56a8e24..c2279e63 100644 --- a/lib/src/swipeable.dart +++ b/lib/src/swipeable.dart @@ -135,7 +135,6 @@ class _SwipeableState extends State with TickerProviderStateMixin { FadeTransition( opacity: _iconFadeAnimation, child: Container( - margin: const EdgeInsets.all(8), padding: const EdgeInsets.all(4), decoration: BoxDecoration( shape: BoxShape.circle, From ae73eb95c60c6f27e81c9ba4ae21c5d52a1c53a4 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 9 Dec 2020 13:50:03 +0530 Subject: [PATCH 08/30] [ReplyMessageWidget] remove expanded width Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 1 + lib/src/reply_message_widget.dart | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 7aaa8f5f..d1a670f9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -405,6 +405,7 @@ class MessageInputState extends State { ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildRepyToMessage(), _buildAttachments(), diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index d09903cf..7fdae3f2 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -117,8 +117,9 @@ class ReplyMessageWidget extends StatelessWidget { padding: const EdgeInsets.only(top: 8, bottom: 6, right: 4, left: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, children: [ - Expanded(child: _buildMessage(context)), + Flexible(child: _buildMessage(context)), SizedBox(width: 4), _buildUserAvatar(), ], @@ -132,7 +133,7 @@ class ReplyMessageWidget extends StatelessWidget { _parseAttachments(context), SizedBox(width: 8), ], - Expanded(child: _buildTextMessage()), + Flexible(child: _buildTextMessage()), ]; return Container( constraints: BoxConstraints( @@ -151,6 +152,7 @@ class ReplyMessageWidget extends StatelessWidget { ), padding: const EdgeInsets.all(8), child: Row( + mainAxisSize: MainAxisSize.min, mainAxisAlignment: reverse ? MainAxisAlignment.end : MainAxisAlignment.start, children: reverse ? children.reversed.toList() : children, From 09fcbad9003546510797af9dd7494ae948fead7c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 9 Dec 2020 16:35:07 +0530 Subject: [PATCH 09/30] [ReplyMessageWidget] fix video attachment thumbnail Signed-off-by: Sahil Kumar --- lib/src/message_widget.dart | 5 +- lib/src/reply_message_widget.dart | 191 +++++++++++++++++++----------- 2 files changed, 123 insertions(+), 73 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index c0968cce..ff0d1c7c 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -892,8 +892,9 @@ class _MessageWidgetState extends State { ), ), if (widget.message.attachments - ?.any((element) => element.ogScrapeUrl != null) == - true) + ?.any((element) => element.ogScrapeUrl != null) == + true && + !_hasReplyMessage) _buildUrlAttachment(), ], ), diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index 7fdae3f2..56f3112f 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -4,6 +4,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:video_player/video_player.dart'; import 'attachment_error.dart'; import 'image_attachment.dart'; @@ -12,6 +13,55 @@ import 'stream_chat_theme.dart'; import 'user_avatar.dart'; import 'utils.dart'; +typedef ReplyMessageAttachmentThumbnailBuilder = Widget Function( + BuildContext, + Attachment, +); + +class _VideoAttachmentThumbnail extends StatefulWidget { + final Size size; + final Attachment attachment; + + const _VideoAttachmentThumbnail({ + Key key, + @required this.attachment, + this.size = const Size(32, 32), + }) : super(key: key); + + @override + _VideoAttachmentThumbnailState createState() => + _VideoAttachmentThumbnailState(); +} + +class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { + VideoPlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = VideoPlayerController.network(widget.attachment.assetUrl) + ..initialize().then((_) { + setState(() {}); //when your thumbnail will show. + }); + } + + @override + void dispose() { + super.dispose(); + _controller.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: widget.size.height, + width: widget.size.width, + child: _controller.value.initialized + ? VideoPlayer(_controller) + : CircularProgressIndicator()); + } +} + /// class ReplyMessageWidget extends StatelessWidget { /// The message @@ -29,7 +79,9 @@ class ReplyMessageWidget extends StatelessWidget { /// limit of the text message shown final int textLimit; - final Map _attachmentBuilders; + /// Map that defines a thumbnail builder for an attachment type + final Map + attachmentThumbnailBuilders; /// ReplyMessageWidget({ @@ -39,69 +91,8 @@ class ReplyMessageWidget extends StatelessWidget { this.reverse = false, this.showBorder = false, this.textLimit = 170, - }) : _attachmentBuilders = { - 'image': (attachment) { - return ImageAttachment( - attachment: attachment, - message: message, - messageTheme: messageTheme, - size: Size(32, 32), - ); - }, - 'video': (attachment) { - final size = Size(32, 32); - if (attachment.thumbUrl != null) { - return Container( - height: size.height, - width: size.width, - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: CachedNetworkImageProvider( - attachment.thumbUrl, - ), - ), - ), - ); - } - return AttachmentError( - attachment: attachment, - size: size, - ); - }, - 'giphy': (attachment) { - final size = Size(32, 32); - return CachedNetworkImage( - height: size?.height, - width: size?.width, - placeholder: (_, __) { - return Container( - width: size?.width, - height: size?.height, - child: Center( - child: CircularProgressIndicator(), - ), - ); - }, - imageUrl: attachment.thumbUrl ?? - attachment.imageUrl ?? - attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - size: size, - ), - fit: BoxFit.cover, - ); - }, - 'file': (attachment) { - return Container( - height: 32, - width: 32, - child: getFileTypeImage(attachment.extraData['mime_type']), - ); - }, - }, - super(key: key); + this.attachmentThumbnailBuilders, + }) : super(key: key); bool get _hasAttachments => message.attachments?.isNotEmpty == true; @@ -215,18 +206,26 @@ class ReplyMessageWidget extends StatelessWidget { ); child = _buildUrlAttachment(attachment); } else { + ReplyMessageAttachmentThumbnailBuilder attachmentBuilder; attachment = message.attachments.last; - final attachmentBuilder = _attachmentBuilders[attachment.type]; + if (attachmentThumbnailBuilders?.containsKey(attachment?.type) == true) { + attachmentBuilder = attachmentThumbnailBuilders[attachment?.type]; + } + attachmentBuilder = _defaultAttachmentBuilder[attachment?.type]; if (attachmentBuilder == null) { child = Offstage(); } - child = attachmentBuilder(attachment); + child = attachmentBuilder(context, attachment); } - return Material( - clipBehavior: Clip.hardEdge, - color: Colors.transparent, - shape: attachment.type == 'file' ? null : _getDefaultShape(context), - child: child, + return Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: Material( + clipBehavior: Clip.hardEdge, + color: Colors.transparent, + shape: attachment.type == 'file' ? null : _getDefaultShape(context), + child: child, + ), ); } @@ -259,6 +258,56 @@ class ReplyMessageWidget extends StatelessWidget { ); } + Map + get _defaultAttachmentBuilder { + return { + 'image': (_, attachment) { + return ImageAttachment( + attachment: attachment, + message: message, + messageTheme: messageTheme, + size: Size(32, 32), + ); + }, + 'video': (_, attachment) { + return _VideoAttachmentThumbnail( + key: ValueKey(attachment.assetUrl), + attachment: attachment, + ); + }, + 'giphy': (_, attachment) { + final size = Size(32, 32); + return CachedNetworkImage( + height: size?.height, + width: size?.width, + placeholder: (_, __) { + return Container( + width: size?.width, + height: size?.height, + child: Center( + child: CircularProgressIndicator(), + ), + ); + }, + imageUrl: + attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + size: size, + ), + fit: BoxFit.cover, + ); + }, + 'file': (_, attachment) { + return Container( + height: 32, + width: 32, + child: getFileTypeImage(attachment.extraData['mime_type']), + ); + }, + }; + } + Color _getBackgroundColor() { if (_containsScrapeUrl) { return Color(0xFFE9F2FF); From a3e2eec345457679a981ffb4c496447f17b35fdc Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 9 Dec 2020 17:09:19 +0530 Subject: [PATCH 10/30] [MessageWidget] Fix widget gesture detector Signed-off-by: Sahil Kumar --- lib/src/message_widget.dart | 165 ++++++++++++++++++------------------ 1 file changed, 81 insertions(+), 84 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index ff0d1c7c..2ab87fbd 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -317,58 +317,64 @@ class _MessageWidgetState extends State { messageTheme: widget.messageTheme, ), ) - : Material( - clipBehavior: Clip.antiAlias, - shape: widget.shape ?? - RoundedRectangleBorder( - side: isOnlyEmoji && - !_hasReplyMessage - ? BorderSide.none - : widget.borderSide ?? - BorderSide( - color: Theme.of(context) - .brightness == - Brightness - .dark - ? Colors.white - .withAlpha(24) - : Colors.black - .withAlpha( - 24), - ), - borderRadius: widget - .borderRadiusGeometry ?? - BorderRadius.zero, + : GestureDetector( + onTap: () => retryMessage(context), + onLongPress: () => + onLongPress(context), + child: Material( + clipBehavior: Clip.antiAlias, + shape: widget.shape ?? + RoundedRectangleBorder( + side: isOnlyEmoji && + !_hasReplyMessage + ? BorderSide.none + : widget.borderSide ?? + BorderSide( + color: Theme.of(context) + .brightness == + Brightness + .dark + ? Colors.white + .withAlpha( + 24) + : Colors.black + .withAlpha( + 24), + ), + borderRadius: widget + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Padding( + padding: EdgeInsets.all( + hasFiles ? 2.0 : 0.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (_hasReplyMessage) + ReplyMessageWidget( + message: + widget.replyMessage, + messageTheme: isMyMessage + ? StreamChatTheme.of( + context) + .otherMessageTheme + : StreamChatTheme.of( + context) + .ownMessageTheme, + reverse: widget.reverse, + ), + ..._parseAttachments(context), + if (widget.message.text + .trim() + .isNotEmpty && + !isGiphy) + _buildTextBubble(context), + ], ), - color: _getBackgroundColor(), - child: Padding( - padding: EdgeInsets.all( - hasFiles ? 2.0 : 0.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (_hasReplyMessage) - ReplyMessageWidget( - message: - widget.replyMessage, - messageTheme: isMyMessage - ? StreamChatTheme.of( - context) - .otherMessageTheme - : StreamChatTheme.of( - context) - .ownMessageTheme, - reverse: widget.reverse, - ), - ..._parseAttachments(context), - if (widget.message.text - .trim() - .isNotEmpty && - !isGiphy) - _buildTextBubble(context), - ], ), ), ), @@ -706,33 +712,29 @@ class _MessageWidgetState extends State { }) { final attachmentShape = widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); - return GestureDetector( - onTap: () => retryMessage(context), - onLongPress: () => onLongPress(context), - child: Material( - color: _getBackgroundColor(), - clipBehavior: Clip.antiAlias, - shape: attachmentShape, - child: Padding( - padding: widget.attachmentPadding, - child: Material( - clipBehavior: Clip.hardEdge, - shape: attachmentShape, - type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - getFailedMessageWidget( - context, - padding: const EdgeInsets.all(8.0), - ), - attachmentWidget, - ], - ), + return Material( + color: _getBackgroundColor(), + clipBehavior: Clip.antiAlias, + shape: attachmentShape, + child: Padding( + padding: widget.attachmentPadding, + child: Material( + clipBehavior: Clip.hardEdge, + shape: attachmentShape, + type: MaterialType.transparency, + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + getFailedMessageWidget( + context, + padding: const EdgeInsets.all(8.0), + ), + attachmentWidget, + ], ), ), ), @@ -862,7 +864,7 @@ class _MessageWidgetState extends State { } Widget _buildTextBubble(BuildContext context) { - Widget child = Transform( + return Transform( transform: Matrix4.rotationY(widget.reverse ? pi : 0), alignment: Alignment.center, child: Column( @@ -899,11 +901,6 @@ class _MessageWidgetState extends State { ], ), ); - return GestureDetector( - onTap: () => retryMessage(context), - onLongPress: () => onLongPress(context), - child: child, - ); } bool get isOnlyEmoji => From 109b1a4e30f7ccac066074da7ac98371f8c1941d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 23 Dec 2020 17:45:55 +0530 Subject: [PATCH 11/30] minor fix Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index cce0cc36..cac699d9 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -411,7 +411,7 @@ class _MessageListViewState extends State { if (widget.messageBuilder != null) { messageWidget = Builder( key: ValueKey('MESSAGE-${message.id}'), - builder: (_) => widget.messageBuilder( + builder: (context) => widget.messageBuilder( context, MessageDetails( context, From 47dafef2aca8ff793601f20ccb3295e33921beef Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 23 Dec 2020 18:04:38 +0530 Subject: [PATCH 12/30] flutter format Signed-off-by: Sahil Kumar --- lib/src/extension.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/extension.dart b/lib/src/extension.dart index 22fb326a..f49c574f 100644 --- a/lib/src/extension.dart +++ b/lib/src/extension.dart @@ -8,7 +8,7 @@ extension StringExtension on String { extension ListX on List { /// Insert any item inBetween the list items List insertBetween(T item) => expand((e) sync* { - yield item; - yield e; - }).skip(1).toList(growable: false); + yield item; + yield e; + }).skip(1).toList(growable: false); } From db7593b6223934bb16aa380c9b8a00e79ff52e59 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 23 Dec 2020 19:03:28 +0530 Subject: [PATCH 13/30] [Message Input] fix icons padding Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 151 ++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 79 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 1a7dfa2c..5d6c16e8 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -248,7 +248,7 @@ class MessageInputState extends State { ], ), Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), + padding: const EdgeInsets.all(8.0), child: _buildTextField(context), ), if (widget.parentMessage != null) @@ -367,17 +367,23 @@ class MessageInputState extends State { return AnimatedCrossFade( crossFadeState: _actionsShrunk ? CrossFadeState.showFirst : CrossFadeState.showSecond, - firstChild: IconButton( - onPressed: () { - setState(() { - _actionsShrunk = false; - }); - }, - visualDensity: VisualDensity.compact, - splashRadius: 24, - padding: const EdgeInsets.all(0), - icon: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context).accentColor, + firstChild: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: IconButton( + onPressed: () { + setState(() { + _actionsShrunk = false; + }); + }, + icon: StreamSvgIcon.emptyCircleLeft( + color: StreamChatTheme.of(context).accentColor, + ), + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, ), ), secondChild: Row( @@ -456,7 +462,7 @@ class MessageInputState extends State { StreamChatTheme.of(context).accentColor, padding: EdgeInsets.zero, labelPadding: - EdgeInsets.symmetric(horizontal: 9.0), + EdgeInsets.symmetric(horizontal: 8.0), label: Row( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, @@ -466,7 +472,7 @@ class MessageInputState extends State { size: 16.0, ), Text( - _chosenCommand?.name?.toUpperCase() ?? "", + _chosenCommand?.name?.toUpperCase() ?? '', style: TextStyle( color: Colors.white, fontSize: 12.0), ), @@ -478,17 +484,12 @@ class MessageInputState extends State { suffixIcon: _commandEnabled ? IconButton( icon: StreamSvgIcon.close_small(), + splashRadius: 24, onPressed: () { - setState(() { - _commandEnabled = false; - }); + setState(() => _commandEnabled = false); }, ) : null, - suffixIconConstraints: BoxConstraints( - maxHeight: 24.0, - maxWidth: 40.0, - ), ), textCapitalization: TextCapitalization.sentences, ), @@ -1548,7 +1549,7 @@ class MessageInputState extends State { Widget _buildCommandButton() { return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), child: IconButton( icon: StreamSvgIcon.lightning( color: _commandsOverlay != null @@ -1588,42 +1589,38 @@ class MessageInputState extends State { } Widget _buildAttachmentButton() { - var padding = widget.editMessage == null ? 4.0 : 8.0; - return Center( - child: Padding( - padding: - EdgeInsets.only(left: 8.0, right: padding, top: 8.0, bottom: 8.0), - child: IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? StreamChatTheme.of(context).accentColor - : Color(0xFF000000).withAlpha(128), - ), - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? StreamChatTheme.of(context).accentColor + : Color(0xFF000000).withAlpha(128), ), + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, ), ); } @@ -1924,33 +1921,29 @@ class MessageInputState extends State { Widget _buildIdleSendButton(BuildContext context) { return Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: StreamSvgIcon( - assetName: _getIdleSendIcon(), - color: Colors.grey, - ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: StreamSvgIcon( + assetName: _getIdleSendIcon(), + color: Colors.grey, ), ); } Widget _buildSendButton(BuildContext context) { - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: IconButton( - onPressed: sendMessage, - visualDensity: VisualDensity.compact, - padding: const EdgeInsets.all(0), - splashRadius: 24, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - icon: StreamSvgIcon( - assetName: _getSendIcon(), - color: StreamChatTheme.of(context).accentColor, - ), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), + child: IconButton( + onPressed: sendMessage, + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + icon: StreamSvgIcon( + assetName: _getSendIcon(), + color: StreamChatTheme.of(context).accentColor, ), ), ); From 46c9858bd447fe1db44eb05b7a2571e7ce04dce0 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Dec 2020 19:08:50 +0530 Subject: [PATCH 14/30] [Message Input] Add reply_to_message_id in case of threadless reply Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 38 ++++++++++++++++++------------- lib/src/message_widget.dart | 19 +++++++--------- lib/src/reply_message_widget.dart | 4 ++-- pubspec.yaml | 3 ++- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 5d6c16e8..38814c2a 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -110,7 +110,7 @@ class MessageInput extends StatefulWidget { this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilders, this.focusNode, - this.replyMessage, + this.replyToMessage, }) : super(key: key); /// Message to edit @@ -160,7 +160,7 @@ class MessageInput extends StatefulWidget { final FocusNode focusNode; /// - final Message replyMessage; + final Message replyToMessage; @override MessageInputState createState() => MessageInputState(); @@ -203,9 +203,9 @@ class MessageInputState extends State { /// The editing controller passed to the input TextField TextEditingController textEditingController; - Message _replyMessage; + Message _replyToMessage; - bool get _hasReplyMessage => _replyMessage != null; + bool get _hasReplyToMessage => _replyToMessage != null; @override Widget build(BuildContext context) { @@ -224,7 +224,7 @@ class MessageInputState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (_hasReplyMessage) + if (_hasReplyToMessage) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -242,7 +242,7 @@ class MessageInputState extends State { visualDensity: VisualDensity.compact, icon: StreamSvgIcon.close_small(), onPressed: () { - setState(() => _replyMessage = null); + setState(() => _replyToMessage = null); }, ), ], @@ -417,7 +417,7 @@ class MessageInputState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildRepyToMessage(), + _buildReplyToMessage(), _buildAttachments(), LimitedBox( maxHeight: widget.maxHeight, @@ -1321,20 +1321,20 @@ class MessageInputState extends State { _commandsOverlay = null; } - Widget _buildRepyToMessage() { - if (!_hasReplyMessage) { + Widget _buildReplyToMessage() { + if (!_hasReplyToMessage) { return Offstage(); } - final containsUrl = _replyMessage.attachments + final containsUrl = _replyToMessage.attachments ?.any((element) => element.ogScrapeUrl != null) == true; return Transform( transform: Matrix4.rotationY(pi), alignment: Alignment.center, - child: ReplyMessageWidget( + child: ReplyToMessageWidget( reverse: true, showBorder: !containsUrl, - message: _replyMessage, + message: _replyToMessage, messageTheme: StreamChatTheme.of(context).otherMessageTheme, ), ); @@ -1982,7 +1982,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); - _replyMessage = null; + _replyToMessage = null; setState(() { _messageIsPresent = false; @@ -2014,6 +2014,12 @@ class MessageInputState extends State { ); } + if (widget.replyToMessage != null) { + message = message.copyWith( + replyToMessageId: widget.replyToMessage.id, + ); + } + if (widget.preMessageSending != null) { message = await widget.preMessageSending(message); } @@ -2057,7 +2063,7 @@ class MessageInputState extends State { void initState() { super.initState(); - _replyMessage = widget.replyMessage; + _replyToMessage = widget.replyToMessage; _focusNode = widget.focusNode ?? FocusNode(); @@ -2124,8 +2130,8 @@ class MessageInputState extends State { @override void didUpdateWidget(MessageInput oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.replyMessage?.id != _replyMessage?.id) { - _replyMessage = widget.replyMessage; + if (widget.replyToMessage?.id != _replyToMessage?.id) { + _replyToMessage = widget.replyToMessage; } } } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 7ff4ff6e..affcc351 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -58,9 +58,6 @@ class MessageWidget extends StatefulWidget { /// The message final Message message; - /// The replyMessage - final Message replyMessage; - /// The message theme final MessageTheme messageTheme; @@ -140,7 +137,6 @@ class MessageWidget extends StatefulWidget { Key key, @required this.message, @required this.messageTheme, - this.replyMessage, this.reverse = false, this.translateUserAvatar = true, this.shape, @@ -241,7 +237,7 @@ class _MessageWidgetState extends State { bool get showInChannel => widget.showInChannelIndicator && widget.message?.showInChannel == true; - bool get _hasReplyMessage => widget.replyMessage != null; + bool get _hasReplyToMessage => widget.message?.replyToMessage != null; @override Widget build(BuildContext context) { @@ -352,7 +348,7 @@ class _MessageWidgetState extends State { shape: widget.shape ?? RoundedRectangleBorder( side: isOnlyEmoji && - !_hasReplyMessage + !_hasReplyToMessage ? BorderSide.none : widget.borderSide ?? BorderSide( @@ -383,10 +379,11 @@ class _MessageWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ - if (_hasReplyMessage) - ReplyMessageWidget( + if (_hasReplyToMessage) + ReplyToMessageWidget( message: widget - .replyMessage, + .message + .replyToMessage, messageTheme: isMyMessage ? StreamChatTheme.of( context) @@ -941,7 +938,7 @@ class _MessageWidgetState extends State { if (widget.message.attachments ?.any((element) => element.ogScrapeUrl != null) == true && - !_hasReplyMessage) + !_hasReplyToMessage) _buildUrlAttachment(), ], ), @@ -953,7 +950,7 @@ class _MessageWidgetState extends State { widget.message.text.characters.every((c) => Emoji.byChar(c) != null); Color _getBackgroundColor() { - if (_hasReplyMessage) { + if (_hasReplyToMessage) { return widget.messageTheme.messageBackgroundColor; } diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index 56f3112f..c335906b 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -63,7 +63,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { } /// -class ReplyMessageWidget extends StatelessWidget { +class ReplyToMessageWidget extends StatelessWidget { /// The message final Message message; @@ -84,7 +84,7 @@ class ReplyMessageWidget extends StatelessWidget { attachmentThumbnailBuilders; /// - ReplyMessageWidget({ + ReplyToMessageWidget({ Key key, @required this.message, @required this.messageTheme, diff --git a/pubspec.yaml b/pubspec.yaml index 12374291..1c99b03b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,8 @@ dependencies: file_picker: ^2.0.12 image_picker: ^0.6.7+2 flutter_keyboard_visibility: ^3.3.0 - stream_chat: ^0.2.20 + stream_chat: + path: ../stream-chat-dart mime: ^0.9.6+3 video_compress: ^2.1.1 visibility_detector: ^0.1.5 From a27efad01bdb317a20a7448bf232472a9beb2135 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 28 Dec 2020 19:09:16 +0530 Subject: [PATCH 15/30] testing changes, remove them when pushed Signed-off-by: Sahil Kumar --- example/lib/choose_user_page.dart | 4 ++-- example/lib/main.dart | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index a64013b9..22582be9 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -13,13 +13,13 @@ import 'routes/routes.dart'; const kStreamApiKey = 'STREAM_API_KEY'; const kStreamUserId = 'STREAM_USER_ID'; const kStreamToken = 'STREAM_TOKEN'; -const kDefaultStreamApiKey = 'uj7qrdbfrzvg'; +const kDefaultStreamApiKey = 'hg4yvnb2g6pu'; class ChooseUserPage extends StatelessWidget { @override Widget build(BuildContext context) { final users = { - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.lCz-idDgaZ-xszjnuB_hTfeIOhTFmJtTB2fEjhwrcCI': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.va4qzf2KTQctOJfqdtzcdETAMKgazshuJnYJCPmmaKY': User( id: 'vishal', extraData: { diff --git a/example/lib/main.dart b/example/lib/main.dart index 0fec35e3..7ef4a7d7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -24,6 +24,7 @@ void main() async { final client = Client( apiKey ?? kDefaultStreamApiKey, + baseURL: 'https://chat-proxy-oregon.stream-io-api.com', logLevel: Level.INFO, showLocalNotification: (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, @@ -477,7 +478,7 @@ class _ChannelPageState extends State { ), MessageInput( focusNode: _focusNode, - replyMessage: _replyMessage, + replyToMessage: _replyMessage, ), ], ), From 328afc44d8d68ad201e80d899b4845679f99ae62 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 31 Dec 2020 18:53:33 +0530 Subject: [PATCH 16/30] merge fixes Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 5 +++-- lib/src/message_list_view.dart | 2 +- lib/src/reply_message_widget.dart | 14 +++++++------- lib/src/swipeable.dart | 6 +++++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index c9496a45..d014e390 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -231,7 +231,8 @@ class MessageInputState extends State { Padding( padding: const EdgeInsets.all(8.0), child: StreamSvgIcon.reply( - color: Colors.black.withOpacity(0.2), + color: + StreamChatTheme.of(context).colorTheme.greyGainsboro, ), ), Text( @@ -263,7 +264,7 @@ class MessageInputState extends State { ); if (widget.editMessage == null) { child = Material( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, elevation: 8, child: child, ); diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index f5545e50..14a49255 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -825,7 +825,7 @@ class _MessageListViewState extends State { child = Swipeable( onSwipeEnd: () => widget.onMessageSwiped(message), backgroundIcon: StreamSvgIcon.reply( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), child: child, ); diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index c335906b..1a4348f3 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -131,9 +131,11 @@ class ReplyToMessageWidget extends StatelessWidget { minHeight: 48.0, ), decoration: BoxDecoration( - color: _getBackgroundColor(), + color: _getBackgroundColor(context), border: showBorder - ? Border.all(color: Colors.black.withOpacity(0.08)) + ? Border.all( + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ) : null, borderRadius: BorderRadius.only( topRight: Radius.circular(12), @@ -232,9 +234,7 @@ class ReplyToMessageWidget extends StatelessWidget { ShapeBorder _getDefaultShape(BuildContext context) { return RoundedRectangleBorder( side: BorderSide( - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withAlpha(24) - : Colors.black.withAlpha(24), + color: StreamChatTheme.of(context).colorTheme.greyWhisper, ), borderRadius: BorderRadius.circular(8), ); @@ -308,9 +308,9 @@ class ReplyToMessageWidget extends StatelessWidget { }; } - Color _getBackgroundColor() { + Color _getBackgroundColor(BuildContext context) { if (_containsScrapeUrl) { - return Color(0xFFE9F2FF); + return StreamChatTheme.of(context).colorTheme.blueAlice; } return messageTheme.messageBackgroundColor; } diff --git a/lib/src/swipeable.dart b/lib/src/swipeable.dart index c2279e63..8280bf24 100644 --- a/lib/src/swipeable.dart +++ b/lib/src/swipeable.dart @@ -2,6 +2,8 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'stream_chat_theme.dart'; + /// class Swipeable extends StatefulWidget { final Widget child; @@ -139,7 +141,9 @@ class _SwipeableState extends State with TickerProviderStateMixin { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: Colors.black.withOpacity(0.08), + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, ), ), child: widget.backgroundIcon, From eb16e15665ce6d15b3cd7f321f0f87be51bef63c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 31 Dec 2020 18:58:56 +0530 Subject: [PATCH 17/30] flutter format Signed-off-by: Sahil Kumar --- lib/src/message_actions_modal.dart | 2 +- lib/src/message_widget.dart | 193 +++++++++++++++-------------- 2 files changed, 100 insertions(+), 95 deletions(-) diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index a57096db..050b5995 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -172,7 +172,7 @@ class MessageActionsModal extends StatelessWidget { _buildReplyButton(context), if (showThreadReply && (message.status == - MessageSendingStatus.SENT || + MessageSendingStatus.SENT || message.status == null) && message.parentId == null) _buildThreadReplyButton(context), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index b4472261..a344068f 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -338,90 +338,94 @@ class _MessageWidgetState extends State { : 0, ) : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: DeletedMessage( - reverse: widget.reverse, - borderRadiusGeometry: - widget.borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : GestureDetector( - onLongPress: () => - onLongPress(context), - child: Material( - clipBehavior: Clip.antiAlias, - shape: widget.shape ?? - RoundedRectangleBorder( - side: isOnlyEmoji - ? BorderSide.none - : widget.borderSide ?? - BorderSide( - color: Theme.of(context) - .brightness == - Brightness - .dark + child: + (widget.message.isDeleted && + !isFailedState) + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + widget.reverse ? pi : 0), + child: DeletedMessage( + reverse: widget.reverse, + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: + widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : GestureDetector( + onLongPress: () => + onLongPress(context), + child: Material( + clipBehavior: + Clip.antiAlias, + shape: widget.shape ?? + RoundedRectangleBorder( + side: isOnlyEmoji + ? BorderSide.none + : widget.borderSide ?? + BorderSide( + color: Theme.of(context).brightness == + Brightness + .dark + ? StreamChatTheme.of(context) + .colorTheme + .white + .withAlpha( + 24) + : StreamChatTheme.of(context) + .colorTheme + .black + .withAlpha(24), + ), + borderRadius: widget + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: + _getBackgroundColor(), + child: Padding( + padding: EdgeInsets.all( + hasFiles ? 2.0 : 0.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + mainAxisSize: + MainAxisSize.min, + children: [ + if (_hasReplyToMessage) + ReplyToMessageWidget( + message: widget + .message + .replyToMessage, + messageTheme: isMyMessage ? StreamChatTheme.of( context) - .colorTheme - .white - .withAlpha( - 24) + .otherMessageTheme : StreamChatTheme.of( context) - .colorTheme - .black - .withAlpha( - 24), + .ownMessageTheme, + reverse: widget + .reverse, ), - borderRadius: widget - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Padding( - padding: EdgeInsets.all( - hasFiles ? 2.0 : 0.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisSize: - MainAxisSize.min, - children: [ - if (_hasReplyToMessage) - ReplyToMessageWidget( - message: widget - .message - .replyToMessage, - messageTheme: isMyMessage - ? StreamChatTheme.of( - context) - .otherMessageTheme - : StreamChatTheme.of( - context) - .ownMessageTheme, - reverse: widget - .reverse, + ..._parseAttachments( + context), + if (widget + .message.text + .trim() + .isNotEmpty && + !isGiphy) + _buildTextBubble( + context), + ], ), - ..._parseAttachments( - context), - if (widget.message.text - .trim() - .isNotEmpty && - !isGiphy) - _buildTextBubble(context), - ], + ), + ), ), - ), - ),), ), if (widget.showReactionPickerIndicator) Positioned( @@ -692,9 +696,10 @@ class _MessageWidgetState extends State { onReplyTap: widget.onReplyTap, onThreadReplyTap: widget.onThreadTap, showResendMessage: - widget.showResendMessage && (isSendFailed || isUpdateFailed), + widget.showResendMessage && (isSendFailed || isUpdateFailed), showCopyMessage: !isFailedState && - widget.message.text?.trim()?.isNotEmpty == true, showEditMessage: widget.showEditMessage && + widget.message.text?.trim()?.isNotEmpty == true, + showEditMessage: widget.showEditMessage && !isDeleteFailed && widget.message.attachments ?.any((element) => element.type == 'giphy') != @@ -704,7 +709,7 @@ class _MessageWidgetState extends State { !isFailedState && widget.onReplyTap != null, showThreadReply: - widget.showThreadReplyIndicator && widget.onThreadTap != null, + widget.showThreadReplyIndicator && widget.onThreadTap != null, ), ); }); @@ -807,22 +812,22 @@ class _MessageWidgetState extends State { final attachmentShape = widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); return Material( - color: _getBackgroundColor(), - clipBehavior: Clip.antiAlias, - shape: attachmentShape, - child: Padding( - padding: widget.attachmentPadding, - child: Material( - clipBehavior: Clip.hardEdge, - shape: attachmentShape, - type: MaterialType.transparency, - child: Transform( - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - alignment: Alignment.center, - child: attachmentWidget, - ), + color: _getBackgroundColor(), + clipBehavior: Clip.antiAlias, + shape: attachmentShape, + child: Padding( + padding: widget.attachmentPadding, + child: Material( + clipBehavior: Clip.hardEdge, + shape: attachmentShape, + type: MaterialType.transparency, + child: Transform( + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + alignment: Alignment.center, + child: attachmentWidget, ), ), + ), ); } From a2b3f34a0fd39bae4f49b462eb71c56b8c66ef6a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 12:52:53 +0530 Subject: [PATCH 18/30] [Message Input] Fix replyToMessage cleared bug Signed-off-by: Sahil Kumar --- example/lib/main.dart | 9 ++++++--- lib/src/message_input.dart | 29 +++++++++-------------------- 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 17f497a4..aff84600 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -434,7 +434,7 @@ class ChannelPage extends StatefulWidget { } class _ChannelPageState extends State { - Message _replyMessage; + Message _replyToMessage; FocusNode _focusNode; @override @@ -450,7 +450,7 @@ class _ChannelPageState extends State { } void _reply(Message message) { - setState(() => _replyMessage = message); + setState(() => _replyToMessage = message); _focusNode.requestFocus(); } @@ -502,7 +502,10 @@ class _ChannelPageState extends State { ), MessageInput( focusNode: _focusNode, - replyToMessage: _replyMessage, + replyToMessage: _replyToMessage, + onReplyToMessageCleared: () { + setState(() => _replyToMessage = null); + }, ), ], ), diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index d014e390..95a4e497 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -111,6 +111,7 @@ class MessageInput extends StatefulWidget { this.attachmentThumbnailBuilders, this.focusNode, this.replyToMessage, + this.onReplyToMessageCleared, }) : super(key: key); /// Message to edit @@ -162,6 +163,9 @@ class MessageInput extends StatefulWidget { /// final Message replyToMessage; + /// + final VoidCallback onReplyToMessageCleared; + @override MessageInputState createState() => MessageInputState(); @@ -203,9 +207,7 @@ class MessageInputState extends State { /// The editing controller passed to the input TextField TextEditingController textEditingController; - Message _replyToMessage; - - bool get _hasReplyToMessage => _replyToMessage != null; + bool get _hasReplyToMessage => widget.replyToMessage != null; @override Widget build(BuildContext context) { @@ -242,9 +244,7 @@ class MessageInputState extends State { IconButton( visualDensity: VisualDensity.compact, icon: StreamSvgIcon.close_small(), - onPressed: () { - setState(() => _replyToMessage = null); - }, + onPressed: widget.onReplyToMessageCleared, ), ], ), @@ -1353,7 +1353,7 @@ class MessageInputState extends State { if (!_hasReplyToMessage) { return Offstage(); } - final containsUrl = _replyToMessage.attachments + final containsUrl = widget.replyToMessage.attachments ?.any((element) => element.ogScrapeUrl != null) == true; return Transform( @@ -1362,7 +1362,7 @@ class MessageInputState extends State { child: ReplyToMessageWidget( reverse: true, showBorder: !containsUrl, - message: _replyToMessage, + message: widget.replyToMessage, messageTheme: StreamChatTheme.of(context).otherMessageTheme, ), ); @@ -2015,7 +2015,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); - _replyToMessage = null; + widget.onReplyToMessageCleared(); setState(() { _messageIsPresent = false; @@ -2091,9 +2091,6 @@ class MessageInputState extends State { @override void initState() { super.initState(); - - _replyToMessage = widget.replyToMessage; - _focusNode = widget.focusNode ?? FocusNode(); _emojiNames = Emoji.all().map((e) => e.name); @@ -2155,14 +2152,6 @@ class MessageInputState extends State { } super.didChangeDependencies(); } - - @override - void didUpdateWidget(MessageInput oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.replyToMessage?.id != _replyToMessage?.id) { - _replyToMessage = widget.replyToMessage; - } - } } class _SendingAttachment { From 380b9373a464ce574b60b62d48a65e4667363b43 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 12:53:21 +0530 Subject: [PATCH 19/30] minor improvements Signed-off-by: Sahil Kumar --- lib/src/lazy_load_scroll_view.dart | 2 +- lib/src/media_list_view.dart | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/lazy_load_scroll_view.dart b/lib/src/lazy_load_scroll_view.dart index 2bc82a55..bc1f4d1b 100644 --- a/lib/src/lazy_load_scroll_view.dart +++ b/lib/src/lazy_load_scroll_view.dart @@ -31,7 +31,7 @@ class LazyLoadScrollView extends StatefulWidget { final bool isLoading; /// Initiates a LazyLoadScrollView widget - LazyLoadScrollView({ + const LazyLoadScrollView({ Key key, @required this.child, this.onStartOfPage, diff --git a/lib/src/media_list_view.dart b/lib/src/media_list_view.dart index 08ebfcc7..4a954857 100644 --- a/lib/src/media_list_view.dart +++ b/lib/src/media_list_view.dart @@ -29,6 +29,7 @@ class MediaListView extends StatefulWidget { this.selectedIds = const [], this.onSelect, }) : super(key: key); + @override _MediaListViewState createState() => _MediaListViewState(); } @@ -42,9 +43,7 @@ class _MediaListViewState extends State { @override Widget build(BuildContext context) { return LazyLoadScrollView( - onEndOfPage: () { - _getMedia(); - }, + onEndOfPage: () async => _getMedia(), child: GridView.builder( itemCount: _media.length, controller: _scrollController, From 8fa8c9dc6b52b73b655a113363e4a1947a27d8ea Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 13:48:29 +0530 Subject: [PATCH 20/30] Rename replyToMessage -> quotedMessage Signed-off-by: Sahil Kumar --- example/lib/main.dart | 10 +++++----- lib/src/message_input.dart | 28 ++++++++++++++-------------- lib/src/message_widget.dart | 12 ++++++------ lib/src/reply_message_widget.dart | 4 ++-- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index aff84600..70cd513a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -434,7 +434,7 @@ class ChannelPage extends StatefulWidget { } class _ChannelPageState extends State { - Message _replyToMessage; + Message _quotedMessage; FocusNode _focusNode; @override @@ -450,7 +450,7 @@ class _ChannelPageState extends State { } void _reply(Message message) { - setState(() => _replyToMessage = message); + setState(() => _quotedMessage = message); _focusNode.requestFocus(); } @@ -502,9 +502,9 @@ class _ChannelPageState extends State { ), MessageInput( focusNode: _focusNode, - replyToMessage: _replyToMessage, - onReplyToMessageCleared: () { - setState(() => _replyToMessage = null); + quotedMessage: _quotedMessage, + onQuotedMessageCleared: () { + setState(() => _quotedMessage = null); }, ), ], diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 95a4e497..a24bda9d 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -110,8 +110,8 @@ class MessageInput extends StatefulWidget { this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilders, this.focusNode, - this.replyToMessage, - this.onReplyToMessageCleared, + this.quotedMessage, + this.onQuotedMessageCleared, }) : super(key: key); /// Message to edit @@ -161,10 +161,10 @@ class MessageInput extends StatefulWidget { final FocusNode focusNode; /// - final Message replyToMessage; + final Message quotedMessage; /// - final VoidCallback onReplyToMessageCleared; + final VoidCallback onQuotedMessageCleared; @override MessageInputState createState() => MessageInputState(); @@ -207,7 +207,7 @@ class MessageInputState extends State { /// The editing controller passed to the input TextField TextEditingController textEditingController; - bool get _hasReplyToMessage => widget.replyToMessage != null; + bool get _hasQuotedMessage => widget.quotedMessage != null; @override Widget build(BuildContext context) { @@ -226,7 +226,7 @@ class MessageInputState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - if (_hasReplyToMessage) + if (_hasQuotedMessage) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -244,7 +244,7 @@ class MessageInputState extends State { IconButton( visualDensity: VisualDensity.compact, icon: StreamSvgIcon.close_small(), - onPressed: widget.onReplyToMessageCleared, + onPressed: widget.onQuotedMessageCleared, ), ], ), @@ -1350,19 +1350,19 @@ class MessageInputState extends State { } Widget _buildReplyToMessage() { - if (!_hasReplyToMessage) { + if (!_hasQuotedMessage) { return Offstage(); } - final containsUrl = widget.replyToMessage.attachments + final containsUrl = widget.quotedMessage.attachments ?.any((element) => element.ogScrapeUrl != null) == true; return Transform( transform: Matrix4.rotationY(pi), alignment: Alignment.center, - child: ReplyToMessageWidget( + child: QuotedMessageWidget( reverse: true, showBorder: !containsUrl, - message: widget.replyToMessage, + message: widget.quotedMessage, messageTheme: StreamChatTheme.of(context).otherMessageTheme, ), ); @@ -2015,7 +2015,7 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); - widget.onReplyToMessageCleared(); + widget.onQuotedMessageCleared(); setState(() { _messageIsPresent = false; @@ -2047,9 +2047,9 @@ class MessageInputState extends State { ); } - if (widget.replyToMessage != null) { + if (widget.quotedMessage != null) { message = message.copyWith( - replyToMessageId: widget.replyToMessage.id, + quotedMessageId: widget.quotedMessage.id, ); } diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 36364416..c0fea794 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -241,7 +241,7 @@ class _MessageWidgetState extends State { bool get showInChannel => widget.showInChannelIndicator && widget.message?.showInChannel == true; - bool get _hasReplyToMessage => widget.message?.replyToMessage != null; + bool get _hasQuotedMessage => widget.message?.quotedMessage != null; bool get isSendFailed => widget.message.status == MessageSendingStatus.FAILED; @@ -397,11 +397,11 @@ class _MessageWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ - if (_hasReplyToMessage) - ReplyToMessageWidget( + if (_hasQuotedMessage) + QuotedMessageWidget( message: widget .message - .replyToMessage, + .quotedMessage, messageTheme: isMyMessage ? StreamChatTheme.of( context) @@ -919,7 +919,7 @@ class _MessageWidgetState extends State { if (widget.message.attachments ?.any((element) => element.ogScrapeUrl != null) == true && - !_hasReplyToMessage) + !_hasQuotedMessage) _buildUrlAttachment(), ], ), @@ -931,7 +931,7 @@ class _MessageWidgetState extends State { widget.message.text.characters.every((c) => Emoji.byChar(c) != null); Color _getBackgroundColor() { - if (_hasReplyToMessage) { + if (_hasQuotedMessage) { return widget.messageTheme.messageBackgroundColor; } diff --git a/lib/src/reply_message_widget.dart b/lib/src/reply_message_widget.dart index 1a4348f3..40b78cfa 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/reply_message_widget.dart @@ -63,7 +63,7 @@ class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> { } /// -class ReplyToMessageWidget extends StatelessWidget { +class QuotedMessageWidget extends StatelessWidget { /// The message final Message message; @@ -84,7 +84,7 @@ class ReplyToMessageWidget extends StatelessWidget { attachmentThumbnailBuilders; /// - ReplyToMessageWidget({ + QuotedMessageWidget({ Key key, @required this.message, @required this.messageTheme, From 032555831c7e7621710029b1b70e49d2a477ac00 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 13:52:05 +0530 Subject: [PATCH 21/30] Revert "testing changes, remove them when pushed" This reverts commit a27efad0 Signed-off-by: Sahil Kumar --- example/lib/choose_user_page.dart | 4 ++-- example/lib/main.dart | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/example/lib/choose_user_page.dart b/example/lib/choose_user_page.dart index ca28b1b3..55bfa36c 100644 --- a/example/lib/choose_user_page.dart +++ b/example/lib/choose_user_page.dart @@ -11,13 +11,13 @@ import 'routes/routes.dart'; const kStreamApiKey = 'STREAM_API_KEY'; const kStreamUserId = 'STREAM_USER_ID'; const kStreamToken = 'STREAM_TOKEN'; -const kDefaultStreamApiKey = 'hg4yvnb2g6pu'; +const kDefaultStreamApiKey = 'uj7qrdbfrzvg'; class ChooseUserPage extends StatelessWidget { @override Widget build(BuildContext context) { final users = { - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.va4qzf2KTQctOJfqdtzcdETAMKgazshuJnYJCPmmaKY': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.lCz-idDgaZ-xszjnuB_hTfeIOhTFmJtTB2fEjhwrcCI': User( id: 'vishal', extraData: { diff --git a/example/lib/main.dart b/example/lib/main.dart index 70cd513a..f4b183c6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -22,7 +22,6 @@ void main() async { final client = Client( apiKey ?? kDefaultStreamApiKey, - baseURL: 'https://chat-proxy-oregon.stream-io-api.com', logLevel: Level.INFO, showLocalNotification: (!kIsWeb && Platform.isAndroid) ? showLocalNotification : null, From 33810de18f1abc808c483c91b16a791a22859798 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 15:49:24 +0530 Subject: [PATCH 22/30] Refactoring, UI fixes Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 6 +- lib/src/message_widget.dart | 67 +++++++------ ...widget.dart => quoted_message_widget.dart} | 98 ++++++++++--------- 3 files changed, 90 insertions(+), 81 deletions(-) rename lib/src/{reply_message_widget.dart => quoted_message_widget.dart} (83%) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index a24bda9d..72c5f70d 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -25,7 +25,7 @@ import 'package:video_compress/video_compress.dart'; import 'extension.dart'; import '../stream_chat_flutter.dart'; -import 'reply_message_widget.dart'; +import 'quoted_message_widget.dart'; import 'stream_channel.dart'; typedef FileUploader = Future Function(PlatformFile, Channel); @@ -2015,7 +2015,9 @@ class MessageInputState extends State { textEditingController.clear(); _attachments.clear(); - widget.onQuotedMessageCleared(); + if (widget.onQuotedMessageCleared != null) { + widget.onQuotedMessageCleared(); + } setState(() { _messageIsPresent = false; diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index c0fea794..1c49a9ca 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -11,7 +11,7 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/message_actions_modal.dart'; import 'package:stream_chat_flutter/src/message_reactions_modal.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; -import 'package:stream_chat_flutter/src/reply_message_widget.dart'; +import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -356,37 +356,39 @@ class _MessageWidgetState extends State { widget.messageTheme, ), ) - : GestureDetector( - onLongPress: () => - onLongPress(context), - child: Material( - clipBehavior: - Clip.antiAlias, - shape: widget.shape ?? - RoundedRectangleBorder( - side: isOnlyEmoji - ? BorderSide.none - : widget.borderSide ?? - BorderSide( - color: Theme.of(context).brightness == - Brightness - .dark - ? StreamChatTheme.of(context) - .colorTheme - .white - .withAlpha( - 24) - : StreamChatTheme.of(context) - .colorTheme - .black - .withAlpha(24), - ), - borderRadius: widget - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: - _getBackgroundColor(), + : Material( + clipBehavior: Clip.antiAlias, + shape: widget.shape ?? + RoundedRectangleBorder( + side: isOnlyEmoji + ? BorderSide.none + : widget.borderSide ?? + BorderSide( + color: Theme.of(context) + .brightness == + Brightness + .dark + ? StreamChatTheme.of( + context) + .colorTheme + .white + .withAlpha( + 24) + : StreamChatTheme.of( + context) + .colorTheme + .black + .withAlpha( + 24), + ), + borderRadius: widget + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: InkWell( + onLongPress: () => + onLongPress(context), child: Padding( padding: EdgeInsets.all( hasFiles ? 2.0 : 0.0), @@ -399,6 +401,7 @@ class _MessageWidgetState extends State { children: [ if (_hasQuotedMessage) QuotedMessageWidget( + onTap: () {}, message: widget .message .quotedMessage, diff --git a/lib/src/reply_message_widget.dart b/lib/src/quoted_message_widget.dart similarity index 83% rename from lib/src/reply_message_widget.dart rename to lib/src/quoted_message_widget.dart index 40b78cfa..7c5dd512 100644 --- a/lib/src/reply_message_widget.dart +++ b/lib/src/quoted_message_widget.dart @@ -12,8 +12,9 @@ import 'message_text.dart'; import 'stream_chat_theme.dart'; import 'user_avatar.dart'; import 'utils.dart'; +import 'extension.dart'; -typedef ReplyMessageAttachmentThumbnailBuilder = Widget Function( +typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function( BuildContext, Attachment, ); @@ -80,9 +81,11 @@ class QuotedMessageWidget extends StatelessWidget { final int textLimit; /// Map that defines a thumbnail builder for an attachment type - final Map + final Map attachmentThumbnailBuilders; + final GestureTapCallback onTap; + /// QuotedMessageWidget({ Key key, @@ -92,6 +95,7 @@ class QuotedMessageWidget extends StatelessWidget { this.showBorder = false, this.textLimit = 170, this.attachmentThumbnailBuilders, + this.onTap, }) : super(key: key); bool get _hasAttachments => message.attachments?.isNotEmpty == true; @@ -104,32 +108,54 @@ class QuotedMessageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(top: 8, bottom: 6, right: 4, left: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible(child: _buildMessage(context)), - SizedBox(width: 4), - _buildUserAvatar(), - ], + return InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.only(top: 8, bottom: 6, right: 4, left: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: _buildMessage(context)), + SizedBox(width: 4), + _buildUserAvatar(), + ], + ), ), ); } Widget _buildMessage(BuildContext context) { + final isOnlyEmoji = + message.text.characters.every((c) => Emoji.byChar(c) != null); + var msg = _hasAttachments && !_containsText + ? message.copyWith(text: message.attachments.last?.title ?? '') + : message; + if (msg.text.length > textLimit) { + msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...'); + } + final children = [ - if (_hasAttachments) ...[ - _parseAttachments(context), - SizedBox(width: 8), - ], - Flexible(child: _buildTextMessage()), - ]; + if (_hasAttachments) _parseAttachments(context), + if (msg.text.isNotEmpty) + Flexible( + child: Transform( + transform: Matrix4.rotationY(reverse ? pi : 0), + alignment: Alignment.center, + child: MessageText( + message: msg, + messageTheme: isOnlyEmoji && _containsText + ? messageTheme.copyWith( + messageText: messageTheme.messageText.copyWith( + fontSize: 24, + )) + : messageTheme, + ), + ), + ), + ].insertBetween(const SizedBox(width: 8)); + return Container( - constraints: BoxConstraints( - minHeight: 48.0, - ), decoration: BoxDecoration( color: _getBackgroundColor(context), border: showBorder @@ -146,6 +172,7 @@ class QuotedMessageWidget extends StatelessWidget { padding: const EdgeInsets.all(8), child: Row( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: reverse ? MainAxisAlignment.end : MainAxisAlignment.start, children: reverse ? children.reversed.toList() : children, @@ -153,30 +180,6 @@ class QuotedMessageWidget extends StatelessWidget { ); } - Widget _buildTextMessage() { - final isOnlyEmoji = - message.text.characters.every((c) => Emoji.byChar(c) != null); - var msg = _hasAttachments && !_containsText - ? message.copyWith(text: message.attachments.last?.title ?? '') - : message; - if (msg.text.length > textLimit) { - msg = msg.copyWith(text: '${msg.text.substring(0, textLimit - 3)}...'); - } - return Transform( - transform: Matrix4.rotationY(reverse ? pi : 0), - alignment: Alignment.center, - child: MessageText( - message: msg, - messageTheme: isOnlyEmoji && _containsText - ? messageTheme.copyWith( - messageText: messageTheme.messageText.copyWith( - fontSize: 24, - )) - : messageTheme, - ), - ); - } - Widget _buildUrlAttachment(Attachment attachment) { final size = Size(32, 32); if (attachment.thumbUrl != null) { @@ -208,7 +211,7 @@ class QuotedMessageWidget extends StatelessWidget { ); child = _buildUrlAttachment(attachment); } else { - ReplyMessageAttachmentThumbnailBuilder attachmentBuilder; + QuotedMessageAttachmentThumbnailBuilder attachmentBuilder; attachment = message.attachments.last; if (attachmentThumbnailBuilders?.containsKey(attachment?.type) == true) { attachmentBuilder = attachmentThumbnailBuilders[attachment?.type]; @@ -219,6 +222,7 @@ class QuotedMessageWidget extends StatelessWidget { } child = attachmentBuilder(context, attachment); } + child = AbsorbPointer(child: child); return Transform( transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, @@ -258,7 +262,7 @@ class QuotedMessageWidget extends StatelessWidget { ); } - Map + Map get _defaultAttachmentBuilder { return { 'image': (_, attachment) { From fc2da1391598759e3a50a6a45eb616240882926f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 17:51:59 +0530 Subject: [PATCH 23/30] Handle onQuotedMessageTap Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 12 ++- lib/src/message_widget.dart | 172 +++++++++++++++++---------------- 2 files changed, 99 insertions(+), 85 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 7cd68eb3..169d9d45 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -181,7 +181,6 @@ class MessageListView extends StatefulWidget { class _MessageListViewState extends State { ItemScrollController _scrollController; bool _bottomWasVisible = false; - bool _topWasVisible = false; Function _onThreadTap; bool _showScrollToBottom = false; ItemPositionsListener _itemPositionListener; @@ -772,6 +771,17 @@ class _MessageListViewState extends State { bottom: index == 0 ? 30 : (isNextUser ? 2 : 7), top: 3, ), + onQuotedMessageTap: (quotedMessageId) { + if (messages.map((e) => e.id).contains(quotedMessageId)) { + final index = messages.indexWhere((m) => m.id == quotedMessageId); + _scrollController?.scrollTo( + index: index, + duration: const Duration(milliseconds: 350), + ); + } else { + streamChannel.loadChannelAtMessage(quotedMessageId); + } + }, showInChannelIndicator: widget.parentMessage == null, showThreadReplyIndicator: widget.parentMessage == null, showUsername: !isMyMessage && !isNextUser, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 1c49a9ca..3aa608ce 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -20,6 +20,7 @@ import 'message_text.dart'; import 'extension.dart'; typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); +typedef OnQuotedMessageTap = void Function(String); /// The display behaviour of a widget enum DisplayWidget { @@ -135,6 +136,9 @@ class MessageWidget extends StatefulWidget { /// Center user avatar with bottom of the message final bool translateUserAvatar; + /// Function called when quotedMessage is tapped + final OnQuotedMessageTap onQuotedMessageTap; + /// MessageWidget({ Key key, @@ -177,6 +181,7 @@ class MessageWidget extends StatefulWidget { ), this.attachmentPadding = EdgeInsets.zero, this.allRead = false, + this.onQuotedMessageTap, }) : attachmentBuilders = { 'image': (context, message, attachment) { return ImageAttachment( @@ -338,97 +343,81 @@ class _MessageWidgetState extends State { : 0, ) : EdgeInsets.zero, - child: - (widget.message.isDeleted && - !isFailedState) - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: DeletedMessage( - reverse: widget.reverse, - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: - widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Material( - clipBehavior: Clip.antiAlias, - shape: widget.shape ?? - RoundedRectangleBorder( - side: isOnlyEmoji - ? BorderSide.none - : widget.borderSide ?? - BorderSide( - color: Theme.of(context) - .brightness == - Brightness - .dark - ? StreamChatTheme.of( - context) - .colorTheme - .white - .withAlpha( - 24) - : StreamChatTheme.of( - context) - .colorTheme - .black - .withAlpha( - 24), - ), - borderRadius: widget - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: InkWell( - onLongPress: () => - onLongPress(context), - child: Padding( - padding: EdgeInsets.all( - hasFiles ? 2.0 : 0.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - mainAxisSize: - MainAxisSize.min, - children: [ - if (_hasQuotedMessage) - QuotedMessageWidget( - onTap: () {}, - message: widget - .message - .quotedMessage, - messageTheme: isMyMessage + child: (widget.message.isDeleted && + !isFailedState) + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + widget.reverse ? pi : 0), + child: DeletedMessage( + reverse: widget.reverse, + borderRadiusGeometry: + widget.borderRadiusGeometry, + borderSide: widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Material( + clipBehavior: Clip.antiAlias, + shape: widget.shape ?? + RoundedRectangleBorder( + side: isOnlyEmoji + ? BorderSide.none + : widget.borderSide ?? + BorderSide( + color: Theme.of(context) + .brightness == + Brightness + .dark ? StreamChatTheme.of( context) - .otherMessageTheme + .colorTheme + .white + .withAlpha( + 24) : StreamChatTheme.of( context) - .ownMessageTheme, - reverse: widget - .reverse, + .colorTheme + .black + .withAlpha( + 24), ), - ..._parseAttachments( - context), - if (widget - .message.text - .trim() - .isNotEmpty && - !isGiphy) - _buildTextBubble( - context), - ], - ), - ), + borderRadius: widget + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: InkWell( + onLongPress: () => + onLongPress(context), + child: Padding( + padding: EdgeInsets.all( + hasFiles ? 2.0 : 0.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + mainAxisSize: + MainAxisSize.min, + children: [ + if (_hasQuotedMessage) + _buildQuotedMessage( + isMyMessage), + ..._parseAttachments( + context), + if (widget.message.text + .trim() + .isNotEmpty && + !isGiphy) + _buildTextBubble( + context), + ], ), ), + ), + ), ), if (widget.showReactionPickerIndicator) Positioned( @@ -480,6 +469,21 @@ class _MessageWidgetState extends State { ); } + Widget _buildQuotedMessage(bool isMyMessage) { + return QuotedMessageWidget( + onTap: () { + if (widget.onQuotedMessageTap != null) { + widget.onQuotedMessageTap(widget.message.quotedMessageId); + } + }, + message: widget.message.quotedMessage, + messageTheme: isMyMessage + ? StreamChatTheme.of(context).otherMessageTheme + : StreamChatTheme.of(context).ownMessageTheme, + reverse: widget.reverse, + ); + } + Widget _buildBottomRow(double leftPadding) { final deleted = widget.message.isDeleted; var children = []; From 8421faa450a73659ffe42cfdf2ec439fe420d820 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 20:04:59 +0530 Subject: [PATCH 24/30] flutter format Signed-off-by: Sahil Kumar --- lib/src/message_widget.dart | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 5cd0c441..597a60c6 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -400,30 +400,30 @@ class _MessageWidgetState extends State { ), color: _getBackgroundColor(), child: InkWell( - onLongPress: () => - onLongPress(context), - child: Padding( - padding: EdgeInsets.all( - hasFiles ? 2.0 : 0.0), - child: Column( - crossAxisAlignment: - CrossAxisAlignment - .start, - mainAxisSize: - MainAxisSize.min, - children: [ - if (_hasQuotedMessage) - _buildQuotedMessage( - isMyMessage), - ..._parseAttachments( - context), - if (widget.message.text - .trim() - .isNotEmpty && - !isGiphy) - _buildTextBubble( + onLongPress: () => + onLongPress(context), + child: Padding( + padding: EdgeInsets.all( + hasFiles ? 2.0 : 0.0), + child: Column( + crossAxisAlignment: + CrossAxisAlignment + .start, + mainAxisSize: + MainAxisSize.min, + children: [ + if (_hasQuotedMessage) + _buildQuotedMessage( + isMyMessage), + ..._parseAttachments( context), - ], + if (widget.message.text + .trim() + .isNotEmpty && + !isGiphy) + _buildTextBubble( + context), + ], ), ), ), From e2a07bbfa513eb32931ece7b9d42c664f96d1320 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 4 Jan 2021 20:20:19 +0530 Subject: [PATCH 25/30] fix tests Signed-off-by: Sahil Kumar --- test/src/message_reaction_modal_test.dart | 2 +- test/src/reaction_bubble_test.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/src/message_reaction_modal_test.dart b/test/src/message_reaction_modal_test.dart index d1c2a49c..c023b20d 100644 --- a/test/src/message_reaction_modal_test.dart +++ b/test/src/message_reaction_modal_test.dart @@ -74,7 +74,7 @@ void main() { ), latestReactions: [ Reaction( - type: 'thumbs_up', + type: 'like', user: User(id: testUserId), ), Reaction( diff --git a/test/src/reaction_bubble_test.dart b/test/src/reaction_bubble_test.dart index 6518dc94..c0bc8cd1 100644 --- a/test/src/reaction_bubble_test.dart +++ b/test/src/reaction_bubble_test.dart @@ -31,7 +31,7 @@ void main() { ); testWidgets( - 'it should show a thumb up', + 'it should show a like', (WidgetTester tester) async { final client = MockClient(); final clientState = MockClientState(); @@ -50,7 +50,7 @@ void main() { child: ReactionBubble( reactions: [ Reaction( - type: 'thumbs_up', + type: 'like', user: User(id: 'test'), ), ], @@ -86,7 +86,7 @@ void main() { child: ReactionBubble( reactions: [ Reaction( - type: 'thumbs_up', + type: 'like', user: User(id: 'test'), ), Reaction( From ed6213e9386c19091ce50a7a8cf67192a39eec1c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 5 Jan 2021 11:19:51 +0530 Subject: [PATCH 26/30] [MessageListView] fix onQuotedMessageTap Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 15 +- lib/src/message_widget.dart | 309 ++++++++++++++++----------------- 2 files changed, 166 insertions(+), 158 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 4ce150df..1d0d4e47 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -775,15 +775,24 @@ class _MessageListViewState extends State { bottom: index == 0 ? 30 : (isNextUser ? 2 : 7), top: 3, ), - onQuotedMessageTap: (quotedMessageId) { - if (messages.map((e) => e.id).contains(quotedMessageId)) { + onQuotedMessageTap: (quotedMessageId) async { + final scrollToIndex = () { final index = messages.indexWhere((m) => m.id == quotedMessageId); _scrollController?.scrollTo( index: index, duration: const Duration(milliseconds: 350), ); + }; + if (messages.map((e) => e.id).contains(quotedMessageId)) { + scrollToIndex(); } else { - streamChannel.loadChannelAtMessage(quotedMessageId); + streamChannel.loadChannelAtMessage(quotedMessageId).then((_) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (messages.map((e) => e.id).contains(quotedMessageId)) { + scrollToIndex(); + } + }); + }); } }, showInChannelIndicator: widget.parentMessage == null, diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 597a60c6..0c108681 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -287,121 +287,119 @@ class _MessageWidgetState extends State { widget.message.attachments?.any((element) => element.type == 'file') == true; - final isMyMessage = - widget.message.user.id == StreamChat.of(context).user.id; - - return Portal( - child: GestureDetector( - onLongPress: () => onLongPress(context), - child: Padding( - padding: widget.padding ?? EdgeInsets.all(8), - child: Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY(widget.reverse ? pi : 0), - child: FractionallySizedBox( - alignment: Alignment.centerLeft, - widthFactor: 0.75, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( - clipBehavior: Clip.none, - alignment: AlignmentDirectional.bottomStart, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == DisplayWidget.show) - _buildUserAvatar(), - SizedBox(width: 6), - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox( - width: widget.messageTheme.avatarTheme - .constraints.maxWidth + - 8, - ), - Flexible( - child: PortalEntry( - portal: Container( - transform: - Matrix4.translationValues(-16, 2, 0), - child: _buildReactionIndicator(context), - constraints: - BoxConstraints(maxWidth: 22 * 6.0), + return Material( + type: MaterialType.transparency, + child: Portal( + child: InkWell( + onLongPress: () => onLongPress(context), + child: Padding( + padding: widget.padding ?? EdgeInsets.all(8), + child: Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY(widget.reverse ? pi : 0), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: 0.75, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + clipBehavior: Clip.none, + alignment: AlignmentDirectional.bottomStart, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == DisplayWidget.show) + _buildUserAvatar(), + SizedBox(width: 6), + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox( + width: widget.messageTheme.avatarTheme + .constraints.maxWidth + + 8, ), - portalAnchor: Alignment(-1.0, -1.0), - childAnchor: Alignment(1, -1.0), - child: Stack( - clipBehavior: Clip.none, - children: [ - Padding( - padding: widget.showReactions - ? EdgeInsets.only( - top: widget - .message - .reactionCounts - ?.isNotEmpty == - true - ? 18 - : 0, - ) - : EdgeInsets.zero, - child: (widget.message.isDeleted && - !isFailedState) - ? Transform( - alignment: Alignment.center, - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: DeletedMessage( - reverse: widget.reverse, - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Material( - clipBehavior: Clip.antiAlias, - shape: widget.shape ?? - RoundedRectangleBorder( - side: isOnlyEmoji - ? BorderSide.none - : widget.borderSide ?? - BorderSide( - color: Theme.of(context) - .brightness == - Brightness - .dark - ? StreamChatTheme.of( - context) - .colorTheme - .white - .withAlpha( - 24) - : StreamChatTheme.of( - context) - .colorTheme - .black - .withAlpha( - 24), - ), - borderRadius: widget - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: InkWell( - onLongPress: () => - onLongPress(context), + Flexible( + child: PortalEntry( + portal: Container( + transform: + Matrix4.translationValues(-16, 2, 0), + child: _buildReactionIndicator(context), + constraints: + BoxConstraints(maxWidth: 22 * 6.0), + ), + portalAnchor: Alignment(-1.0, -1.0), + childAnchor: Alignment(1, -1.0), + child: Stack( + clipBehavior: Clip.none, + children: [ + Padding( + padding: widget.showReactions + ? EdgeInsets.only( + top: widget + .message + .reactionCounts + ?.isNotEmpty == + true + ? 18 + : 0, + ) + : EdgeInsets.zero, + child: (widget.message.isDeleted && + !isFailedState) + ? Transform( + alignment: Alignment.center, + transform: Matrix4.rotationY( + widget.reverse ? pi : 0), + child: DeletedMessage( + reverse: widget.reverse, + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: + widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0.0, + shape: widget.shape ?? + RoundedRectangleBorder( + side: isOnlyEmoji + ? BorderSide.none + : widget.borderSide ?? + BorderSide( + color: Theme.of(context) + .brightness == + Brightness + .dark + ? StreamChatTheme.of( + context) + .colorTheme + .white + .withAlpha( + 24) + : StreamChatTheme.of( + context) + .colorTheme + .black + .withAlpha( + 24), + ), + borderRadius: widget + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), child: Padding( padding: EdgeInsets.all( hasFiles ? 2.0 : 0.0), @@ -413,8 +411,7 @@ class _MessageWidgetState extends State { MainAxisSize.min, children: [ if (_hasQuotedMessage) - _buildQuotedMessage( - isMyMessage), + _buildQuotedMessage(), ..._parseAttachments( context), if (widget.message.text @@ -427,51 +424,51 @@ class _MessageWidgetState extends State { ), ), ), - ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: 0, - top: -6, - child: Transform( - transform: Matrix4.rotationY( - widget.reverse ? pi : 0), - child: CustomPaint( - painter: ReactionBubblePainter( - widget.messageTheme - .reactionsBackgroundColor, - widget.messageTheme - .reactionsBorderColor, + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: 0, + top: -6, + child: Transform( + transform: Matrix4.rotationY( + widget.reverse ? pi : 0), + child: CustomPaint( + painter: ReactionBubblePainter( + widget.messageTheme + .reactionsBackgroundColor, + widget.messageTheme + .reactionsBorderColor, + ), ), ), ), - ), - ], + ], + ), ), ), - ), - ], - ), - if (showBottomRow) SizedBox(height: 20.0), - ], - ), - if (showBottomRow) _buildBottomRow(leftPadding), - if (isFailedState) - Positioned( - left: widget.reverse ? -3 : null, - right: widget.reverse ? null : -9, - bottom: showBottomRow ? 20 : 0, - child: Container( - decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle, + ], ), - child: StreamSvgIcon.error(size: 20), - ), + if (showBottomRow) SizedBox(height: 20.0), + ], ), - ], - ), - ], + if (showBottomRow) _buildBottomRow(leftPadding), + if (isFailedState) + Positioned( + left: widget.reverse ? -3 : null, + right: widget.reverse ? null : -9, + bottom: showBottomRow ? 20 : 0, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: StreamSvgIcon.error(size: 20), + ), + ), + ], + ), + ], + ), ), ), ), @@ -480,7 +477,9 @@ class _MessageWidgetState extends State { ); } - Widget _buildQuotedMessage(bool isMyMessage) { + Widget _buildQuotedMessage() { + final isMyMessage = + widget.message.user.id == StreamChat.of(context).user.id; return QuotedMessageWidget( onTap: () { if (widget.onQuotedMessageTap != null) { From a317e620a0d4484102234b6f6cca688e37684cef Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 5 Jan 2021 12:06:09 +0530 Subject: [PATCH 27/30] [MessageActionsModal] Fix reaction picker alignment for quotedMessage Signed-off-by: Sahil Kumar --- lib/src/message_actions_modal.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 050b5995..f0e228c9 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -55,17 +55,25 @@ class MessageActionsModal extends StatelessWidget { 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 = - message.text.length * messageTheme.messageText.fontSize * 1.2; + messageTextLength * messageTheme.messageText.fontSize * 1.2; final divFactor = message.attachments?.isNotEmpty == true ? 1 : (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize)); return GestureDetector( behavior: HitTestBehavior.translucent, - onTap: () { - Navigator.pop(context); - }, + onTap: () => Navigator.pop(context), child: Stack( children: [ Positioned.fill( From 61a1eecc52a643310ed879441090decca7606924 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 5 Jan 2021 12:22:19 +0530 Subject: [PATCH 28/30] [MessageWidget] Disable onLongPress for deleted message Signed-off-by: Sahil Kumar --- lib/src/message_widget.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 0c108681..bc6646c5 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -291,7 +291,9 @@ class _MessageWidgetState extends State { type: MaterialType.transparency, child: Portal( child: InkWell( - onLongPress: () => onLongPress(context), + onLongPress: widget.message.isDeleted && !isFailedState + ? null + : () => onLongPress(context), child: Padding( padding: widget.padding ?? EdgeInsets.all(8), child: Transform( From dc801ca91debd627ead0374ae61c05b29422e1b8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 5 Jan 2021 13:02:17 +0530 Subject: [PATCH 29/30] minor ui fixes Signed-off-by: Sahil Kumar --- lib/src/message_actions_modal.dart | 41 ++++++++++------------------- lib/src/message_input.dart | 42 ++++++++++++++++-------------- lib/src/stream_svg_icon.dart | 4 +-- 3 files changed, 39 insertions(+), 48 deletions(-) diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index f0e228c9..6294ef6a 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -313,8 +313,8 @@ class MessageActionsModal extends StatelessWidget { backgroundColor: StreamChatTheme.of(context).colorTheme.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: Radius.circular(32), - topRight: Radius.circular(32), + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), ), builder: (context) { @@ -326,39 +326,26 @@ class MessageActionsModal extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: const EdgeInsets.only( - top: 16.0, - left: 16.0, - right: 16.0, - ), + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - IconButton( - icon: StreamSvgIcon.edit( - size: 22, - color: - StreamChatTheme.of(context).primaryIconTheme.color, + Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.edit( + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, ), - onPressed: () {}, ), Text( - 'Edit message', - style: Theme.of(context) - .textTheme - .headline6 - .copyWith(fontWeight: FontWeight.bold), + 'Edit Message', + style: TextStyle(fontWeight: FontWeight.bold), ), IconButton( - icon: Icon( - Icons.cancel_outlined, - size: 22, - color: - StreamChatTheme.of(context).primaryIconTheme.color, - ), - onPressed: () { - Navigator.of(context).pop(); - }, + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.close_small(), + onPressed: Navigator.of(context).pop, ), ], ), diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 72c5f70d..52d91d45 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -227,26 +227,30 @@ class MessageInputState extends State { mainAxisSize: MainAxisSize.min, children: [ if (_hasQuotedMessage) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: StreamSvgIcon.reply( - color: - StreamChatTheme.of(context).colorTheme.greyGainsboro, + Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.reply( + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + ), ), - ), - Text( - 'Reply to Message', - style: TextStyle(fontWeight: FontWeight.bold), - ), - IconButton( - visualDensity: VisualDensity.compact, - icon: StreamSvgIcon.close_small(), - onPressed: widget.onQuotedMessageCleared, - ), - ], + Text( + 'Reply to Message', + style: TextStyle(fontWeight: FontWeight.bold), + ), + IconButton( + visualDensity: VisualDensity.compact, + icon: StreamSvgIcon.close_small(), + onPressed: widget.onQuotedMessageCleared, + ), + ], + ), ), Padding( padding: const EdgeInsets.all(8.0), diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index d00448bd..e7ef46c3 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -10,9 +10,9 @@ class StreamSvgIcon extends StatelessWidget { const StreamSvgIcon({ this.assetName, - this.width, - this.height, this.color, + this.width = 24, + this.height = 24, }); @override From b8c05d791f8be02356e93cb460dc68e859ad9f1b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 5 Jan 2021 10:28:40 +0100 Subject: [PATCH 30/30] change default scroll physics --- example/ios/Flutter/.last_build_id | 2 +- example/ios/Podfile.lock | 4 ++-- example/pubspec.yaml | 2 +- lib/src/message_list_view.dart | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id index e72273c3..f57e1e1f 100644 --- a/example/ios/Flutter/.last_build_id +++ b/example/ios/Flutter/.last_build_id @@ -1 +1 @@ -13a41b8138c4868054e44b5158f3bdd6 \ No newline at end of file +c770358a10272b4ee1cdc2eb432445f6 \ No newline at end of file diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 2d9d0784..f266b656 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -40,7 +40,7 @@ PODS: - Firebase/Messaging (6.33.0): - Firebase/CoreOnly - FirebaseMessaging (~> 4.7.0) - - firebase_core (0.5.2): + - firebase_core (0.5.3): - Firebase/CoreOnly (~> 6.33.0) - Flutter - firebase_messaging (7.0.3): @@ -258,7 +258,7 @@ SPEC CHECKSUMS: esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 - firebase_core: 350ba329d1641211bc6183a3236893cafdacfea7 + firebase_core: 5d6a02f3d85acd5f8321c2d6d62877626a670659 firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index dbaf61b5..4a0a755a 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.108+111 +version: 1.0.110+113 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 1d0d4e47..68a1633b 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -115,7 +115,7 @@ class MessageListView extends StatefulWidget { this.onThreadTap, this.onReplyTap, this.dateDividerBuilder, - this.scrollPhysics = const AlwaysScrollableScrollPhysics(), + this.scrollPhysics = const ClampingScrollPhysics(), this.initialScrollIndex, this.initialAlignment, this.scrollController,