From 0e1e9a12f97cf1a8c5241711780cdfddd1e6ced7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 26 May 2021 16:39:48 +0530 Subject: [PATCH 01/10] feat: Added pin message functionality --- packages/stream_chat/lib/src/client.dart | 6 ++- .../lib/src/message_actions_modal.dart | 49 ++++++++++++++++++ .../lib/src/message_reactions_modal.dart | 1 + .../lib/src/message_widget.dart | 50 ++++++++++++++++++- .../lib/src/stream_svg_icon.dart | 12 +++++ .../stream_chat_flutter/lib/svgs/icon_pin.svg | 3 ++ 6 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/svgs/icon_pin.svg diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 6e7caa9e..98b05634 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -315,7 +315,7 @@ class StreamChatClient { await connectUser(User(id: userId), newToken); try { - handler.resolve( + return handler.resolve( await httpClient.request( err.requestOptions.path, cancelToken: err.requestOptions.cancelToken, @@ -343,10 +343,12 @@ class StreamChatClient { ), ); } on DioError { - handler.reject(err); + return handler.reject(err); } } } + + return handler.next(err); } LogHandlerFunction _getDefaultLogHandler() { diff --git a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart index 5336ed48..7f2a2430 100644 --- a/packages/stream_chat_flutter/lib/src/message_actions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_actions_modal.dart @@ -27,6 +27,8 @@ class MessageActionsModal extends StatefulWidget { this.showResendMessage = true, this.showThreadReplyMessage = true, this.showFlagButton = true, + this.showPinButton = true, + this.showPinHighlight = false, this.showUserAvatar = DisplayWidget.show, this.editMessageInputBuilder, this.messageShape, @@ -79,6 +81,12 @@ class MessageActionsModal extends StatefulWidget { /// Flag for showing flag action final bool showFlagButton; + /// Flag for showing pin action + final bool showPinButton; + + /// Display Pin Highlight + final bool showPinHighlight; + /// Flag for reversing message final bool reverse; @@ -222,6 +230,7 @@ class _MessageActionsModalState extends State { showSendingIndicator: false, shape: widget.messageShape, attachmentShape: widget.attachmentShape, + showPinHighlight: false, ), ), const SizedBox(height: 8), @@ -258,6 +267,8 @@ class _MessageActionsModalState extends State { _buildCopyButton(context), if (widget.showFlagButton) _buildFlagButton(context), + if (widget.showPinButton) + _buildPinButton(context), if (widget.showDeleteMessage) _buildDeleteButton(context), ...widget.customActions @@ -359,6 +370,21 @@ class _MessageActionsModalState extends State { } } + void _togglePin() async { + final channel = StreamChannel.of(context).channel; + + try { + if (!widget.message.pinned) { + await channel.pinMessage(widget.message); + } else { + await channel.unpinMessage(widget.message); + } + Navigator.pop(context); + } catch (e) { + _showErrorAlert(); + } + } + void _showDeleteDialog() async { setState(() { _showActions = false; @@ -451,6 +477,29 @@ class _MessageActionsModalState extends State { ); } + Widget _buildPinButton(BuildContext context) { + final streamChatThemeData = StreamChatTheme.of(context); + return InkWell( + onTap: _togglePin, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), + child: Row( + children: [ + StreamSvgIcon.pin( + color: streamChatThemeData.primaryIconTheme.color, + size: 24, + ), + const SizedBox(width: 16), + Text( + '${widget.message.pinned ? 'Unpin from' : 'Pin to'} Conversation', + style: streamChatThemeData.textTheme.body, + ), + ], + ), + ), + ); + } + Widget _buildDeleteButton(BuildContext context) { final isDeleteFailed = widget.message.status == MessageSendingStatus.failed_delete; diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index 5f3bc5a4..23522508 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -157,6 +157,7 @@ class MessageReactionsModal extends StatelessWidget { ), showReactionPickerIndicator: showReactions && (message.status == MessageSendingStatus.sent), + showPinHighlight: false, ), ), if (message.latestReactions?.isNotEmpty == true) ...[ diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 97e061ae..3c4aec14 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -83,6 +83,8 @@ class MessageWidget extends StatefulWidget { this.showResendMessage = true, this.showCopyMessage = true, this.showFlagButton = true, + this.showPinButton = true, + this.showPinHighlight = true, this.onUserAvatarTap, this.onLinkTap, this.onMessageActions, @@ -367,6 +369,12 @@ class MessageWidget extends StatefulWidget { /// Show flag action final bool showFlagButton; + /// Show flag action + final bool showPinButton; + + /// Display Pin Highlight + final bool showPinHighlight; + /// Builder for respective attachment types final Map attachmentBuilders; @@ -450,7 +458,12 @@ class _MessageWidgetState extends State widget.showUserAvatar != DisplayWidget.gone ? avatarWidth + 8.5 : 0.5; return Material( - type: MaterialType.transparency, + type: widget.message.pinned && widget.showPinHighlight + ? MaterialType.card + : MaterialType.transparency, + color: widget.message.pinned && widget.showPinHighlight + ? StreamChatTheme.of(context).colorTheme.highlight + : null, child: Portal( child: InkWell( onTap: () { @@ -483,6 +496,10 @@ class _MessageWidgetState extends State : CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + if (widget.message.pinned && + widget.message.pinnedBy != null && + widget.showPinHighlight) + _buildPinnedMessage(widget.message), Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, @@ -918,6 +935,7 @@ class _MessageWidgetState extends State !isFailedState && widget.onThreadTap != null, showFlagButton: widget.showFlagButton, + showPinButton: widget.showPinButton, customActions: widget.customActions, ), )); @@ -1113,8 +1131,38 @@ class _MessageWidgetState extends State ); } + Widget _buildPinnedMessage(Message message) { + final pinnedBy = message.pinnedBy; + final pinnedByMe = StreamChat.of(context).user!.id == pinnedBy!.id; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + StreamSvgIcon.pin( + size: 16, + ), + const SizedBox( + width: 4, + ), + Text( + 'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.grey, + fontSize: 13, + fontWeight: FontWeight.w400, + ), + ) + ], + ), + ); + } + bool get isOnlyEmoji => widget.message.text!.isOnlyEmoji; + bool get isPinned => widget.message.pinned; + Color? _getBackgroundColor() { if (hasQuotedMessage) { return widget.messageTheme.messageBackgroundColor; diff --git a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart index 78dfba2c..ae344a89 100644 --- a/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart +++ b/packages/stream_chat_flutter/lib/src/stream_svg_icon.dart @@ -889,6 +889,18 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); + /// [StreamSvgIcon] type + factory StreamSvgIcon.pin({ + double? size, + Color? color, + }) => + StreamSvgIcon( + assetName: 'icon_pin.svg', + color: color, + width: size, + height: size, + ); + /// Name of icon asset final String? assetName; diff --git a/packages/stream_chat_flutter/lib/svgs/icon_pin.svg b/packages/stream_chat_flutter/lib/svgs/icon_pin.svg new file mode 100644 index 00000000..0f494729 --- /dev/null +++ b/packages/stream_chat_flutter/lib/svgs/icon_pin.svg @@ -0,0 +1,3 @@ + + + From 259f717ec824dace08a7ffc955cec5cd169ca867 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 27 May 2021 14:03:46 +0530 Subject: [PATCH 02/10] feat: Added pin permissions list --- .../lib/src/message_list_view.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index e07d9358..03c64606 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -158,6 +158,7 @@ class MessageListView extends StatefulWidget { this.onAttachmentTap, this.textBuilder, this.onLinkTap, + this.pinPermissions = const [], }) : super(key: key); /// Function used to build a custom message widget @@ -264,6 +265,9 @@ class MessageListView extends StatefulWidget { /// Callback for when link is tapped final void Function(String link)? onLinkTap; + /// A List of user types that have permission to pin messages + final List pinPermissions; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -800,6 +804,10 @@ class _MessageListViewState extends State { ) { final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isOnlyEmoji = message.text!.isOnlyEmoji; + final currentUser = StreamChat.of(context).user; + final members = StreamChannel.of(context).channel.state?.members ?? []; + final currentUserMember = + members.firstWhere((e) => e.user!.id == currentUser!.id); final chatThemeData = StreamChatTheme.of(context); return MessageWidget( @@ -853,6 +861,7 @@ class _MessageListViewState extends State { textBuilder: widget.textBuilder as Widget Function(BuildContext, Message)?, onLinkTap: widget.onLinkTap, + showPinButton: widget.pinPermissions.contains(currentUserMember.role), ); } @@ -939,6 +948,11 @@ class _MessageListViewState extends State { ? BorderSide.none : null; + final currentUser = StreamChat.of(context).user; + final members = StreamChannel.of(context).channel.state?.members ?? []; + final currentUserMember = + members.firstWhere((e) => e.user!.id == currentUser!.id); + final chatThemeData = StreamChatTheme.of(context); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), @@ -1052,6 +1066,7 @@ class _MessageListViewState extends State { textBuilder: widget.textBuilder as Widget Function(BuildContext, Message)?, onLinkTap: widget.onLinkTap, + showPinButton: widget.pinPermissions.contains(currentUserMember.role), ); if (!message.isDeleted && From 23fbfbeff9b831576d9652a540aef54feddf6a1a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 27 May 2021 16:41:04 +0530 Subject: [PATCH 03/10] fix: Now caches message before update --- packages/stream_chat/lib/src/api/channel.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 2b6bc0a9..2f95be78 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -411,6 +411,8 @@ class Channel { /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. Future updateMessage(Message message) async { + var currentMessage = state?.messages.firstWhere((e) => e.id == message.id); + // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. _messageAttachmentsUploadCompleter @@ -459,6 +461,10 @@ class Channel { } catch (error) { if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); + } else if (error is ApiError) { + if(currentMessage != null) { + state?.addMessage(currentMessage); + } } rethrow; } From edf0d84b317a065a939326de41b3432c4a63fd1d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 27 May 2021 18:57:30 +0530 Subject: [PATCH 04/10] fix: Corrected message padding --- packages/stream_chat_flutter/lib/src/message_widget.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 3c4aec14..342f17c3 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -1136,7 +1136,7 @@ class _MessageWidgetState extends State final pinnedByMe = StreamChat.of(context).user!.id == pinnedBy!.id; return Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), + padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), child: Row( mainAxisSize: MainAxisSize.min, children: [ From 39d82c75ad4226ca0b3c9ef337802cc40f66faa9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 2 Jun 2021 15:55:39 +0530 Subject: [PATCH 05/10] feat: Added new API endpoint --- packages/stream_chat/lib/src/api/channel.dart | 41 +++++++++++++++---- packages/stream_chat/lib/src/client.dart | 35 +++++++++++----- 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index 2f95be78..a10ec32d 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -462,7 +462,7 @@ class Channel { if (error is DioError && error.type != DioErrorType.response) { state?.retryQueue?.add([message]); } else if (error is ApiError) { - if(currentMessage != null) { + if (currentMessage != null) { state?.addMessage(currentMessage); } } @@ -470,6 +470,27 @@ class Channel { } } + /// Partially updates the [message] in this channel. + Future partiallyUpdateMessage( + Message message, Map data) async { + try { + final response = await _client.partiallyUpdateMessage(message.id, data); + + final m = response.message.copyWith( + ownReactions: message.ownReactions, + ); + + state?.addMessage(m); + + return response; + } catch (error) { + if (error is DioError && error.type != DioErrorType.response) { + state?.retryQueue?.add([message]); + } + rethrow; + } + } + /// Deletes the [message] from the channel. Future deleteMessage(Message message) async { // Directly deleting the local messages which are not yet sent to server @@ -533,17 +554,21 @@ class Channel { Duration(seconds: timeoutOrExpirationDate.toInt()), ); } - return updateMessage( - message.copyWith( - pinned: true, - pinExpires: pinExpires, - ), - ); + return partiallyUpdateMessage(message, { + 'set': { + 'pinned': true, + if (pinExpires != null) 'pin_expires': pinExpires.toIso8601String(), + } + }); } /// Unpins provided message Future unpinMessage(Message message) => - updateMessage(message.copyWith(pinned: false)); + partiallyUpdateMessage(message, { + 'set': { + 'pinned': false, + } + }); /// Send a file to this channel Future sendFile( diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 98b05634..e0e29fff 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1347,6 +1347,18 @@ class StreamChatClient { return decode(response.data, UpdateMessageResponse.fromJson); } + /// Partially update the given message + /// Use 'set' in map to set values + /// User 'unset' in map to unset values + Future partiallyUpdateMessage( + String id, Map data) async { + final response = await put( + '/messages/${id}', + data: data, + ); + return decode(response.data, UpdateMessageResponse.fromJson); + } + /// Deletes the given message Future deleteMessage(Message message) async { final response = await delete('/messages/${message.id}'); @@ -1385,20 +1397,21 @@ class StreamChatClient { ) .toUtc(); } - return updateMessage( - message.copyWith( - pinned: true, - pinExpires: pinExpires, - ), - ); + return partiallyUpdateMessage(message.id, { + 'set': { + 'pinned': true, + if (pinExpires != null) 'pin_expires': pinExpires.toIso8601String(), + } + }); } /// Unpins provided message - Future unpinMessage(Message message) => updateMessage( - message.copyWith( - pinned: false, - ), - ); + Future unpinMessage(Message message) => + partiallyUpdateMessage(message.id, { + 'set': { + 'pinned': false, + } + }); } /// The class that handles the state of the channel listening to the events From e3513741008ecb92d048e9e6a8da7e93b1a40e49 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 12:44:49 +0530 Subject: [PATCH 06/10] fix: fixed update tests --- .../test/src/api/channel_test.dart | 12 +++--- .../stream_chat/test/src/client_test.dart | 37 ++++++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/packages/stream_chat/test/src/api/channel_test.dart b/packages/stream_chat/test/src/api/channel_test.dart index f92b05bb..6d15f4ef 100644 --- a/packages/stream_chat/test/src/api/channel_test.dart +++ b/packages/stream_chat/test/src/api/channel_test.dart @@ -430,7 +430,7 @@ void main() { await channelClient.watch(); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -445,7 +445,7 @@ void main() { await channelClient.pinMessage(message, 30); verify(() => - mockDio.post('/messages/${message.id}', data: anything)) + mockDio.put('/messages/${message.id}', data: anything)) .called(1); }); @@ -475,7 +475,7 @@ void main() { await channelClient.watch(); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -490,7 +490,7 @@ void main() { await channelClient.pinMessage(message); verify(() => - mockDio.post('/messages/${message.id}', data: anything)) + mockDio.put('/messages/${message.id}', data: anything)) .called(1); }); @@ -520,7 +520,7 @@ void main() { await channelClient.watch(); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -535,7 +535,7 @@ void main() { await channelClient.unpinMessage(message); verify(() => - mockDio.post('/messages/${message.id}', data: anything)) + mockDio.put('/messages/${message.id}', data: anything)) .called(1); }); }); diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 53792764..431b0c04 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -745,6 +745,39 @@ void main() { data: {'message': anything})).called(1); }); + test('partiallyUpdateMessage', () async { + final mockDio = MockDio(); + + when(() => mockDio.options).thenReturn(BaseOptions()); + when(() => mockDio.interceptors).thenReturn(Interceptors()); + + final client = StreamChatClient('api-key', httpClient: mockDio); + final message = Message( + id: 'test', + text: 'demo', + ); + + when( + () => mockDio.put( + '/messages/${message.id}', + data: {'set': anything}, + ), + ).thenAnswer( + (_) async => Response( + data: jsonEncode({'message': message}), + statusCode: 200, + requestOptions: FakeRequestOptions(), + ), + ); + + await client.partiallyUpdateMessage(message.id, { + 'set': {'text': message.text} + }); + + verify(() => mockDio.put('/messages/${message.id}', + data: {'set': anything})).called(1); + }); + test('deleteMessage', () async { final mockDio = MockDio(); @@ -1094,7 +1127,7 @@ void main() { final message = Message(text: 'Hello'); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -1108,7 +1141,7 @@ void main() { await client.pinMessage(message, timeout); - verify(() => mockDio.post('/messages/${message.id}', + verify(() => mockDio.put('/messages/${message.id}', data: {'message': anything})).called(1); }); From 9f60af5e166b094165f96ab06ea63b3ee8eb336a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 12:52:57 +0530 Subject: [PATCH 07/10] fix: fixed pin tests --- packages/stream_chat/test/src/client_test.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat/test/src/client_test.dart b/packages/stream_chat/test/src/client_test.dart index 431b0c04..2450de7c 100644 --- a/packages/stream_chat/test/src/client_test.dart +++ b/packages/stream_chat/test/src/client_test.dart @@ -1142,14 +1142,14 @@ void main() { await client.pinMessage(message, timeout); verify(() => mockDio.put('/messages/${message.id}', - data: {'message': anything})).called(1); + data: {'set': anything})).called(1); }); test('should complete successfully with a null value', () async { final message = Message(text: 'Hello'); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -1163,15 +1163,15 @@ void main() { await client.pinMessage(message); - verify(() => mockDio.post('/messages/${message.id}', - data: {'message': anything})).called(1); + verify(() => mockDio.put('/messages/${message.id}', + data: {'set': anything})).called(1); }); test('should unpin message successfully', () async { final message = Message(text: 'Hello'); when( - () => mockDio.post( + () => mockDio.put( '/messages/${message.id}', data: anything, ), @@ -1185,7 +1185,7 @@ void main() { await client.unpinMessage(message); - verify(() => mockDio.post('/messages/${message.id}', + verify(() => mockDio.put('/messages/${message.id}', data: anything)).called(1); }); }); From 96f65ae6190677b1d6c9afdddd6a9a7f68c252dd Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 13:04:38 +0530 Subject: [PATCH 08/10] fix: fixed pin tests --- packages/stream_chat/lib/src/api/channel.dart | 3 ++- packages/stream_chat/lib/src/client.dart | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/api/channel.dart b/packages/stream_chat/lib/src/api/channel.dart index a10ec32d..212996b3 100644 --- a/packages/stream_chat/lib/src/api/channel.dart +++ b/packages/stream_chat/lib/src/api/channel.dart @@ -411,7 +411,8 @@ class Channel { /// Waits for a [_messageAttachmentsUploadCompleter] to complete /// before actually updating the message. Future updateMessage(Message message) async { - var currentMessage = state?.messages.firstWhere((e) => e.id == message.id); + final currentMessage = + state?.messages.firstWhere((e) => e.id == message.id); // Cancelling previous completer in case it's called again in the process // Eg. Updating the message while the previous call is in progress. diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index e0e29fff..6c3b71f0 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1353,7 +1353,7 @@ class StreamChatClient { Future partiallyUpdateMessage( String id, Map data) async { final response = await put( - '/messages/${id}', + '/messages/$id', data: data, ); return decode(response.data, UpdateMessageResponse.fromJson); From 244c77cbc74d333e69f799d9adf90545b153bbb8 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 13:45:03 +0530 Subject: [PATCH 09/10] fix: Timestamp alignment --- .../lib/src/message_widget.dart | 277 +++++++++--------- 1 file changed, 145 insertions(+), 132 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 342f17c3..f2f08d82 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -490,152 +490,165 @@ class _MessageWidgetState extends State ? AlignmentDirectional.bottomEnd : AlignmentDirectional.bottomStart, children: [ - Column( - crossAxisAlignment: widget.reverse - ? CrossAxisAlignment.end - : CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.message.pinned && - widget.message.pinnedBy != null && - widget.showPinHighlight) - _buildPinnedMessage(widget.message), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.showUserAvatar == DisplayWidget.show && - widget.message.user != null) ...[ - _buildUserAvatar(), - const SizedBox(width: 4), - ], - if (widget.showUserAvatar == DisplayWidget.hide) - SizedBox(width: avatarWidth + 4), - Flexible( - child: PortalEntry( - portal: Container( - transform: Matrix4.translationValues( - widget.reverse ? 12 : -12, 0, 0), - constraints: const BoxConstraints( - maxWidth: 22 * 6.0), - child: _buildReactionIndicator(context), - ), - portalAnchor: - Alignment(widget.reverse ? 1 : -1, -1), - childAnchor: - Alignment(widget.reverse ? -1 : 1, -1), - 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) - ? Container( - // ignore: lines_longer_than_80_chars - margin: EdgeInsets.symmetric( - horizontal: + Padding( + padding: EdgeInsets.only( + bottom: + isPinned && widget.showPinHighlight ? 8.0 : 0.0, + ), + child: Column( + crossAxisAlignment: widget.reverse + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.message.pinned && + widget.message.pinnedBy != null && + widget.showPinHighlight) + _buildPinnedMessage(widget.message), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.showUserAvatar == + DisplayWidget.show && + widget.message.user != null) ...[ + _buildUserAvatar(), + const SizedBox(width: 4), + ], + if (widget.showUserAvatar == DisplayWidget.hide) + SizedBox(width: avatarWidth + 4), + Flexible( + child: PortalEntry( + portal: Container( + transform: Matrix4.translationValues( + widget.reverse ? 12 : -12, 0, 0), + constraints: const BoxConstraints( + maxWidth: 22 * 6.0), + child: _buildReactionIndicator(context), + ), + portalAnchor: + Alignment(widget.reverse ? 1 : -1, -1), + childAnchor: + Alignment(widget.reverse ? -1 : 1, -1), + 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) + ? Container( + // ignore: lines_longer_than_80_chars + margin: EdgeInsets.symmetric( + horizontal: + // ignore: lines_longer_than_80_chars + widget.showUserAvatar == + // ignore: lines_longer_than_80_chars + DisplayWidget.gone + ? 0 + : 4.0), + child: DeletedMessage( + borderRadiusGeometry: widget + .borderRadiusGeometry, + borderSide: + widget.borderSide, + shape: widget.shape, + messageTheme: + widget.messageTheme, + ), + ) + : Card( + clipBehavior: Clip.antiAlias, + elevation: 0, + margin: EdgeInsets.symmetric( + horizontal: (isFailedState + ? 15.0 + : 0.0) + // ignore: lines_longer_than_80_chars - widget.showUserAvatar == - // ignore: lines_longer_than_80_chars + (widget.showUserAvatar == DisplayWidget .gone ? 0 : 4.0), - child: DeletedMessage( - borderRadiusGeometry: widget - .borderRadiusGeometry, - borderSide: widget.borderSide, - shape: widget.shape, - messageTheme: - widget.messageTheme, - ), - ) - : Card( - clipBehavior: Clip.antiAlias, - elevation: 0, - margin: EdgeInsets.symmetric( - horizontal: (isFailedState - ? 15.0 - : 0.0) + - // ignore: lines_longer_than_80_chars - (widget.showUserAvatar == - DisplayWidget.gone - ? 0 - : 4.0), - ), - shape: widget.shape ?? - RoundedRectangleBorder( - side: widget.borderSide ?? - BorderSide( - color: widget - // ignore: lines_longer_than_80_chars - .messageTheme - // ignore: lines_longer_than_80_chars - .messageBorderColor ?? - Colors.grey, - ), - borderRadius: widget - // ignore: lines_longer_than_80_chars - .borderRadiusGeometry ?? - BorderRadius.zero, - ), - color: _getBackgroundColor(), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.end, - mainAxisSize: - MainAxisSize.min, - children: [ - if (hasQuotedMessage) - _buildQuotedMessage(), - if (hasNonUrlAttachments) - _parseAttachments(), - if (!isGiphy) - _buildTextBubble(), - ], + ), + shape: widget.shape ?? + RoundedRectangleBorder( + side: widget + .borderSide ?? + BorderSide( + color: widget + // ignore: lines_longer_than_80_chars + .messageTheme + // ignore: lines_longer_than_80_chars + .messageBorderColor ?? + Colors.grey, + ), + borderRadius: widget + // ignore: lines_longer_than_80_chars + .borderRadiusGeometry ?? + BorderRadius.zero, + ), + color: _getBackgroundColor(), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + mainAxisSize: + MainAxisSize.min, + children: [ + if (hasQuotedMessage) + _buildQuotedMessage(), + if (hasNonUrlAttachments) + _parseAttachments(), + if (!isGiphy) + _buildTextBubble(), + ], + ), ), + ), + if (widget.showReactionPickerIndicator) + Positioned( + right: widget.reverse ? null : 4, + left: widget.reverse ? 4 : null, + top: -8, + child: CustomPaint( + painter: ReactionBubblePainter( + StreamChatTheme.of(context) + .colorTheme + .white, + Colors.transparent, + Colors.transparent, + tailCirclesSpace: 1, ), - ), - if (widget.showReactionPickerIndicator) - Positioned( - right: widget.reverse ? null : 4, - left: widget.reverse ? 4 : null, - top: -8, - child: CustomPaint( - painter: ReactionBubblePainter( - StreamChatTheme.of(context) - .colorTheme - .white, - Colors.transparent, - Colors.transparent, - tailCirclesSpace: 1, ), ), - ), - ], + ], + ), ), ), - ), - ], - ), - if (showBottomRow) - SizedBox(height: context.textScaleFactor * 18.0), - ], + ], + ), + if (showBottomRow) + SizedBox(height: context.textScaleFactor * 18.0), + ], + ), ), if (showBottomRow) Padding( - padding: EdgeInsets.only(left: leftPadding), + padding: EdgeInsets.only( + left: leftPadding, + bottom: + isPinned && widget.showPinHighlight ? 6.0 : 0.0, + ), child: _bottomRow, ), if (isFailedState) From 7e463a96633505e33fd75c4a8ac7378367fb41b5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Jun 2021 19:29:00 +0530 Subject: [PATCH 10/10] fix: Fixed trailing comma --- packages/stream_chat/lib/src/client.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client.dart b/packages/stream_chat/lib/src/client.dart index 6c3b71f0..0d584c92 100644 --- a/packages/stream_chat/lib/src/client.dart +++ b/packages/stream_chat/lib/src/client.dart @@ -1351,7 +1351,9 @@ class StreamChatClient { /// Use 'set' in map to set values /// User 'unset' in map to unset values Future partiallyUpdateMessage( - String id, Map data) async { + String id, + Map data, + ) async { final response = await put( '/messages/$id', data: data,